ansible-rs 1.1.0

A Rust wrapper library for Ansible command-line tools (Linux/Unix only)
Documentation
//! 异步 Ansible 清单管理
//!
//! 提供完全独立的异步 Ansible 清单解析和主机管理功能。

use crate::{AnsibleInventory, AnsibleError, InventoryFormat, InventoryData};
use crate::command_config::CommandConfig;
use crate::async_support::{AsyncResult, AsyncExecute, IntoAsync, FromAsync};
use std::ffi::OsStr;
use std::fmt::Display;
use tokio::process::Command;

/// 异步 Ansible 清单管理工具
///
/// `AsyncAnsibleInventory` 提供与 [`AnsibleInventory`] 相同的功能,但所有操作都是异步的。
///
/// # Examples
///
/// ## 基本异步清单查询
///
/// ```rust,no_run
/// use ansible::{AsyncAnsibleInventory, InventoryFormat};
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let mut inventory = AsyncAnsibleInventory::new();
///     inventory
///         .set_inventory("hosts.yml")
///         .set_format(InventoryFormat::Json);
///
///     // 异步列出所有主机
///     let hosts = inventory.list().await?;
///     println!("Hosts: {}", hosts);
///
///     // 异步获取特定主机信息
///     let host_info = inventory.host("web01").await?;
///     println!("Host info: {}", host_info);
///
///     Ok(())
/// }
/// ```
///
/// ## 并发清单操作
///
/// ```rust,no_run
/// use ansible::AsyncAnsibleInventory;
/// use tokio::try_join;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let mut inventory1 = AsyncAnsibleInventory::new();
///     inventory1.set_inventory("production");
///
///     let mut inventory2 = AsyncAnsibleInventory::new();
///     inventory2.set_inventory("staging");
///
///     // 并发查询不同环境的主机
///     let (prod_hosts, staging_hosts) = try_join!(
///         inventory1.list(),
///         inventory2.list()
///     )?;
///
///     println!("Production hosts: {}", prod_hosts);
///     println!("Staging hosts: {}", staging_hosts);
///
///     Ok(())
/// }
/// ```
#[derive(Debug, Clone)]
pub struct AsyncAnsibleInventory {
    command: String,
    cfg: CommandConfig,
    inventory: Option<String>,
    playbook_dir: Option<String>,
}

impl Default for AsyncAnsibleInventory {
    fn default() -> Self {
        Self {
            command: "ansible-inventory".to_string(),
            cfg: CommandConfig::default(),
            inventory: None,
            playbook_dir: None,
        }
    }
}

impl AsyncAnsibleInventory {
    /// 创建新的 AsyncAnsibleInventory 实例
    pub fn new() -> Self {
        Self::default()
    }

    /// 设置系统环境变量
    pub fn set_system_envs(&mut self) -> &mut Self {
        self.cfg.set_system_envs();
        self
    }

    /// 过滤环境变量
    pub fn filter_envs<T, S>(&mut self, iter: T) -> &mut Self
    where
        T: IntoIterator<Item = S>,
        S: AsRef<OsStr> + Display,
    {
        self.cfg.filter_envs(iter);
        self
    }

    /// 添加环境变量
    pub fn add_env(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
        self.cfg.add_env(key, value);
        self
    }

    /// 添加命令行参数
    pub fn arg<S: AsRef<OsStr> + Display>(&mut self, arg: S) -> &mut Self {
        self.cfg.arg(arg);
        self
    }

    /// 设置清单文件或目录
    pub fn set_inventory(&mut self, inventory: impl Into<String>) -> &mut Self {
        self.inventory = Some(inventory.into());
        self
    }

    /// 设置清单文件或目录(别名)
    pub fn set_inventory_file(&mut self, inventory: impl Into<String>) -> &mut Self {
        self.set_inventory(inventory)
    }

    /// 设置 playbook 目录
    pub fn set_playbook_dir(&mut self, dir: impl Into<String>) -> &mut Self {
        self.playbook_dir = Some(dir.into());
        self
    }

    /// 设置输出格式
    pub fn set_format(&mut self, format: InventoryFormat) -> &mut Self {
        self.arg("--output").arg(format.to_string());
        self
    }

    /// 异步列出所有主机和组
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use ansible::AsyncAnsibleInventory;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let mut inventory = AsyncAnsibleInventory::new();
    ///     inventory.set_inventory("hosts.yml");
    ///
    ///     let hosts = inventory.list().await?;
    ///     println!("All hosts: {}", hosts);
    ///
    ///     Ok(())
    /// }
    /// ```
    pub async fn list(&self) -> AsyncResult<String> {
        self.execute_inventory_command(&["--list"]).await
    }

    /// 异步获取特定主机信息
    pub async fn host(&self, hostname: impl AsRef<str>) -> AsyncResult<String> {
        self.execute_inventory_command(&["--host", hostname.as_ref()]).await
    }

    /// 异步生成清单图表
    pub async fn graph(&self) -> AsyncResult<String> {
        self.execute_inventory_command(&["--graph"]).await
    }

    /// 异步输出 JSON 格式清单
    pub async fn json(&self) -> AsyncResult<String> {
        self.execute_inventory_command(&["--list"]).await
    }

    /// 异步解析清单数据为结构化格式
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use ansible::AsyncAnsibleInventory;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let mut inventory = AsyncAnsibleInventory::new();
    ///     inventory.set_inventory("hosts.yml");
    ///
    ///     let data = inventory.parse_inventory_data().await?;
    ///     for (group_name, group) in &data.groups {
    ///         println!("Group {}: {} hosts", group_name, group.hosts.len());
    ///     }
    ///
    ///     Ok(())
    /// }
    /// ```
    pub async fn parse_inventory_data(&self) -> AsyncResult<InventoryData> {
        let json_output = self.json().await?;
        let inventory_data: InventoryData = serde_json::from_str(&json_output)
            .map_err(|e| AnsibleError::parsing_failed(&format!("Failed to parse inventory JSON: {}", e)))?;
        Ok(inventory_data)
    }

    /// 执行清单命令的内部方法
    async fn execute_inventory_command(&self, args: &[&str]) -> AsyncResult<String> {
        let mut cmd = Command::new(&self.command);
        cmd.envs(&self.cfg.envs);
        cmd.args(&self.cfg.args);
        cmd.args(args);
        
        if let Some(ref inventory) = self.inventory {
            cmd.arg("--inventory").arg(inventory);
        }
        
        if let Some(ref playbook_dir) = self.playbook_dir {
            cmd.arg("--playbook-dir").arg(playbook_dir);
        }

        let output = cmd.output().await?;
        
        if !output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout).to_string();
            let stderr = String::from_utf8_lossy(&output.stderr).to_string();
            return Err(AnsibleError::command_failed(
                "Ansible inventory command failed",
                output.status.code(),
                Some(stdout),
                Some(stderr),
            ));
        }

        let result = [output.stdout, "\n".as_bytes().to_vec(), output.stderr].concat();
        let s = String::from_utf8_lossy(&result);
        Ok(s.to_string())
    }
}

impl AsyncExecute for AsyncAnsibleInventory {
    async fn execute_async(&self) -> AsyncResult<String> {
        self.list().await
    }
}

impl IntoAsync<AsyncAnsibleInventory> for AnsibleInventory {
    fn into_async(self) -> AsyncAnsibleInventory {
        AsyncAnsibleInventory {
            command: self.command,
            cfg: self.cfg,
            inventory: self.inventory,
            playbook_dir: self.playbook_dir,
        }
    }
}

impl FromAsync<AsyncAnsibleInventory> for AnsibleInventory {
    fn from_async(async_inventory: AsyncAnsibleInventory) -> Self {
        AnsibleInventory {
            command: async_inventory.command,
            cfg: async_inventory.cfg,
            inventory: async_inventory.inventory,
            playbook_dir: async_inventory.playbook_dir,
        }
    }
}

impl std::fmt::Display for AsyncAnsibleInventory {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.command)
    }
}