ansible-rs 1.1.0

A Rust wrapper library for Ansible command-line tools (Linux/Unix only)
Documentation
//! 异步 Ansible Vault 操作
//!
//! 提供完全独立的异步 Ansible Vault 功能,支持文件和字符串的加密解密操作。

use crate::{AnsibleVault, 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 Vault 加密解密工具
///
/// `AsyncAnsibleVault` 提供与 [`AnsibleVault`] 相同的功能,但所有操作都是异步的。
///
/// # Examples
///
/// ## 基本异步 Vault 操作
///
/// ```rust,no_run
/// use ansible::AsyncAnsibleVault;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let mut vault = AsyncAnsibleVault::new();
///     vault.set_vault_password_file("vault_pass.txt");
///
///     // 异步加密文件
///     vault.encrypt("secrets.yml").await?;
///
///     // 异步解密文件
///     vault.decrypt("secrets.yml").await?;
///
///     Ok(())
/// }
/// ```
///
/// ## 并发 Vault 操作
///
/// ```rust,no_run
/// use ansible::AsyncAnsibleVault;
/// use tokio::try_join;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let mut vault1 = AsyncAnsibleVault::new();
///     vault1.set_vault_password_file("vault_pass.txt");
///
///     let mut vault2 = AsyncAnsibleVault::new();
///     vault2.set_vault_password_file("vault_pass.txt");
///
///     // 并发加密多个文件
///     let (result1, result2) = try_join!(
///         vault1.encrypt("file1.yml"),
///         vault2.encrypt("file2.yml")
///     )?;
///
///     println!("Encrypted files successfully");
///     Ok(())
/// }
/// ```
#[derive(Debug, Clone)]
pub struct AsyncAnsibleVault {
    command: String,
    cfg: CommandConfig,
    vault_id: Option<String>,
    vault_password_file: Option<String>,
}

impl Default for AsyncAnsibleVault {
    fn default() -> Self {
        Self {
            command: "ansible-vault".to_string(),
            cfg: CommandConfig::default(),
            vault_id: None,
            vault_password_file: None,
        }
    }
}

impl AsyncAnsibleVault {
    /// 创建新的 AsyncAnsibleVault 实例
    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
    }

    /// 设置 Vault ID
    pub fn set_vault_id(&mut self, vault_id: impl Into<String>) -> &mut Self {
        self.vault_id = Some(vault_id.into());
        self
    }

    /// 设置 Vault 密码文件
    pub fn set_vault_password_file(&mut self, file_path: impl Into<String>) -> &mut Self {
        self.vault_password_file = Some(file_path.into());
        self
    }

    /// 设置新的 Vault 密码文件(用于重新加密)
    pub fn set_new_vault_password_file(&mut self, file_path: impl Into<String>) -> &mut Self {
        self.arg("--new-vault-password-file").arg(file_path.into());
        self
    }

    /// 异步加密文件
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use ansible::AsyncAnsibleVault;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let mut vault = AsyncAnsibleVault::new();
    ///     vault.set_vault_password_file("vault_pass.txt");
    ///
    ///     vault.encrypt("secrets.yml").await?;
    ///     println!("File encrypted successfully");
    ///
    ///     Ok(())
    /// }
    /// ```
    pub async fn encrypt(&self, file_path: impl AsRef<str>) -> AsyncResult<String> {
        self.execute_vault_command(&["encrypt", file_path.as_ref()]).await
    }

    /// 异步解密文件
    pub async fn decrypt(&self, file_path: impl AsRef<str>) -> AsyncResult<String> {
        self.execute_vault_command(&["decrypt", file_path.as_ref()]).await
    }

    /// 异步查看加密文件内容
    pub async fn view(&self, file_path: impl AsRef<str>) -> AsyncResult<String> {
        self.execute_vault_command(&["view", file_path.as_ref()]).await
    }

    /// 异步编辑加密文件
    pub async fn edit(&self, file_path: impl AsRef<str>) -> AsyncResult<String> {
        self.execute_vault_command(&["edit", file_path.as_ref()]).await
    }

    /// 异步重新加密文件
    pub async fn rekey(&self, file_path: impl AsRef<str>) -> AsyncResult<String> {
        self.execute_vault_command(&["rekey", file_path.as_ref()]).await
    }

    /// 异步加密字符串
    pub async fn encrypt_string(&self, string_to_encrypt: impl AsRef<str>) -> AsyncResult<String> {
        let mut cmd = Command::new(&self.command);
        cmd.envs(&self.cfg.envs);
        cmd.args(&self.cfg.args);
        cmd.arg("encrypt_string");
        
        if let Some(ref vault_id) = self.vault_id {
            cmd.arg("--vault-id").arg(vault_id);
        }
        
        if let Some(ref password_file) = self.vault_password_file {
            cmd.arg("--vault-password-file").arg(password_file);
        }
        
        cmd.arg(string_to_encrypt.as_ref());

        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 vault encrypt_string failed",
                output.status.code(),
                Some(stdout),
                Some(stderr),
            ));
        }

        let result = String::from_utf8_lossy(&output.stdout);
        Ok(result.to_string())
    }

    /// 执行 Vault 命令的内部方法
    async fn execute_vault_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 vault_id) = self.vault_id {
            cmd.arg("--vault-id").arg(vault_id);
        }
        
        if let Some(ref password_file) = self.vault_password_file {
            cmd.arg("--vault-password-file").arg(password_file);
        }

        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 vault 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 AsyncAnsibleVault {
    async fn execute_async(&self) -> AsyncResult<String> {
        // 默认执行 version 命令
        self.execute_vault_command(&["--version"]).await
    }
}

impl IntoAsync<AsyncAnsibleVault> for AnsibleVault {
    fn into_async(self) -> AsyncAnsibleVault {
        AsyncAnsibleVault {
            command: self.command,
            cfg: self.cfg,
            vault_id: self.vault_id,
            vault_password_file: self.vault_password_file,
        }
    }
}

impl FromAsync<AsyncAnsibleVault> for AnsibleVault {
    fn from_async(async_vault: AsyncAnsibleVault) -> Self {
        AnsibleVault {
            command: async_vault.command,
            cfg: async_vault.cfg,
            vault_id: async_vault.vault_id,
            vault_password_file: async_vault.vault_password_file,
        }
    }
}

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