- 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)
252 lines
7.4 KiB
Rust
252 lines
7.4 KiB
Rust
//! 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);
|
|
}
|
|
}
|