ansible/
platform.rs

1//! Platform compatibility and system requirements validation.
2//!
3//! This module provides utilities for checking platform compatibility,
4//! validating Ansible installation, and gathering system information.
5
6use crate::errors::{AnsibleError, Result};
7use std::process::Command;
8
9/// Platform compatibility and system requirements validator.
10///
11/// The `PlatformValidator` provides static methods for checking platform
12/// compatibility, validating Ansible installation, and verifying that all
13/// required components are available.
14///
15/// # Examples
16///
17/// ## Basic Platform Check
18///
19/// ```rust,no_run
20/// use ansible::PlatformValidator;
21///
22/// // Check if current platform is supported
23/// PlatformValidator::check_platform()?;
24///
25/// // Check platform compatibility without panicking
26/// if PlatformValidator::is_platform_supported() {
27///     println!("Platform is supported");
28/// } else {
29///     println!("Platform not supported");
30/// }
31/// # Ok::<(), ansible::AnsibleError>(())
32/// ```
33///
34/// ## Ansible Installation Check
35///
36/// ```rust,no_run
37/// use ansible::PlatformValidator;
38///
39/// // Check if Ansible is installed
40/// match PlatformValidator::check_ansible_installation() {
41///     Ok(version) => println!("Ansible version: {}", version),
42///     Err(e) => eprintln!("Ansible not found: {}", e),
43/// }
44/// # Ok::<(), ansible::AnsibleError>(())
45/// ```
46///
47/// ## Component Availability
48///
49/// ```rust,no_run
50/// use ansible::PlatformValidator;
51///
52/// // Check individual components
53/// let components = [
54///     ("ansible", PlatformValidator::check_ansible_installation as fn() -> Result<String, _>),
55///     ("ansible-playbook", PlatformValidator::check_ansible_playbook as fn() -> Result<String, _>),
56///     ("ansible-vault", PlatformValidator::check_ansible_vault as fn() -> Result<String, _>),
57/// ];
58///
59/// for (name, check_fn) in &components {
60///     match check_fn() {
61///         Ok(_) => println!("✅ {} is available", name),
62///         Err(_) => println!("❌ {} is not available", name),
63///     }
64/// }
65/// # Ok::<(), ansible::AnsibleError>(())
66/// ```
67pub struct PlatformValidator;
68
69impl PlatformValidator {
70    /// Check if the current platform is supported
71    pub fn check_platform() -> Result<()> {
72        #[cfg(not(any(
73            target_os = "linux",
74            target_os = "macos", 
75            target_os = "freebsd",
76            target_os = "openbsd",
77            target_os = "netbsd"
78        )))]
79        {
80            return Err(AnsibleError::unsupported_platform(format!(
81                "ansible-rs only supports Unix-like systems. Current platform: {}",
82                std::env::consts::OS
83            )));
84        }
85
86        Ok(())
87    }
88
89    /// Check if Ansible is installed and accessible
90    pub fn check_ansible_installation() -> Result<String> {
91        Self::check_platform()?;
92
93        let output = Command::new("ansible")
94            .arg("--version")
95            .output()
96            .map_err(|e| {
97                AnsibleError::command_not_found(format!(
98                    "Ansible command not found. Please install Ansible first. Error: {}",
99                    e
100                ))
101            })?;
102
103        if !output.status.success() {
104            let stderr = String::from_utf8_lossy(&output.stderr);
105            return Err(AnsibleError::command_failed(
106                "Failed to get Ansible version",
107                output.status.code(),
108                None,
109                Some(stderr.to_string()),
110            ));
111        }
112
113        let version_output = String::from_utf8_lossy(&output.stdout);
114        Ok(version_output.to_string())
115    }
116
117    /// Check if ansible-playbook is available
118    pub fn check_ansible_playbook() -> Result<String> {
119        Self::check_platform()?;
120
121        let output = Command::new("ansible-playbook")
122            .arg("--version")
123            .output()
124            .map_err(|e| {
125                AnsibleError::command_not_found(format!(
126                    "ansible-playbook command not found. Please install Ansible first. Error: {}",
127                    e
128                ))
129            })?;
130
131        if !output.status.success() {
132            let stderr = String::from_utf8_lossy(&output.stderr);
133            return Err(AnsibleError::command_failed(
134                "Failed to get ansible-playbook version",
135                output.status.code(),
136                None,
137                Some(stderr.to_string()),
138            ));
139        }
140
141        let version_output = String::from_utf8_lossy(&output.stdout);
142        Ok(version_output.to_string())
143    }
144
145    /// Check if ansible-vault is available
146    pub fn check_ansible_vault() -> Result<String> {
147        Self::check_platform()?;
148
149        let output = Command::new("ansible-vault")
150            .arg("--help")
151            .output()
152            .map_err(|e| {
153                AnsibleError::command_not_found(format!(
154                    "ansible-vault command not found. Please install Ansible first. Error: {}",
155                    e
156                ))
157            })?;
158
159        if !output.status.success() {
160            let stderr = String::from_utf8_lossy(&output.stderr);
161            return Err(AnsibleError::command_failed(
162                "Failed to check ansible-vault availability",
163                output.status.code(),
164                None,
165                Some(stderr.to_string()),
166            ));
167        }
168
169        Ok("ansible-vault is available".to_string())
170    }
171
172    /// Check if ansible-config is available
173    pub fn check_ansible_config() -> Result<String> {
174        Self::check_platform()?;
175
176        let output = Command::new("ansible-config")
177            .arg("--help")
178            .output()
179            .map_err(|e| {
180                AnsibleError::command_not_found(format!(
181                    "ansible-config command not found. Please install Ansible first. Error: {}",
182                    e
183                ))
184            })?;
185
186        if !output.status.success() {
187            let stderr = String::from_utf8_lossy(&output.stderr);
188            return Err(AnsibleError::command_failed(
189                "Failed to check ansible-config availability",
190                output.status.code(),
191                None,
192                Some(stderr.to_string()),
193            ));
194        }
195
196        Ok("ansible-config is available".to_string())
197    }
198
199    /// Check if ansible-inventory is available
200    pub fn check_ansible_inventory() -> Result<String> {
201        Self::check_platform()?;
202
203        let output = Command::new("ansible-inventory")
204            .arg("--help")
205            .output()
206            .map_err(|e| {
207                AnsibleError::command_not_found(format!(
208                    "ansible-inventory command not found. Please install Ansible first. Error: {}",
209                    e
210                ))
211            })?;
212
213        if !output.status.success() {
214            let stderr = String::from_utf8_lossy(&output.stderr);
215            return Err(AnsibleError::command_failed(
216                "Failed to check ansible-inventory availability",
217                output.status.code(),
218                None,
219                Some(stderr.to_string()),
220            ));
221        }
222
223        Ok("ansible-inventory is available".to_string())
224    }
225
226    /// Comprehensive system check
227    pub fn check_all_requirements() -> Result<SystemInfo> {
228        Self::check_platform()?;
229
230        let ansible_version = Self::check_ansible_installation()?;
231        let playbook_available = Self::check_ansible_playbook().is_ok();
232        let vault_available = Self::check_ansible_vault().is_ok();
233        let config_available = Self::check_ansible_config().is_ok();
234        let inventory_available = Self::check_ansible_inventory().is_ok();
235
236        Ok(SystemInfo {
237            platform: std::env::consts::OS.to_string(),
238            architecture: std::env::consts::ARCH.to_string(),
239            ansible_version,
240            playbook_available,
241            vault_available,
242            config_available,
243            inventory_available,
244        })
245    }
246
247    /// Get minimum required Ansible version
248    pub fn minimum_ansible_version() -> &'static str {
249        "2.9"
250    }
251
252    /// Get supported platforms
253    pub fn supported_platforms() -> Vec<&'static str> {
254        vec![
255            "linux",
256            "macos",
257            "freebsd", 
258            "openbsd",
259            "netbsd",
260        ]
261    }
262
263    /// Check if current platform is supported
264    pub fn is_platform_supported() -> bool {
265        Self::supported_platforms().contains(&std::env::consts::OS)
266    }
267}
268
269/// Comprehensive system information and feature availability.
270///
271/// The `SystemInfo` struct provides detailed information about the current
272/// system, including platform details, Ansible version, and availability
273/// of various Ansible components.
274///
275/// # Examples
276///
277/// ## Getting System Information
278///
279/// ```rust,no_run
280/// use ansible::get_system_info;
281///
282/// let system_info = get_system_info()?;
283/// println!("{}", system_info);
284///
285/// if system_info.is_fully_supported() {
286///     println!("All Ansible features are available!");
287/// } else {
288///     println!("Missing features: {:?}", system_info.missing_features());
289/// }
290/// # Ok::<(), ansible::AnsibleError>(())
291/// ```
292///
293/// ## Feature Checking
294///
295/// ```rust,no_run
296/// use ansible::get_system_info;
297///
298/// let system_info = get_system_info()?;
299///
300/// for (feature, available) in system_info.feature_summary() {
301///     let status = if available { "✅" } else { "❌" };
302///     println!("{} {}", status, feature);
303/// }
304/// # Ok::<(), ansible::AnsibleError>(())
305/// ```
306#[derive(Debug, Clone)]
307pub struct SystemInfo {
308    /// Operating system name (e.g., "linux", "macos")
309    pub platform: String,
310
311    /// System architecture (e.g., "x86_64", "aarch64")
312    pub architecture: String,
313
314    /// Ansible version string (empty if not installed)
315    pub ansible_version: String,
316
317    /// Whether ansible-playbook is available
318    pub playbook_available: bool,
319
320    /// Whether ansible-vault is available
321    pub vault_available: bool,
322
323    /// Whether ansible-config is available
324    pub config_available: bool,
325
326    /// Whether ansible-inventory is available
327    pub inventory_available: bool,
328}
329
330impl SystemInfo {
331    /// Check if all required components are available
332    pub fn is_fully_supported(&self) -> bool {
333        self.playbook_available 
334            && self.vault_available 
335            && self.config_available 
336            && self.inventory_available
337    }
338
339    /// Get a summary of available features
340    pub fn feature_summary(&self) -> Vec<(String, bool)> {
341        vec![
342            ("ansible".to_string(), !self.ansible_version.is_empty()),
343            ("ansible-playbook".to_string(), self.playbook_available),
344            ("ansible-vault".to_string(), self.vault_available),
345            ("ansible-config".to_string(), self.config_available),
346            ("ansible-inventory".to_string(), self.inventory_available),
347        ]
348    }
349
350    /// Get missing features
351    pub fn missing_features(&self) -> Vec<String> {
352        let mut missing = Vec::new();
353        
354        if self.ansible_version.is_empty() {
355            missing.push("ansible".to_string());
356        }
357        if !self.playbook_available {
358            missing.push("ansible-playbook".to_string());
359        }
360        if !self.vault_available {
361            missing.push("ansible-vault".to_string());
362        }
363        if !self.config_available {
364            missing.push("ansible-config".to_string());
365        }
366        if !self.inventory_available {
367            missing.push("ansible-inventory".to_string());
368        }
369        
370        missing
371    }
372}
373
374impl std::fmt::Display for SystemInfo {
375    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376        writeln!(f, "System Information:")?;
377        writeln!(f, "  Platform: {} ({})", self.platform, self.architecture)?;
378        writeln!(f, "  Ansible Version: {}", self.ansible_version.trim())?;
379        writeln!(f, "  Available Features:")?;
380        
381        for (feature, available) in self.feature_summary() {
382            let status = if available { "✅" } else { "❌" };
383            writeln!(f, "    {} {}", status, feature)?;
384        }
385        
386        if !self.is_fully_supported() {
387            writeln!(f, "  Missing Features: {:?}", self.missing_features())?;
388        }
389        
390        Ok(())
391    }
392}
393
394/// Convenience function to validate system requirements at startup
395pub fn validate_system() -> Result<()> {
396    PlatformValidator::check_platform()?;
397    PlatformValidator::check_ansible_installation()?;
398    Ok(())
399}
400
401/// Convenience function to get system information
402pub fn get_system_info() -> Result<SystemInfo> {
403    PlatformValidator::check_all_requirements()
404}