rustible/src/modules/package/mod.rs
2023-10-05 17:58:41 +02:00

213 lines
No EOL
5.6 KiB
Rust

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<PackageModule<Box<dyn PackageManager>>, PackageArgs, (), PackageError> for PackageArgs {
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn PackageManager>> {
let manager: Box<dyn PackageManager> = 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<D>(de: D) -> Result<Self, D::Error>
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: Manager,
args: PackageArgs,
}
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn PackageManager>> {
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<PackageModule<Box<dyn PackageManager>>, PackageArgs, (), PackageError> for PackageModule<NoManager> {
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn PackageManager>> {
let Self { args, .. } = self;
let manager: Box<dyn PackageManager> = match facts.os.family() {
OsFamily::Debian => Box::new(AptManager),
OsFamily::Archlinux => Box::new(PacmanManager),
};
PackageModule {
args,
manager,
}
}
}
// Stupid impl just for tests
impl ModuleSetup<PackageModule<Box<dyn PackageManager>>, PackageArgs, (), PackageError> for PackageModule<Box<dyn PackageManager>> {
fn with_facts(self, _facts: &Facts) -> PackageModule<Box<dyn PackageManager>> {
self
}
}
impl PackageModule<NoManager> {
pub fn new() -> PackageArgsBuilder<builder::NoPackage, builder::NoState, builder::NoManager> {
PackageArgsBuilder::new()
}
}
#[derive(Clone, Debug, Serialize)]
pub enum PackageError {
IoError(String),
CmdFail(CmdOutput),
}
impl From<std::io::Error> 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<bool, PackageError>;
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,
})
)
}
}