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;
#[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 {
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
}
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
}
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(" ")
}
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
}
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())
}
}