ansible-rs 1.1.0

A Rust wrapper library for Ansible command-line tools (Linux/Unix only)
Documentation
//! 异步 Ansible 命令执行
//!
//! 提供完全独立的异步 Ansible 命令执行功能,直接使用 tokio 进行异步操作,
//! 避免了包装同步代码可能带来的线程池阻塞问题。

use crate::{Ansible, Module, AnsibleError};
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 命令构建器和执行器
///
/// `AsyncAnsible` 提供与 [`Ansible`] 相同的功能,但所有执行操作都是异步的。
/// 这使得它可以在异步环境中使用,而不会阻塞事件循环。
///
/// # Examples
///
/// ## 基本异步使用
///
/// ```rust,no_run
/// use ansible::AsyncAnsible;
/// use ansible::Module;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let mut ansible = AsyncAnsible::default();
///     ansible
///         .set_system_envs()
///         .add_host("localhost")
///         .set_inventory("hosts.yml");
///
///     // 异步执行 ping
///     let result = ansible.ping().await?;
///     println!("Ping result: {}", result);
///
///     Ok(())
/// }
/// ```
///
/// ## 并发执行多个命令
///
/// ```rust,no_run
/// use ansible::AsyncAnsible;
/// use tokio::try_join;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let mut ansible1 = AsyncAnsible::default();
///     ansible1.add_host("host1");
///
///     let mut ansible2 = AsyncAnsible::default();
///     ansible2.add_host("host2");
///
///     // 并发执行多个命令
///     let (result1, result2) = try_join!(
///         ansible1.ping(),
///         ansible2.ping()
///     )?;
///
///     println!("Host1: {}", result1);
///     println!("Host2: {}", result2);
///
///     Ok(())
/// }
/// ```
#[derive(Debug, Clone)]
pub struct AsyncAnsible {
    command: String,
    cfg: CommandConfig,
    inventory: Option<String>,
    hosts: Vec<String>,
}

impl Default for AsyncAnsible {
    fn default() -> Self {
        Self {
            command: "ansible".to_string(),
            cfg: CommandConfig::default(),
            inventory: None,
            hosts: Vec::new(),
        }
    }
}

impl AsyncAnsible {
    /// 创建新的 AsyncAnsible 实例
    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 args<T, S>(&mut self, args: T) -> &mut Self
    where
        T: IntoIterator<Item = S>,
        S: AsRef<OsStr> + Display,
    {
        self.cfg.args(args);
        self
    }

    /// 添加目标主机
    pub fn add_host<S: AsRef<OsStr> + Display>(&mut self, host: S) -> &mut Self {
        self.hosts.push(host.to_string());
        self
    }

    /// 添加多个目标主机
    pub fn add_hosts<T, S>(&mut self, hosts: T) -> &mut Self
    where
        T: IntoIterator<Item = S>,
        S: AsRef<OsStr> + Display,
    {
        self.hosts.extend(hosts.into_iter().map(|h| h.to_string()));
        self
    }

    /// 清空所有主机
    pub fn clear_hosts(&mut self) -> &mut Self {
        self.hosts.clear();
        self
    }

    /// 设置清单文件
    pub fn set_inventory(&mut self, inventory: &str) -> &mut Self {
        self.inventory = Some(inventory.to_string());
        self
    }

    /// 设置 JSON 输出格式
    pub fn set_output_json(&mut self) -> &mut Self {
        self.cfg
            .add_env("ANSIBLE_STDOUT_CALLBACK", "json")
            .add_env("ANSIBLE_LOAD_CALLBACK_PLUGINS", "True");
        self
    }

    /// 异步执行 Ansible 模块
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use ansible::{AsyncAnsible, Module, PackageState};
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let mut ansible = AsyncAnsible::default();
    ///     ansible.add_host("localhost");
    ///
    ///     let module = Module::package("nginx", PackageState::Present);
    ///     let result = ansible.run(module).await?;
    ///     println!("Result: {}", result);
    ///
    ///     Ok(())
    /// }
    /// ```
    pub async fn run(&self, module: Module) -> AsyncResult<String> {
        if module == Module::None {
            return Err(AnsibleError::invalid_module("no module choice"));
        }

        let full_cmd = self.to_string();
        let cmd_vec: Vec<&str> = full_cmd.split_whitespace().collect();

        let mut cmd = Command::new(&self.command);
        cmd.envs(&self.cfg.envs);
        cmd.args(&cmd_vec.as_slice()[1..]);
        cmd.args(&self.cfg.args);
        cmd.args(module.to_args());

        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 command execution 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())
    }

    /// 构建完整的命令字符串(内部使用)
    fn to_string(&self) -> String {
        let mut parts = vec![self.command.clone()];

        if let Some(ref inventory) = self.inventory {
            parts.push("-i".to_string());
            parts.push(inventory.clone());
        }

        if !self.hosts.is_empty() {
            parts.push(self.hosts.join(","));
        }

        // 添加配置中的参数
        for arg in &self.cfg.args {
            parts.push(arg.to_string());
        }

        parts.join(" ")
    }

    /// 异步执行 shell 命令
    pub async fn shell(&self, command: impl Into<String>) -> AsyncResult<String> {
        self.run(Module::shell(command)).await
    }

    /// 异步执行命令
    pub async fn command(&self, command: impl Into<String>) -> AsyncResult<String> {
        self.run(Module::command(command)).await
    }

    /// 异步执行脚本
    pub async fn script(&self, script_path: impl Into<String>) -> AsyncResult<String> {
        self.run(Module::script(script_path)).await
    }

    /// 异步执行 ping 测试
    pub async fn ping(&self) -> AsyncResult<String> {
        self.run(Module::Ping).await
    }

    /// 异步收集系统信息
    pub async fn setup(&self) -> AsyncResult<String> {
        self.run(Module::Setup).await
    }
}

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

impl IntoAsync<AsyncAnsible> for Ansible {
    fn into_async(self) -> AsyncAnsible {
        AsyncAnsible {
            command: self.command,
            cfg: self.cfg,
            inventory: self.inventory,
            hosts: self.hosts,
        }
    }
}

impl FromAsync<AsyncAnsible> for Ansible {
    fn from_async(async_ansible: AsyncAnsible) -> Self {
        Ansible {
            command: async_ansible.command,
            cfg: async_ansible.cfg,
            inventory: async_ansible.inventory,
            hosts: async_ansible.hosts,
        }
    }
}

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