feat: Add service manager support
- Add a new service manager crate for dynamic service management - Integrate service manager with Rhai for scripting - Provide examples for circle worker management and basic usage - Add comprehensive tests for service lifecycle and error handling - Implement cross-platform support for macOS and Linux (zinit/systemd)
This commit is contained in:
251
service_manager/src/rhai.rs
Normal file
251
service_manager/src/rhai.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
//! Rhai integration for the service manager module
|
||||
//!
|
||||
//! This module provides Rhai scripting support for service management operations.
|
||||
|
||||
use crate::{create_service_manager, ServiceConfig, ServiceManager};
|
||||
use rhai::{Engine, EvalAltResult, Map};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A wrapper around ServiceManager that can be used in Rhai
|
||||
#[derive(Clone)]
|
||||
pub struct RhaiServiceManager {
|
||||
inner: Arc<Box<dyn ServiceManager>>,
|
||||
}
|
||||
|
||||
impl RhaiServiceManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(create_service_manager()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the service manager module with a Rhai engine
|
||||
pub fn register_service_manager_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Factory function to create service manager
|
||||
engine.register_type::<RhaiServiceManager>();
|
||||
engine.register_fn("create_service_manager", RhaiServiceManager::new);
|
||||
|
||||
// Service management functions
|
||||
engine.register_fn(
|
||||
"start",
|
||||
|manager: &mut RhaiServiceManager, config: Map| -> Result<(), Box<EvalAltResult>> {
|
||||
let service_config = map_to_service_config(config)?;
|
||||
manager
|
||||
.inner
|
||||
.start(&service_config)
|
||||
.map_err(|e| format!("Failed to start service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"stop",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.stop(&service_name)
|
||||
.map_err(|e| format!("Failed to stop service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"restart",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.restart(&service_name)
|
||||
.map_err(|e| format!("Failed to restart service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"status",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<String, Box<EvalAltResult>> {
|
||||
let status = manager
|
||||
.inner
|
||||
.status(&service_name)
|
||||
.map_err(|e| format!("Failed to get service status: {}", e))?;
|
||||
Ok(format!("{:?}", status))
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"logs",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String,
|
||||
lines: i64|
|
||||
-> Result<String, Box<EvalAltResult>> {
|
||||
let lines_opt = if lines > 0 {
|
||||
Some(lines as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
manager
|
||||
.inner
|
||||
.logs(&service_name, lines_opt)
|
||||
.map_err(|e| format!("Failed to get service logs: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"list",
|
||||
|manager: &mut RhaiServiceManager| -> Result<Vec<String>, Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.list()
|
||||
.map_err(|e| format!("Failed to list services: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"remove",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.remove(&service_name)
|
||||
.map_err(|e| format!("Failed to remove service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"exists",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<bool, Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.exists(&service_name)
|
||||
.map_err(|e| format!("Failed to check if service exists: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"start_and_confirm",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
config: Map,
|
||||
timeout_secs: i64|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
let service_config = map_to_service_config(config)?;
|
||||
let timeout = if timeout_secs > 0 {
|
||||
timeout_secs as u64
|
||||
} else {
|
||||
30
|
||||
};
|
||||
manager
|
||||
.inner
|
||||
.start_and_confirm(&service_config, timeout)
|
||||
.map_err(|e| format!("Failed to start and confirm service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"start_existing_and_confirm",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String,
|
||||
timeout_secs: i64|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
let timeout = if timeout_secs > 0 {
|
||||
timeout_secs as u64
|
||||
} else {
|
||||
30
|
||||
};
|
||||
manager
|
||||
.inner
|
||||
.start_existing_and_confirm(&service_name, timeout)
|
||||
.map_err(|e| format!("Failed to start existing service and confirm: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert a Rhai Map to a ServiceConfig
|
||||
fn map_to_service_config(map: Map) -> Result<ServiceConfig, Box<EvalAltResult>> {
|
||||
let name = map
|
||||
.get("name")
|
||||
.and_then(|v| v.clone().into_string().ok())
|
||||
.ok_or("Service config must have a 'name' field")?;
|
||||
|
||||
let binary_path = map
|
||||
.get("binary_path")
|
||||
.and_then(|v| v.clone().into_string().ok())
|
||||
.ok_or("Service config must have a 'binary_path' field")?;
|
||||
|
||||
let args = map
|
||||
.get("args")
|
||||
.and_then(|v| v.clone().try_cast::<rhai::Array>())
|
||||
.map(|arr| {
|
||||
arr.into_iter()
|
||||
.filter_map(|v| v.into_string().ok())
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let working_directory = map
|
||||
.get("working_directory")
|
||||
.and_then(|v| v.clone().into_string().ok());
|
||||
|
||||
let environment = map
|
||||
.get("environment")
|
||||
.and_then(|v| v.clone().try_cast::<Map>())
|
||||
.map(|env_map| {
|
||||
env_map
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| v.into_string().ok().map(|val| (k.to_string(), val)))
|
||||
.collect::<HashMap<String, String>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let auto_restart = map
|
||||
.get("auto_restart")
|
||||
.and_then(|v| v.as_bool().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(ServiceConfig {
|
||||
name,
|
||||
binary_path,
|
||||
args,
|
||||
working_directory,
|
||||
environment,
|
||||
auto_restart,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rhai::{Engine, Map};
|
||||
|
||||
#[test]
|
||||
fn test_register_service_manager_module() {
|
||||
let mut engine = Engine::new();
|
||||
register_service_manager_module(&mut engine).unwrap();
|
||||
|
||||
// Test that the functions are registered
|
||||
// Note: Rhai doesn't expose a public API to check if functions are registered
|
||||
// So we'll just verify the module registration doesn't panic
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_to_service_config() {
|
||||
let mut map = Map::new();
|
||||
map.insert("name".into(), "test-service".into());
|
||||
map.insert("binary_path".into(), "/bin/echo".into());
|
||||
map.insert("auto_restart".into(), true.into());
|
||||
|
||||
let config = map_to_service_config(map).unwrap();
|
||||
assert_eq!(config.name, "test-service");
|
||||
assert_eq!(config.binary_path, "/bin/echo");
|
||||
assert_eq!(config.auto_restart, true);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user