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}