ansible/
config.rs

1//! Ansible configuration querying and management.
2//!
3//! This module provides the [`AnsibleConfig`] struct for querying and managing
4//! Ansible configuration settings, along with related enums for type-safe configuration.
5
6use crate::command_config::CommandConfig;
7use crate::errors::{AnsibleError, Result};
8use std::fmt::{Display, Formatter};
9use std::process;
10
11/// Ansible configuration management utility.
12///
13/// The `AnsibleConfig` struct provides a comprehensive interface for querying
14/// and managing Ansible configuration settings. It supports listing, dumping,
15/// viewing, initializing, and validating configuration files.
16///
17/// # Examples
18///
19/// ## Basic Configuration Queries
20///
21/// ```rust,no_run
22/// use ansible::AnsibleConfig;
23///
24/// let mut config = AnsibleConfig::new();
25///
26/// // List all configuration options
27/// let config_list = config.list()?;
28/// println!("Configuration: {}", config_list);
29///
30/// // Dump current configuration
31/// let config_dump = config.dump()?;
32/// println!("Config dump: {}", config_dump);
33/// # Ok::<(), ansible::AnsibleError>(())
34/// ```
35///
36/// ## Formatted Output
37///
38/// ```rust,no_run
39/// use ansible::{AnsibleConfig, ConfigFormat};
40///
41/// let mut config = AnsibleConfig::new();
42/// config.set_format(ConfigFormat::Json);
43///
44/// // Get configuration in JSON format
45/// let json_config = config.dump()?;
46/// println!("JSON config: {}", json_config);
47/// # Ok::<(), ansible::AnsibleError>(())
48/// ```
49///
50/// ## Plugin-Specific Configuration
51///
52/// ```rust,no_run
53/// use ansible::{AnsibleConfig, PluginType};
54///
55/// let mut config = AnsibleConfig::new();
56/// config.set_plugin_type(PluginType::Callback);
57///
58/// // List only callback plugin configuration
59/// let callback_config = config.list()?;
60/// println!("Callback plugins: {}", callback_config);
61/// # Ok::<(), ansible::AnsibleError>(())
62/// ```
63///
64/// ## Configuration File Management
65///
66/// ```rust,no_run
67/// use ansible::AnsibleConfig;
68///
69/// let mut config = AnsibleConfig::new();
70///
71/// // Initialize a new configuration file
72/// config.init()?;
73///
74/// // Validate existing configuration
75/// config.validate()?;
76///
77/// // View specific configuration file
78/// config.set_config_file("custom.cfg");
79/// let content = config.view()?;
80/// # Ok::<(), ansible::AnsibleError>(())
81/// ```
82#[derive(Debug, Clone)]
83pub struct AnsibleConfig {
84    pub(crate) command: String,
85    pub(crate) cfg: CommandConfig,
86    pub(crate) config_file: Option<String>,
87    pub(crate) plugin_type: Option<String>,
88}
89
90impl Default for AnsibleConfig {
91    fn default() -> Self {
92        Self {
93            command: "ansible-config".into(),
94            cfg: CommandConfig::default(),
95            config_file: None,
96            plugin_type: None,
97        }
98    }
99}
100
101impl Display for AnsibleConfig {
102    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
103        write!(f, "{}", self.command)?;
104
105        if let Some(ref config_file) = self.config_file {
106            write!(f, " --config {}", config_file)?;
107        }
108
109        if let Some(ref plugin_type) = self.plugin_type {
110            write!(f, " --type {}", plugin_type)?;
111        }
112
113        if !self.cfg.args.is_empty() {
114            write!(f, " {}", self.cfg.args.join(" "))?;
115        }
116
117        Ok(())
118    }
119}
120
121impl AnsibleConfig {
122    /// Create a new AnsibleConfig instance
123    pub fn new() -> Self {
124        Self::default()
125    }
126
127    /// Set the configuration file path
128    pub fn set_config_file(&mut self, file_path: impl Into<String>) -> &mut Self {
129        self.config_file = Some(file_path.into());
130        self
131    }
132
133    /// Set the output format for configuration commands.
134    ///
135    /// This method sets the format for configuration output.
136    ///
137    /// # Examples
138    ///
139    /// ```rust
140    /// use ansible::{AnsibleConfig, ConfigFormat};
141    ///
142    /// let mut config = AnsibleConfig::new();
143    /// config.set_format(ConfigFormat::Json);
144    /// ```
145    pub fn set_format(&mut self, format: ConfigFormat) -> &mut Self {
146        self.arg("--format").arg(format.to_string());
147        self
148    }
149
150    /// Set the plugin type filter.
151    ///
152    /// This method filters configuration to a specific plugin type.
153    ///
154    /// # Examples
155    ///
156    /// ```rust
157    /// use ansible::{AnsibleConfig, PluginType};
158    ///
159    /// let mut config = AnsibleConfig::new();
160    /// config.set_plugin_type(PluginType::Callback);
161    /// ```
162    pub fn set_plugin_type(&mut self, plugin_type: PluginType) -> &mut Self {
163        self.plugin_type = Some(plugin_type.to_string());
164        self
165    }
166
167    /// Add a custom argument
168    pub fn arg(&mut self, arg: impl Into<String>) -> &mut Self {
169        self.cfg.arg(arg.into());
170        self
171    }
172
173    /// Add multiple arguments
174    pub fn args<I, S>(&mut self, args: I) -> &mut Self
175    where
176        I: IntoIterator<Item = S>,
177        S: Into<String>,
178    {
179        let args_vec: Vec<String> = args.into_iter().map(|s| s.into()).collect();
180        self.cfg.args(args_vec);
181        self
182    }
183
184    /// Set environment variables from the system
185    pub fn set_system_envs(&mut self) -> &mut Self {
186        self.cfg.set_system_envs();
187        self
188    }
189
190    /// Add an environment variable
191    pub fn add_env(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
192        self.cfg.add_env(key, value);
193        self
194    }
195
196    /// Execute a config command with the given action and arguments
197    fn execute_config_command(&self, action: &str, args: &[String]) -> Result<String> {
198        let mut cmd = process::Command::new(&self.command);
199        cmd.envs(&self.cfg.envs);
200        cmd.arg(action);
201
202        // Add config-specific options
203        if let Some(ref config_file) = self.config_file {
204            cmd.args(["--config", config_file]);
205        }
206
207        if let Some(ref plugin_type) = self.plugin_type {
208            cmd.args(["--type", plugin_type]);
209        }
210
211        // Add custom arguments
212        cmd.args(&self.cfg.args);
213
214        // Add action-specific arguments
215        cmd.args(args);
216
217        let output = cmd.output()?;
218
219        if !output.status.success() {
220            let stdout = String::from_utf8_lossy(&output.stdout).to_string();
221            let stderr = String::from_utf8_lossy(&output.stderr).to_string();
222            return Err(AnsibleError::command_failed(
223                format!("Ansible config {} command failed", action),
224                output.status.code(),
225                Some(stdout),
226                Some(stderr),
227            ));
228        }
229
230        let result = [output.stdout, "\n".as_bytes().to_vec(), output.stderr].concat();
231        let s = String::from_utf8_lossy(&result);
232
233        Ok(s.to_string())
234    }
235
236    /// List all available configuration options
237    pub fn list(&self) -> Result<String> {
238        self.execute_config_command("list", &[])
239    }
240
241    /// List configuration options with specific format
242    pub fn list_with_format(&self, format: ConfigFormat) -> Result<String> {
243        self.execute_config_command("list", &[
244            "--format".to_string(),
245            format.to_string(),
246        ])
247    }
248
249    /// Show current configuration settings
250    pub fn dump(&self) -> Result<String> {
251        self.execute_config_command("dump", &[])
252    }
253
254    /// Show current configuration settings with specific format
255    pub fn dump_with_format(&self, format: ConfigFormat) -> Result<String> {
256        self.execute_config_command("dump", &[
257            "--format".to_string(),
258            format.to_string(),
259        ])
260    }
261
262    /// Show only changed configuration settings
263    pub fn dump_changed_only(&self) -> Result<String> {
264        self.execute_config_command("dump", &["--only-changed".to_string()])
265    }
266
267    /// View the current configuration file
268    pub fn view(&self) -> Result<String> {
269        self.execute_config_command("view", &[])
270    }
271
272    /// Initialize a new configuration file
273    pub fn init(&self) -> Result<String> {
274        self.execute_config_command("init", &[])
275    }
276
277    /// Initialize a new configuration file with specific format
278    pub fn init_with_format(&self, format: ConfigFormat) -> Result<String> {
279        self.execute_config_command("init", &[
280            "--format".to_string(),
281            format.to_string(),
282        ])
283    }
284
285    /// Initialize a configuration file with all options disabled (commented out)
286    pub fn init_disabled(&self) -> Result<String> {
287        self.execute_config_command("init", &["--disabled".to_string()])
288    }
289
290    /// Validate the configuration file
291    pub fn validate(&self) -> Result<String> {
292        self.execute_config_command("validate", &[])
293    }
294
295    /// Validate the configuration file with specific format
296    pub fn validate_with_format(&self, format: ConfigFormat) -> Result<String> {
297        self.execute_config_command("validate", &[
298            "--format".to_string(),
299            format.to_string(),
300        ])
301    }
302
303    /// Enable verbose output
304    pub fn verbose(&mut self) -> &mut Self {
305        self.cfg.arg("-v");
306        self
307    }
308
309    /// Set multiple levels of verbosity
310    pub fn verbosity(&mut self, level: u8) -> &mut Self {
311        let v_arg = "-".to_string() + &"v".repeat(level as usize);
312        self.cfg.arg(v_arg);
313        self
314    }
315
316    /// Get a reference to the command configuration (for testing)
317    pub fn get_config(&self) -> &CommandConfig {
318        &self.cfg
319    }
320}
321
322/// Configuration output formats for ansible-config commands.
323///
324/// Specifies the format for configuration output when using dump, list,
325/// or view operations.
326///
327/// # Examples
328///
329/// ```rust
330/// use ansible::{AnsibleConfig, ConfigFormat};
331///
332/// let mut config = AnsibleConfig::new();
333/// config.set_format(ConfigFormat::Json);
334///
335/// // All available formats
336/// let formats = ConfigFormat::all();
337/// assert_eq!(formats.len(), 3);
338/// ```
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub enum ConfigFormat {
341    /// JSON format - structured data suitable for programmatic processing
342    Json,
343    /// YAML format - human-readable structured format
344    Yaml,
345    /// Display format - human-readable text format with descriptions
346    Display,
347}
348
349impl Display for ConfigFormat {
350    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
351        match self {
352            ConfigFormat::Json => write!(f, "json"),
353            ConfigFormat::Yaml => write!(f, "yaml"),
354            ConfigFormat::Display => write!(f, "display"),
355        }
356    }
357}
358
359impl ConfigFormat {
360    /// Get all available formats
361    pub fn all() -> Vec<ConfigFormat> {
362        vec![ConfigFormat::Json, ConfigFormat::Yaml, ConfigFormat::Display]
363    }
364}
365
366/// Plugin types for filtering configuration queries.
367///
368/// Ansible supports various plugin types, each with their own configuration
369/// options. This enum allows filtering configuration queries to specific
370/// plugin types.
371///
372/// # Examples
373///
374/// ```rust
375/// use ansible::{AnsibleConfig, PluginType};
376///
377/// let mut config = AnsibleConfig::new();
378/// config.set_plugin_type(PluginType::Callback);
379///
380/// // Get all available plugin types
381/// let types = PluginType::all();
382/// assert_eq!(types.len(), 11);
383/// ```
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub enum PluginType {
386    /// Become plugins - handle privilege escalation (sudo, su, etc.)
387    Become,
388    /// Cache plugins - cache facts and inventory data
389    Cache,
390    /// Callback plugins - handle output and notifications
391    Callback,
392    /// Connection plugins - handle connections to remote hosts
393    Connection,
394    /// HTTP API plugins - handle HTTP-based API connections
395    Httpapi,
396    /// Inventory plugins - parse and provide inventory data
397    Inventory,
398    /// Lookup plugins - retrieve data from external sources
399    Lookup,
400    /// NETCONF plugins - handle NETCONF protocol connections
401    Netconf,
402    /// Shell plugins - handle shell command execution
403    Shell,
404    /// Strategy plugins - control task execution strategies
405    Strategy,
406    /// Vars plugins - provide additional variables
407    Vars,
408}
409
410impl Display for PluginType {
411    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
412        match self {
413            PluginType::Become => write!(f, "become"),
414            PluginType::Cache => write!(f, "cache"),
415            PluginType::Callback => write!(f, "callback"),
416            PluginType::Connection => write!(f, "connection"),
417            PluginType::Httpapi => write!(f, "httpapi"),
418            PluginType::Inventory => write!(f, "inventory"),
419            PluginType::Lookup => write!(f, "lookup"),
420            PluginType::Netconf => write!(f, "netconf"),
421            PluginType::Shell => write!(f, "shell"),
422            PluginType::Strategy => write!(f, "strategy"),
423            PluginType::Vars => write!(f, "vars"),
424        }
425    }
426}
427
428impl PluginType {
429    /// Get all available plugin types
430    pub fn all() -> Vec<PluginType> {
431        vec![
432            PluginType::Become,
433            PluginType::Cache,
434            PluginType::Callback,
435            PluginType::Connection,
436            PluginType::Httpapi,
437            PluginType::Inventory,
438            PluginType::Lookup,
439            PluginType::Netconf,
440            PluginType::Shell,
441            PluginType::Strategy,
442            PluginType::Vars,
443        ]
444    }
445}