use serde::{Serialize, Deserialize, Deserializer}; use crate::{Module, ModuleSetup}; use crate::facts::{Facts, os::OsFamily}; use crate::utils::cmd::CmdOutput; use std::boxed::Box; pub mod list; pub use list::{PackageList, IntoPackageList}; pub mod pacman; use pacman::PacmanManager; pub mod apt; use apt::AptManager; pub mod builder; pub use builder::{PackageArgsBuilder, NoManager}; pub type PackageStatus = (); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PackageArgs { name: PackageList, state: PackageState, } impl ModuleSetup>, PackageArgs, (), PackageError> for PackageArgs { fn with_facts(self, facts: &Facts) -> PackageModule> { let manager: Box = match facts.os.family() { OsFamily::Debian => Box::new(AptManager), OsFamily::Archlinux => Box::new(PacmanManager), }; PackageModule { args: self, manager, } } } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub enum PackageState { Present, Absent, Latest, } impl<'de> Deserialize<'de> for PackageState { fn deserialize(de: D) -> Result where D: Deserializer<'de>, { let variant = String::deserialize(de)?; Ok(match variant.as_str() { "present" => PackageState::Present, "absent" => PackageState::Absent, "latest" => PackageState::Latest, _other => unimplemented!(), }) } } #[derive(Debug, PartialEq, Eq)] pub struct PackageModule { manager: Manager, args: PackageArgs, } impl Module for PackageModule> { fn serialize_args(&self) -> serde_json::Value { serde_json::to_value(&self.args).unwrap() } fn module_name(&self) -> &'static str { "package" } fn run(self) -> Result<(), PackageError> { let packages = self.args.name; match &self.args.state { PackageState::Present => { self.manager.install(packages) }, PackageState::Absent => { self.manager.remove(packages) }, PackageState::Latest => { unimplemented!() } } } } impl ModuleSetup>, PackageArgs, (), PackageError> for PackageModule { fn with_facts(self, facts: &Facts) -> PackageModule> { let Self { args, .. } = self; let manager: Box = match facts.os.family() { OsFamily::Debian => Box::new(AptManager), OsFamily::Archlinux => Box::new(PacmanManager), }; PackageModule { args, manager, } } } // Stupid impl just for tests impl ModuleSetup>, PackageArgs, (), PackageError> for PackageModule> { fn with_facts(self, _facts: &Facts) -> PackageModule> { self } } impl PackageModule { pub fn new() -> PackageArgsBuilder { PackageArgsBuilder::new() } } #[derive(Clone, Debug, Serialize)] pub enum PackageError { IoError(String), CmdFail(CmdOutput), } impl From for PackageError { fn from(e: std::io::Error) -> PackageError { PackageError::IoError(e.to_string()) } } pub trait PackageManager: std::fmt::Debug { fn name(&self) -> &'static str; fn update(&self) -> Result<(), PackageError>; fn is_installed(&self, pkg: &str) -> Result; fn install(&self, list: PackageList) -> Result<(), PackageError>; fn remove(&self, list: PackageList) -> Result<(), PackageError>; } #[cfg(test)] mod tests { use super::*; #[test] fn ensure_present() { let pkg = PackageModule::new() .name("hello") .state(PackageState::Present) .with_apt() .build(); assert_eq!( pkg.serialize_args(), serde_json::json!({ "name": vec!("hello"), "state": PackageState::Present, }) ); } #[test] fn ensure_present_multi() { let pkg = PackageModule::new() .name(&[ "hello", "sl" ]) .state(PackageState::Present) .with_apt() .build(); assert_eq!( pkg.serialize_args(), serde_json::json!({ "name": vec!("hello", "sl"), "state": PackageState::Present, }) ) } #[test] fn ensure_absent() { let pkg = PackageModule::new() .name("hello") .state(PackageState::Absent) .with_apt() .build(); assert_eq!( pkg.serialize_args(), serde_json::json!({ "name": vec!("hello"), "state": PackageState::Absent, }) ) } #[test] fn ensure_absent_multi() { let pkg = PackageModule::new() .name(&[ "hello", "sl" ]) .state(PackageState::Absent) .with_apt() .build(); assert_eq!( pkg.serialize_args(), serde_json::json!({ "name": vec!("hello", "sl"), "state": PackageState::Absent, }) ) } }