sal_zinit_client/
lib.rs

1//! SAL Zinit Client
2//!
3//! This crate provides a Rust interface for interacting with a Zinit process supervisor daemon.
4//! Zinit is a process and service manager for Linux systems, designed for simplicity and robustness.
5//!
6//! # Features
7//!
8//! - Async operations using tokio
9//! - Unix socket communication with Zinit daemon
10//! - Global client instance management
11//! - Comprehensive service management (start, stop, restart, monitor, etc.)
12//! - Service configuration management (create, delete, get)
13//! - Log retrieval from Zinit
14//! - Rhai scripting integration
15//!
16//! # Example
17//!
18//! ```rust,no_run
19//! use sal_zinit_client::{list, status};
20//!
21//! #[tokio::main]
22//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
23//!     let socket_path = "/var/run/zinit.sock";
24//!     
25//!     // List all services
26//!     let services = list(socket_path).await?;
27//!     println!("Services: {:?}", services);
28//!     
29//!     // Get status of a specific service
30//!     if let Some(service_name) = services.keys().next() {
31//!         let status = status(socket_path, service_name).await?;
32//!         println!("Status: {:?}", status);
33//!     }
34//!     
35//!     Ok(())
36//! }
37//! ```
38
39pub mod rhai;
40
41use lazy_static::lazy_static;
42use serde_json::Value;
43use std::collections::HashMap;
44use std::sync::atomic::{AtomicBool, Ordering};
45use std::sync::{Arc, Mutex};
46use zinit_client::{ServiceState, ServiceStatus as Status, ZinitClient, ZinitError};
47
48// Global Zinit client instance using lazy_static
49lazy_static! {
50    static ref ZINIT_CLIENT: Mutex<Option<Arc<ZinitClientWrapper>>> = Mutex::new(None);
51}
52
53// Wrapper for Zinit client to handle connection
54pub struct ZinitClientWrapper {
55    client: ZinitClient,
56    initialized: AtomicBool,
57}
58
59impl ZinitClientWrapper {
60    // Create a new Zinit client wrapper
61    fn new(client: ZinitClient) -> Self {
62        ZinitClientWrapper {
63            client,
64            initialized: AtomicBool::new(false),
65        }
66    }
67
68    // Initialize the client
69    async fn initialize(&self) -> Result<(), ZinitError> {
70        if self.initialized.load(Ordering::Relaxed) {
71            return Ok(());
72        }
73
74        // Try to list services to check if the connection works
75        let _ = self.client.list().await.map_err(|e| {
76            log::error!("Failed to initialize Zinit client: {}", e);
77            e
78        })?;
79
80        self.initialized.store(true, Ordering::Relaxed);
81        Ok(())
82    }
83
84    // List all services
85    pub async fn list(&self) -> Result<HashMap<String, ServiceState>, ZinitError> {
86        self.client.list().await
87    }
88
89    // Get status of a service
90    pub async fn status(&self, name: &str) -> Result<Status, ZinitError> {
91        self.client.status(name).await
92    }
93
94    // Start a service
95    pub async fn start(&self, name: &str) -> Result<(), ZinitError> {
96        self.client.start(name).await
97    }
98
99    // Stop a service
100    pub async fn stop(&self, name: &str) -> Result<(), ZinitError> {
101        self.client.stop(name).await
102    }
103
104    // Restart a service
105    pub async fn restart(&self, name: &str) -> Result<(), ZinitError> {
106        self.client.restart(name).await
107    }
108
109    // Monitor a service
110    pub async fn monitor(&self, name: &str) -> Result<(), ZinitError> {
111        self.client.monitor(name).await
112    }
113
114    // Forget a service (stop monitoring)
115    pub async fn forget(&self, name: &str) -> Result<(), ZinitError> {
116        self.client.forget(name).await
117    }
118
119    // Kill a service
120    pub async fn kill(&self, name: &str, signal: Option<&str>) -> Result<(), ZinitError> {
121        let signal_str = signal.unwrap_or("TERM");
122        self.client.kill(name, signal_str).await
123    }
124
125    // Create a service
126    pub async fn create_service(
127        &self,
128        name: &str,
129        service_config: Value,
130    ) -> Result<(), ZinitError> {
131        self.client.create_service(name, service_config).await
132    }
133
134    // Delete a service
135    pub async fn delete_service(&self, name: &str) -> Result<(), ZinitError> {
136        self.client.delete_service(name).await
137    }
138
139    // Get service configuration
140    pub async fn get_service(&self, name: &str) -> Result<Value, ZinitError> {
141        self.client.get_service(name).await
142    }
143
144    // Reboot the system
145    pub async fn reboot(&self) -> Result<(), ZinitError> {
146        self.client.reboot().await
147    }
148
149    // Get logs with real implementation
150    pub async fn logs(&self, filter: Option<String>) -> Result<Vec<String>, ZinitError> {
151        use futures::StreamExt;
152
153        // The logs method requires a follow parameter and filter
154        let follow = false; // Don't follow logs, just get existing ones
155        let mut log_stream = self.client.logs(follow, filter).await?;
156        let mut logs = Vec::new();
157
158        // Collect logs from the stream with a reasonable limit
159        let mut count = 0;
160        const MAX_LOGS: usize = 1000;
161
162        while let Some(log_result) = log_stream.next().await {
163            match log_result {
164                Ok(log_entry) => {
165                    // Convert LogEntry to String using Debug formatting
166                    logs.push(format!("{:?}", log_entry));
167                    count += 1;
168                    if count >= MAX_LOGS {
169                        break;
170                    }
171                }
172                Err(e) => {
173                    log::warn!("Error reading log entry: {}", e);
174                    break;
175                }
176            }
177        }
178
179        Ok(logs)
180    }
181}
182
183// Get the Zinit client instance
184pub async fn get_zinit_client(socket_path: &str) -> Result<Arc<ZinitClientWrapper>, ZinitError> {
185    // Check if we already have a client
186    {
187        let guard = ZINIT_CLIENT.lock().unwrap();
188        if let Some(ref client) = &*guard {
189            return Ok(Arc::clone(client));
190        }
191    }
192
193    // Create a new client
194    let client = create_zinit_client(socket_path).await?;
195
196    // Store the client globally
197    {
198        let mut guard = ZINIT_CLIENT.lock().unwrap();
199        *guard = Some(Arc::clone(&client));
200    }
201
202    Ok(client)
203}
204
205// Create a new Zinit client
206async fn create_zinit_client(socket_path: &str) -> Result<Arc<ZinitClientWrapper>, ZinitError> {
207    // Connect via Unix socket
208    let client = ZinitClient::new(socket_path);
209    let wrapper = Arc::new(ZinitClientWrapper::new(client));
210
211    // Initialize the client
212    wrapper.initialize().await?;
213
214    Ok(wrapper)
215}
216
217// Reset the Zinit client
218pub async fn reset(socket_path: &str) -> Result<(), ZinitError> {
219    // Clear the existing client
220    {
221        let mut client_guard = ZINIT_CLIENT.lock().unwrap();
222        *client_guard = None;
223    }
224
225    // Create a new client, only return error if it fails
226    get_zinit_client(socket_path).await?;
227    Ok(())
228}
229
230// Convenience functions for common operations
231
232// List all services - convert ServiceState to String for compatibility
233pub async fn list(socket_path: &str) -> Result<HashMap<String, String>, ZinitError> {
234    let client = get_zinit_client(socket_path).await?;
235    let services = client.list().await?;
236
237    // Convert HashMap<String, ServiceState> to HashMap<String, String>
238    let mut result = HashMap::new();
239    for (name, state) in services {
240        result.insert(name, format!("{:?}", state));
241    }
242
243    Ok(result)
244}
245
246// Get status of a service
247pub async fn status(socket_path: &str, name: &str) -> Result<Status, ZinitError> {
248    let client = get_zinit_client(socket_path).await?;
249    client.status(name).await
250}
251
252// Start a service
253pub async fn start(socket_path: &str, name: &str) -> Result<(), ZinitError> {
254    let client = get_zinit_client(socket_path).await?;
255    client.start(name).await
256}
257
258// Stop a service
259pub async fn stop(socket_path: &str, name: &str) -> Result<(), ZinitError> {
260    let client = get_zinit_client(socket_path).await?;
261    client.stop(name).await
262}
263
264// Restart a service
265pub async fn restart(socket_path: &str, name: &str) -> Result<(), ZinitError> {
266    let client = get_zinit_client(socket_path).await?;
267    client.restart(name).await
268}
269
270// Monitor a service
271pub async fn monitor(socket_path: &str, name: &str) -> Result<(), ZinitError> {
272    let client = get_zinit_client(socket_path).await?;
273    client.monitor(name).await
274}
275
276// Forget a service (stop monitoring)
277pub async fn forget(socket_path: &str, name: &str) -> Result<(), ZinitError> {
278    let client = get_zinit_client(socket_path).await?;
279    client.forget(name).await
280}
281
282// Kill a service
283pub async fn kill(socket_path: &str, name: &str, signal: Option<&str>) -> Result<(), ZinitError> {
284    let client = get_zinit_client(socket_path).await?;
285    client.kill(name, signal).await
286}
287
288// Create a service with simplified parameters
289pub async fn create_service(
290    socket_path: &str,
291    name: &str,
292    exec: &str,
293    oneshot: bool,
294) -> Result<(), ZinitError> {
295    use serde_json::json;
296
297    let service_config = json!({
298        "exec": exec,
299        "oneshot": oneshot
300    });
301
302    let client = get_zinit_client(socket_path).await?;
303    client.create_service(name, service_config).await
304}
305
306// Create a service with full parameters
307pub async fn create_service_full(
308    socket_path: &str,
309    name: &str,
310    exec: &str,
311    oneshot: bool,
312    after: Option<Vec<String>>,
313    env: Option<HashMap<String, String>>,
314    log: Option<String>,
315    test: Option<String>,
316) -> Result<(), ZinitError> {
317    use serde_json::json;
318
319    let mut service_config = json!({
320        "exec": exec,
321        "oneshot": oneshot
322    });
323
324    if let Some(after_deps) = after {
325        service_config["after"] = json!(after_deps);
326    }
327    if let Some(environment) = env {
328        service_config["env"] = json!(environment);
329    }
330    if let Some(log_path) = log {
331        service_config["log"] = json!(log_path);
332    }
333    if let Some(test_cmd) = test {
334        service_config["test"] = json!(test_cmd);
335    }
336
337    let client = get_zinit_client(socket_path).await?;
338    client.create_service(name, service_config).await
339}
340
341// Delete a service
342pub async fn delete_service(socket_path: &str, name: &str) -> Result<(), ZinitError> {
343    let client = get_zinit_client(socket_path).await?;
344    client.delete_service(name).await
345}
346
347// Get service configuration
348pub async fn get_service(socket_path: &str, name: &str) -> Result<Value, ZinitError> {
349    let client = get_zinit_client(socket_path).await?;
350    client.get_service(name).await
351}
352
353// Reboot the system
354pub async fn reboot(socket_path: &str) -> Result<(), ZinitError> {
355    let client = get_zinit_client(socket_path).await?;
356    client.reboot().await
357}
358
359// Get logs
360pub async fn logs(socket_path: &str, filter: Option<String>) -> Result<Vec<String>, ZinitError> {
361    let client = get_zinit_client(socket_path).await?;
362    client.logs(filter).await
363}