use crate::command_config::CommandConfig;
use crate::errors::{AnsibleError, Result};
use std::ffi::OsStr;
use std::fmt::{Display, Formatter};
use std::process;
#[derive(Debug, Clone, PartialEq)]
pub enum PackageState {
Present,
Absent,
Latest,
}
impl Display for PackageState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PackageState::Present => write!(f, "present"),
PackageState::Absent => write!(f, "absent"),
PackageState::Latest => write!(f, "latest"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ServiceState {
Started,
Stopped,
Restarted,
Reloaded,
}
impl Display for ServiceState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ServiceState::Started => write!(f, "started"),
ServiceState::Stopped => write!(f, "stopped"),
ServiceState::Restarted => write!(f, "restarted"),
ServiceState::Reloaded => write!(f, "reloaded"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FileState {
File,
Directory,
Link,
Absent,
Touch,
}
impl Display for FileState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FileState::File => write!(f, "file"),
FileState::Directory => write!(f, "directory"),
FileState::Link => write!(f, "link"),
FileState::Absent => write!(f, "absent"),
FileState::Touch => write!(f, "touch"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UserState {
Present,
Absent,
}
impl Display for UserState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UserState::Present => write!(f, "present"),
UserState::Absent => write!(f, "absent"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GroupState {
Present,
Absent,
}
impl Display for GroupState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
GroupState::Present => write!(f, "present"),
GroupState::Absent => write!(f, "absent"),
}
}
}
#[derive(Debug, Clone)]
pub struct Ansible {
pub(crate) command: String,
pub(crate) cfg: CommandConfig,
pub(crate) inventory: Option<String>,
pub(crate) hosts: Vec<String>,
}
impl Default for Ansible {
fn default() -> Self {
Self {
command: "ansible".into(),
cfg: CommandConfig::default(),
inventory: None,
hosts: Vec::new(),
}
}
}
impl Display for Ansible {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.command)?;
if let Some(ref inventory) = self.inventory {
if !inventory.is_empty() {
write!(f, " -i {}", inventory)?;
}
}
if !self.hosts.is_empty() {
write!(f, " {}", self.hosts.join(","))?;
}
if !self.cfg.args.is_empty() {
write!(f, " {}", self.cfg.args.join(" "))?;
}
Ok(())
}
}
impl Ansible {
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.args.push(arg.to_string());
self
}
pub fn args<T, S>(&mut self, args: T) -> &mut Self
where
T: IntoIterator<Item = S>,
S: AsRef<OsStr> + Display,
{
for arg in args {
self.arg(arg);
}
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, s: &str) -> &mut Self {
self.inventory = Some(s.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 fn run(&self, m: Module) -> Result<String> {
if m == 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 = process::Command::new(&self.command);
cmd.envs(&self.cfg.envs);
cmd.args(&cmd_vec.as_slice()[1..]);
cmd.args(&self.cfg.args);
cmd.args(m.to_args());
let output = cmd.output()?;
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())
}
pub fn shell(&self, command: impl Into<String>) -> Result<String> {
self.run(Module::shell(command))
}
pub fn command(&self, command: impl Into<String>) -> Result<String> {
self.run(Module::command(command))
}
pub fn script(&self, script_path: impl Into<String>) -> Result<String> {
self.run(Module::script(script_path))
}
pub fn ping(&self) -> Result<String> {
self.run(Module::Ping)
}
pub fn setup(&self) -> Result<String> {
self.run(Module::Setup)
}
pub fn copy(&self, src: impl Into<String>, dest: impl Into<String>) -> Result<String> {
self.run(Module::copy(src, dest))
}
pub fn package(&self, name: impl Into<String>, state: PackageState) -> Result<String> {
self.run(Module::package(name, state))
}
pub fn service(&self, name: impl Into<String>, state: ServiceState) -> Result<String> {
self.run(Module::service(name, state))
}
pub fn file(&self, path: impl Into<String>, state: FileState) -> Result<String> {
self.run(Module::file(path, state))
}
pub fn template(&self, src: impl Into<String>, dest: impl Into<String>) -> Result<String> {
self.run(Module::template(src, dest))
}
pub fn user(&self, name: impl Into<String>, state: UserState) -> Result<String> {
self.run(Module::user(name, state))
}
pub fn group(&self, name: impl Into<String>, state: GroupState) -> Result<String> {
self.run(Module::group(name, state))
}
pub fn raw(&self, command: impl Into<String>) -> Result<String> {
self.run(Module::raw(command))
}
pub fn fetch(&self, src: impl Into<String>, dest: impl Into<String>) -> Result<String> {
self.run(Module::fetch(src, dest))
}
pub fn synchronize(&self, src: impl Into<String>, dest: impl Into<String>) -> Result<String> {
self.run(Module::synchronize(src, dest))
}
pub fn git(&self, repo: impl Into<String>, dest: impl Into<String>) -> Result<String> {
self.run(Module::git(repo, dest))
}
pub fn cron(&self, name: impl Into<String>, job: impl Into<String>) -> Result<String> {
self.run(Module::cron(name, job))
}
pub fn mount(&self, path: impl Into<String>, src: impl Into<String>, fstype: impl Into<String>) -> Result<String> {
self.run(Module::mount(path, src, fstype))
}
pub fn systemd(&self, name: impl Into<String>, state: ServiceState) -> Result<String> {
self.run(Module::systemd(name, state))
}
}
#[derive(Debug, Default, PartialEq, Clone)]
pub enum Module {
#[default]
None,
Ping,
Setup,
Shell(String),
Command(String),
Script(String),
Copy { src: String, dest: String },
Package { name: String, state: PackageState },
Service { name: String, state: ServiceState },
File { path: String, state: FileState },
Template { src: String, dest: String },
User { name: String, state: UserState },
Group { name: String, state: GroupState },
Raw(String),
Fetch { src: String, dest: String },
Synchronize { src: String, dest: String },
Git { repo: String, dest: String },
Cron { name: String, job: String },
Mount { path: String, src: String, fstype: String },
Systemd { name: String, state: ServiceState },
Other { name: String, args: String },
}
impl Display for Module {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Module::None => write!(f, ""),
Module::Ping => write!(f, "-m ping"),
Module::Setup => write!(f, "-m setup"),
Module::Shell(s) => write!(f, "-m shell -a {}", s),
Module::Command(s) => write!(f, "-m command -a {}", s),
Module::Script(s) => write!(f, "-m script -a {}", s),
Module::Copy { src, dest } => write!(f, "-m copy -a 'src={} dest={}'", src, dest),
Module::Package { name, state } => write!(f, "-m package -a 'name={} state={}'", name, state),
Module::Service { name, state } => write!(f, "-m service -a 'name={} state={}'", name, state),
Module::File { path, state } => write!(f, "-m file -a 'path={} state={}'", path, state),
Module::Template { src, dest } => write!(f, "-m template -a 'src={} dest={}'", src, dest),
Module::User { name, state } => write!(f, "-m user -a 'name={} state={}'", name, state),
Module::Group { name, state } => write!(f, "-m group -a 'name={} state={}'", name, state),
Module::Raw(s) => write!(f, "-m raw -a {}", s),
Module::Fetch { src, dest } => write!(f, "-m fetch -a 'src={} dest={}'", src, dest),
Module::Synchronize { src, dest } => write!(f, "-m synchronize -a 'src={} dest={}'", src, dest),
Module::Git { repo, dest } => write!(f, "-m git -a 'repo={} dest={}'", repo, dest),
Module::Cron { name, job } => write!(f, "-m cron -a 'name={} job={}'", name, job),
Module::Mount { path, src, fstype } => write!(f, "-m mount -a 'path={} src={} fstype={}'", path, src, fstype),
Module::Systemd { name, state } => write!(f, "-m systemd -a 'name={} state={}'", name, state),
Module::Other { name, args } => write!(f, "-m {} -a {}", name, args),
}
}
}
impl Module {
pub fn to_args(&self) -> Vec<String> {
match self {
Module::None => vec![],
Module::Ping => vec!["-m".to_string(), "ping".to_string()],
Module::Setup => vec!["-m".to_string(), "setup".to_string()],
Module::Command(s) => vec!["-m".to_string(), "command".to_string(), "-a".to_string(), s.clone()],
Module::Shell(s) => vec!["-m".to_string(), "shell".to_string(), "-a".to_string(), s.clone()],
Module::Script(s) => vec!["-m".to_string(), "script".to_string(), "-a".to_string(), s.clone()],
Module::Copy { src, dest } => vec!["-m".to_string(), "copy".to_string(), "-a".to_string(), format!("src={} dest={}", src, dest)],
Module::Package { name, state } => vec!["-m".to_string(), "package".to_string(), "-a".to_string(), format!("name={} state={}", name, state)],
Module::Service { name, state } => vec!["-m".to_string(), "service".to_string(), "-a".to_string(), format!("name={} state={}", name, state)],
Module::File { path, state } => vec!["-m".to_string(), "file".to_string(), "-a".to_string(), format!("path={} state={}", path, state)],
Module::Template { src, dest } => vec!["-m".to_string(), "template".to_string(), "-a".to_string(), format!("src={} dest={}", src, dest)],
Module::User { name, state } => vec!["-m".to_string(), "user".to_string(), "-a".to_string(), format!("name={} state={}", name, state)],
Module::Group { name, state } => vec!["-m".to_string(), "group".to_string(), "-a".to_string(), format!("name={} state={}", name, state)],
Module::Raw(s) => vec!["-m".to_string(), "raw".to_string(), "-a".to_string(), s.clone()],
Module::Fetch { src, dest } => vec!["-m".to_string(), "fetch".to_string(), "-a".to_string(), format!("src={} dest={}", src, dest)],
Module::Synchronize { src, dest } => vec!["-m".to_string(), "synchronize".to_string(), "-a".to_string(), format!("src={} dest={}", src, dest)],
Module::Git { repo, dest } => vec!["-m".to_string(), "git".to_string(), "-a".to_string(), format!("repo={} dest={}", repo, dest)],
Module::Cron { name, job } => vec!["-m".to_string(), "cron".to_string(), "-a".to_string(), format!("name={} job={}", name, job)],
Module::Mount { path, src, fstype } => vec!["-m".to_string(), "mount".to_string(), "-a".to_string(), format!("path={} src={} fstype={}", path, src, fstype)],
Module::Systemd { name, state } => vec!["-m".to_string(), "systemd".to_string(), "-a".to_string(), format!("name={} state={}", name, state)],
Module::Other { name, args } => vec!["-m".to_string(), name.clone(), "-a".to_string(), args.clone()],
}
}
pub fn shell(command: impl Into<String>) -> Self {
Module::Shell(command.into())
}
pub fn command(command: impl Into<String>) -> Self {
Module::Command(command.into())
}
pub fn script(script_path: impl Into<String>) -> Self {
Module::Script(script_path.into())
}
pub fn copy(src: impl Into<String>, dest: impl Into<String>) -> Self {
Module::Copy {
src: src.into(),
dest: dest.into(),
}
}
pub fn package(name: impl Into<String>, state: PackageState) -> Self {
Module::Package {
name: name.into(),
state,
}
}
pub fn service(name: impl Into<String>, state: ServiceState) -> Self {
Module::Service {
name: name.into(),
state,
}
}
pub fn file(path: impl Into<String>, state: FileState) -> Self {
Module::File {
path: path.into(),
state,
}
}
pub fn template(src: impl Into<String>, dest: impl Into<String>) -> Self {
Module::Template {
src: src.into(),
dest: dest.into(),
}
}
pub fn user(name: impl Into<String>, state: UserState) -> Self {
Module::User {
name: name.into(),
state,
}
}
pub fn group(name: impl Into<String>, state: GroupState) -> Self {
Module::Group {
name: name.into(),
state,
}
}
pub fn raw(command: impl Into<String>) -> Self {
Module::Raw(command.into())
}
pub fn fetch(src: impl Into<String>, dest: impl Into<String>) -> Self {
Module::Fetch {
src: src.into(),
dest: dest.into(),
}
}
pub fn synchronize(src: impl Into<String>, dest: impl Into<String>) -> Self {
Module::Synchronize {
src: src.into(),
dest: dest.into(),
}
}
pub fn git(repo: impl Into<String>, dest: impl Into<String>) -> Self {
Module::Git {
repo: repo.into(),
dest: dest.into(),
}
}
pub fn cron(name: impl Into<String>, job: impl Into<String>) -> Self {
Module::Cron {
name: name.into(),
job: job.into(),
}
}
pub fn mount(path: impl Into<String>, src: impl Into<String>, fstype: impl Into<String>) -> Self {
Module::Mount {
path: path.into(),
src: src.into(),
fstype: fstype.into(),
}
}
pub fn systemd(name: impl Into<String>, state: ServiceState) -> Self {
Module::Systemd {
name: name.into(),
state,
}
}
pub fn other(name: impl Into<String>, args: impl Into<String>) -> Self {
Module::Other {
name: name.into(),
args: args.into(),
}
}
}