Add new Package examples to main

Print debug line with module arguments when run starts
Make apt package manager non-interactive
Move PackageList to dedicated module and make more impls
utils::Cmd now can take environment variables
This commit is contained in:
selfhoster selfhoster 2023-04-20 18:31:45 +02:00
parent f26f1c1bf2
commit 1245db813b
7 changed files with 122 additions and 154 deletions

View File

@ -44,12 +44,29 @@ fn main() -> Result<(), PlaybookError> {
.build(); .build();
playbook.run(cmd)?; playbook.run(cmd)?;
let pkg = Package::new()
.name("hello")
.state(PackageState::Present)
.build();
playbook.run(pkg)?;
let pkg = Package::new() let pkg = Package::new()
.name("hello") .name("hello")
.state(PackageState::Absent) .state(PackageState::Absent)
.build(); .build();
playbook.run(pkg)?; playbook.run(pkg)?;
let pkg = Package::new()
.name(vec!("hello", "sl"))
.state(PackageState::Present)
.build();
playbook.run(pkg)?;
let pkg = Package::new()
.name(&[ "hello", "sl" ])
.state(PackageState::Absent)
.build();
playbook.run(pkg)?;
playbook.print_json_pretty(); playbook.print_json_pretty();
Ok(()) Ok(())

View File

@ -79,6 +79,7 @@ impl PlaybookRun {
let module_name = cmd.module_name().to_string(); let module_name = cmd.module_name().to_string();
let args = cmd.serialize_args(); let args = cmd.serialize_args();
println!("Running module {} with args:{:#?}\n\n", &module_name, &args);
let res = cmd.run(); let res = cmd.run();

View File

@ -2,6 +2,10 @@ use crate::utils::cmd::Cmd;
use super::{PackageError, PackageList, SpecificPackageManager}; use super::{PackageError, PackageList, SpecificPackageManager};
pub fn apt() -> Cmd {
Cmd::new("apt").env("DEBIAN_FRONTEND", "noninteractive")
}
#[derive(Debug)] #[derive(Debug)]
pub struct DebianPackageManager; pub struct DebianPackageManager;
@ -11,7 +15,7 @@ impl SpecificPackageManager for DebianPackageManager {
} }
fn update(&self) -> Result<(), PackageError> { fn update(&self) -> Result<(), PackageError> {
let res = Cmd::new("apt").arg("update").run()?; let res = apt().arg("update").run()?;
if ! res.success { if ! res.success {
return Err(PackageError::CmdFail(res)); return Err(PackageError::CmdFail(res));
@ -20,7 +24,7 @@ impl SpecificPackageManager for DebianPackageManager {
} }
fn install(&self, list: PackageList) -> Result<(), PackageError> { fn install(&self, list: PackageList) -> Result<(), PackageError> {
let res = Cmd::new("apt").arg("install").args(list.list()).run()?; let res = apt().arg("install").args(list.list()).run()?;
if ! res.success { if ! res.success {
return Err(PackageError::CmdFail(res)); return Err(PackageError::CmdFail(res));
@ -29,7 +33,7 @@ impl SpecificPackageManager for DebianPackageManager {
} }
fn remove(&self, list: PackageList) -> Result<(), PackageError> { fn remove(&self, list: PackageList) -> Result<(), PackageError> {
let res = Cmd::new("apt").arg("remove").args(list.list()).run()?; let res = apt().arg("remove").args(list.list()).run()?;
if ! res.success { if ! res.success {
return Err(PackageError::CmdFail(res)); return Err(PackageError::CmdFail(res));

View File

@ -1,110 +1,80 @@
use serde::Serialize; use serde::Serialize;
use crate::facts::{Facts, os::OsFamily};
use crate::utils::cmd::CmdOutput;
use std::boxed::Box;
pub mod pacman;
use pacman::ArchlinuxPackageManager;
pub mod apt;
use apt::DebianPackageManager;
pub mod builder;
use builder::{NoPackage, PackageArgsBuilder};
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct PackageArgs { pub struct PackageList {
name: PackageList, pub list: Vec<String>,
state: PackageState,
} }
#[derive(Clone, Debug, Serialize)] impl PackageList {
pub enum PackageState { pub fn list(&self) -> & [ String ] {
Present, &self.list
Absent,
Latest,
} }
#[derive(Debug)] pub fn add<T: IntoPackageList>(self, add: T) -> PackageList {
pub struct PackageModule<Manager> { let Self { mut list } = self;
manager: Manager,
args: PackageArgs,
}
impl<Manager> Module<PackageArgs, (), PackageError> for PackageModule<Manager> { let extend_with = add.into_package_list();
fn clone_args(&self) -> PackageArgs { list.extend(extend_with.list);
self.args.clone()
}
fn module_name() -> &'static str { PackageList {
"package" list
}
}
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
fn run(&self) -> Result<(), PackageError> {
Ok(())
}
}
impl Module<PackageArgs, (), PackageError> for PackageModule<NoManager> {
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn SpecificPackageManager>> {
let Self { args, .. } = self;
let manager = match facts.os.family() {
OsFamily::Debian => Box::new(DebianPackageManager),
OsFamily::Archlinux => Box::new(ArchlinuxPackageManager),
};
PackageModule {
args,
manager,
} }
} }
} }
/// Turn a stringy value or list of values into an actual package list.
impl PackageModule { /// Only implemented for &str and &str/String slices because apparently
pub fn new() -> PackageArgsBuilder { /// there may be a future implementation that turns a slice of stringy values
PackageArgsBuilder::new() /// into a stringy value, resulting in conflicting implementations.
/// https://stackoverflow.com/questions/63136970/how-do-i-work-around-the-upstream-crates-may-add-a-new-impl-of-trait-error
pub trait IntoPackageList {
fn into_package_list(self) -> PackageList;
} }
pub fn from_args_with_facts(args: PackageArgs, facts: &Facts) -> PackageModule { impl IntoPackageList for &str {
let manager = match facts.os.family() { fn into_package_list(self) -> PackageList {
OsFamily::Debian => PackageModule(Box::new(DebianPackageManager)), PackageList {
OsFamily::Archlinux => PackageModule(Box::new(ArchlinuxPackageManager)), list: vec!(self.to_string()),
};
PackageModule {
manager,
args,
}
}
pub fn from_args_with(args: PackageArgs, manager: SpecificPackageManager) -> PackageManager {
PackageModule {
manager: Box::new(manager),
args,
} }
} }
} }
#[derive(Clone, Debug, Serialize)] impl IntoPackageList for String {
pub enum PackageError { fn into_package_list(self) -> PackageList {
IoError(String), PackageList {
CmdFail(CmdOutput), list: vec!(self),
} }
impl From<std::io::Error> for PackageError {
fn from(e: std::io::Error) -> PackageError {
PackageError::IoError(e.to_string())
} }
} }
pub trait SpecificPackageManager: std::fmt::Debug { impl<const C: usize> IntoPackageList for & [ &str; C ] {
fn name(&self) -> &'static str; fn into_package_list(self) -> PackageList {
fn update(&self) -> Result<(), PackageError>; PackageList {
fn install(&self, list: PackageList) -> Result<(), PackageError>; list: self.iter().map(|x| x.to_string()).collect(),
fn remove(&self, list: PackageList) -> Result<(), PackageError>; }
}
}
impl<const C: usize> IntoPackageList for & [ String; C ] {
fn into_package_list(self) -> PackageList {
PackageList {
list: self.to_vec(),
}
}
}
impl IntoPackageList for Vec<&str> {
fn into_package_list(self) -> PackageList {
PackageList {
list: self.iter().map(|x| x.to_string()).collect(),
}
}
}
impl IntoPackageList for Vec<String> {
fn into_package_list(self) -> PackageList {
PackageList {
list: self,
}
}
} }

View File

@ -6,6 +6,9 @@ use crate::utils::cmd::CmdOutput;
use std::boxed::Box; use std::boxed::Box;
pub mod list;
pub use list::{PackageList, IntoPackageList};
pub mod pacman; pub mod pacman;
use pacman::ArchlinuxPackageManager; use pacman::ArchlinuxPackageManager;
pub mod apt; pub mod apt;
@ -100,58 +103,3 @@ pub trait SpecificPackageManager: std::fmt::Debug {
fn install(&self, list: PackageList) -> Result<(), PackageError>; fn install(&self, list: PackageList) -> Result<(), PackageError>;
fn remove(&self, list: PackageList) -> Result<(), PackageError>; fn remove(&self, list: PackageList) -> Result<(), PackageError>;
} }
#[derive(Clone, Debug, Serialize)]
pub struct PackageList {
pub list: Vec<String>,
}
impl PackageList {
pub fn list(&self) -> & [ String ] {
&self.list
}
pub fn add<T: IntoPackageList>(self, add: T) -> PackageList {
let Self { mut list } = self;
let extend_with = add.into_package_list();
list.extend(extend_with.list);
PackageList {
list
}
}
}
/// Turn a stringy value or list of values into an actual package list.
/// Only implemented for &str and &str/String slices because apparently
/// there may be a future implementation that turns a slice of stringy values
/// into a stringy value, resulting in conflicting implementations.
/// https://stackoverflow.com/questions/63136970/how-do-i-work-around-the-upstream-crates-may-add-a-new-impl-of-trait-error
pub trait IntoPackageList {
fn into_package_list(self) -> PackageList;
}
impl IntoPackageList for &str {
fn into_package_list(self) -> PackageList {
PackageList {
list: vec!(self.to_string()),
}
}
}
impl IntoPackageList for & [ &str ] {
fn into_package_list(self) -> PackageList {
PackageList {
list: self.iter().map(|x| x.to_string()).collect(),
}
}
}
impl IntoPackageList for & [ String ] {
fn into_package_list(self) -> PackageList {
PackageList {
list: self.to_vec(),
}
}
}

View File

@ -30,7 +30,7 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
Ok(()) Ok(())
} }
fn remove(&self, _list: PackageList) -> Result<(), PackageError> { fn remove(&self, list: PackageList) -> Result<(), PackageError> {
let res = Cmd::new("pacman").arg("-R").args(list.list()).run()?; let res = Cmd::new("pacman").arg("-R").args(list.list()).run()?;
if ! res.success { if ! res.success {

View File

@ -1,5 +1,6 @@
use duct::cmd as duct_cmd; use duct::cmd as duct_cmd;
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -8,6 +9,7 @@ pub struct Cmd {
args: Vec<String>, args: Vec<String>,
stdin: Option<String>, stdin: Option<String>,
chdir: Option<PathBuf>, chdir: Option<PathBuf>,
env: HashMap<String, String>,
} }
impl Cmd { impl Cmd {
@ -17,11 +19,12 @@ impl Cmd {
args: Vec::new(), args: Vec::new(),
stdin: None, stdin: None,
chdir: None, chdir: None,
env: HashMap::new(),
} }
} }
pub fn arg<T: AsRef<str>>(self, arg: T) -> Cmd { pub fn arg<T: AsRef<str>>(self, arg: T) -> Cmd {
let Self { program, mut args, stdin, chdir, .. } = self; let Self { program, mut args, stdin, chdir, env, .. } = self;
args.push(arg.as_ref().to_string()); args.push(arg.as_ref().to_string());
@ -30,12 +33,13 @@ impl Cmd {
args, args,
stdin, stdin,
chdir, chdir,
env,
} }
} }
pub fn args<T: AsRef<str>>(self, new_args: & [ T ]) -> Cmd { pub fn args<T: AsRef<str>>(self, new_args: & [ T ]) -> Cmd {
let Self { program, mut args, stdin, chdir, .. } = self; let Self { program, mut args, stdin, chdir, env, .. } = self;
for arg in new_args { for arg in new_args {
args.push(arg.as_ref().to_string()); args.push(arg.as_ref().to_string());
@ -46,10 +50,12 @@ impl Cmd {
args, args,
stdin, stdin,
chdir, chdir,
} } env,
}
}
pub fn stdin<T: AsRef<str>>(self, input: T) -> Cmd { pub fn stdin<T: AsRef<str>>(self, input: T) -> Cmd {
let Self { program, args, mut stdin, chdir, .. } = self; let Self { program, args, mut stdin, chdir, env, .. } = self;
stdin = if let Some(mut s) = stdin { stdin = if let Some(mut s) = stdin {
s.push_str(input.as_ref()); s.push_str(input.as_ref());
@ -63,25 +69,47 @@ impl Cmd {
args, args,
stdin, stdin,
chdir, chdir,
env,
} }
} }
pub fn chdir<T: AsRef<str>>(self, dir: T) -> Cmd { pub fn chdir<T: AsRef<str>>(self, dir: T) -> Cmd {
let Self { program, args, stdin, .. } = self; let Self { program, args, stdin, env, .. } = self;
Cmd { Cmd {
program, program,
args, args,
stdin, stdin,
chdir: Some(PathBuf::from(dir.as_ref())), chdir: Some(PathBuf::from(dir.as_ref())),
env,
}
}
pub fn env<K: AsRef<str>, V: AsRef<str>>(self, key: K, value: V) -> Cmd {
let Self { program, args, stdin, chdir, mut env, .. } = self;
env.insert(key.as_ref().to_string(), value.as_ref().to_string());
Cmd {
program,
args,
stdin,
chdir,
env,
} }
} }
pub fn run(self) -> Result<CmdOutput, std::io::Error> { pub fn run(self) -> Result<CmdOutput, std::io::Error> {
let Self { program, args, stdin, chdir, .. } = self; let Self { program, args, stdin, chdir, env, .. } = self;
let mut cmd = duct_cmd(&program, &args); let mut cmd = duct_cmd(&program, &args);
if ! env.is_empty() {
for (key, value) in env {
cmd = cmd.env(key, value);
}
}
if let Some(stdin) = &stdin { if let Some(stdin) = &stdin {
cmd = cmd.stdin_bytes(stdin.as_bytes()); cmd = cmd.stdin_bytes(stdin.as_bytes());
} }