Compare commits
5 Commits
0f85701cc4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 89ef66a694 | |||
| 7d9e3d63b7 | |||
| d2ef322761 | |||
| 1245db813b | |||
| f26f1c1bf2 |
Generated
+142
@@ -11,6 +11,73 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.0"
|
||||
@@ -39,6 +106,12 @@ dependencies = [
|
||||
"shared_child",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.25"
|
||||
@@ -65,6 +138,15 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.5"
|
||||
@@ -93,6 +175,25 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
@@ -127,6 +228,28 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
@@ -144,12 +267,25 @@ version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitflags",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustible"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"duct",
|
||||
"rayon",
|
||||
"regex",
|
||||
"ron",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snafu",
|
||||
@@ -162,6 +298,12 @@ version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
|
||||
@@ -15,6 +15,8 @@ typetag = "0.2"
|
||||
# Error management
|
||||
#thiserror = "1"
|
||||
snafu = "0.7"
|
||||
ron = "0.8"
|
||||
rayon = "1.7"
|
||||
|
||||
[lib]
|
||||
name = "rustible"
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
WIP ansible-like system for sysadmin in rust
|
||||
|
||||
TODO: finish test.sh for testing things
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
use rustible::Facts;
|
||||
|
||||
fn main() {
|
||||
let facts = Facts::new();
|
||||
println!("rustible running system: {}", facts.os.family().as_str());
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use rustible::modules::{
|
||||
PlaybookRun,
|
||||
package::{PackageModule as Package, PackageState, PackageError},
|
||||
command::{CommandModule as Command, CommandError},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
pub enum PlaybookError {
|
||||
Command(CommandError),
|
||||
Package(PackageError),
|
||||
}
|
||||
|
||||
impl From<CommandError> for PlaybookError {
|
||||
fn from(e: CommandError) -> PlaybookError {
|
||||
PlaybookError::Command(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PackageError> for PlaybookError {
|
||||
fn from(e: PackageError) -> PlaybookError {
|
||||
PlaybookError::Package(e)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), PlaybookError> {
|
||||
let mut playbook = PlaybookRun::new();
|
||||
|
||||
let pkg = Package::new()
|
||||
.name("hello")
|
||||
.state(PackageState::Present)
|
||||
.build();
|
||||
playbook.run(pkg)?;
|
||||
|
||||
let cmd = Command::new()
|
||||
.program("hello")
|
||||
.arg("-g")
|
||||
.arg("\"Welcome to rustible!\"")
|
||||
.build();
|
||||
|
||||
let res = playbook.run(cmd)?;
|
||||
println!("STDOUT:\n{}", res.stdout().unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+69
-20
@@ -1,28 +1,77 @@
|
||||
use ron::value::Value;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use rustible::Facts;
|
||||
use rustible::modules::{PlaybookRun, Module, Command};
|
||||
use rustible::modules::{
|
||||
Module, PlaybookRun, ModuleSetup,
|
||||
package::{PackageModule as Package, PackageState, PackageError, PackageArgs},
|
||||
command::{CommandModule as Command, CommandError, CommandArgs},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), rustible::modules::command::CommandError> {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DeclarativePlaybook {
|
||||
modules: Vec<DeclarativeModule>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DeclarativeModule {
|
||||
name: String,
|
||||
module: String,
|
||||
args: Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
pub enum PlaybookError {
|
||||
Command(CommandError),
|
||||
Package(PackageError),
|
||||
}
|
||||
|
||||
impl From<CommandError> for PlaybookError {
|
||||
fn from(e: CommandError) -> PlaybookError {
|
||||
PlaybookError::Command(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PackageError> for PlaybookError {
|
||||
fn from(e: PackageError) -> PlaybookError {
|
||||
PlaybookError::Package(e)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), PlaybookError> {
|
||||
let facts = Facts::new();
|
||||
println!("Hello, world! Running system {}", facts.os.family().as_str());
|
||||
|
||||
|
||||
let mut playbook = PlaybookRun::new();
|
||||
|
||||
let cmd = Command::new().program("echo").args(&vec!("lol".to_string())).build();
|
||||
let res = cmd.run()?;
|
||||
println!("{}", res.success());
|
||||
|
||||
let cmd = Command::new().program("echo").args(&vec!("lol".to_string())).build();
|
||||
playbook.run(cmd)?;
|
||||
|
||||
let cmd = Command::new()
|
||||
.creates("/tmp/lol")
|
||||
.program("touch")
|
||||
.arg("/tmp/lol")
|
||||
.build();
|
||||
playbook.run(cmd)?;
|
||||
//let res = cmd.run
|
||||
let mut cli_args = args();
|
||||
let test_playbook = cli_args.nth(1).expect("First argument should be playbook.ron");
|
||||
let test_shell = cli_args.nth(0).expect("Second argument should be test.sh");
|
||||
|
||||
playbook.print_json_pretty();
|
||||
let test_playbook_content = std::fs::read_to_string(&test_playbook).unwrap();
|
||||
|
||||
let playbook_ron: DeclarativePlaybook = ron::from_str(&test_playbook_content).unwrap();
|
||||
|
||||
for task in playbook_ron.modules {
|
||||
println!("{}", task.name);
|
||||
match task.module.as_str() {
|
||||
"command" => {
|
||||
let args: CommandArgs = task.args.into_rust().unwrap();
|
||||
let module = args.with_facts(&facts);
|
||||
playbook.run(module)?;
|
||||
//println!("{:?}", module);
|
||||
}, "package" => {
|
||||
let args: PackageArgs = task.args.into_rust().unwrap();
|
||||
let module = args.with_facts(&facts);
|
||||
playbook.run(module)?;
|
||||
//println!("{:?}", module);
|
||||
}, _ => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ use crate::utils::cmd::Cmd;
|
||||
pub struct NoCmd;
|
||||
|
||||
pub struct NoCondition;
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CreatesCondition(pub PathBuf);
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RemovesCondition(pub PathBuf);
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct BothConditions(pub CreatesCondition, pub RemovesCondition);
|
||||
|
||||
/// Build a [`CommandModule`].
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::builder::{NoCondition, CreatesCondition, RemovesCondition, BothConditions};
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum SomeCondition {
|
||||
#[serde(rename="creates")]
|
||||
Creates(CreatesCondition),
|
||||
|
||||
@@ -12,13 +12,24 @@ use crate::{Module, ModuleSetup, Facts};
|
||||
|
||||
/// A copy of the argument passed to the [`CommandBuilder`],
|
||||
/// as returned serialized in the task run output
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CommandArgs {
|
||||
#[serde(flatten)]
|
||||
pub args: Cmd,
|
||||
pub condition: Option<SomeCondition>,
|
||||
}
|
||||
|
||||
impl ModuleSetup<CommandModule, CommandArgs, CommandStatus, CommandError> for CommandArgs {
|
||||
fn with_facts(self, _facts: &Facts) -> CommandModule {
|
||||
CommandModule {
|
||||
cmd: self.args,
|
||||
condition: self.condition,
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandModule {
|
||||
cmd: Cmd,
|
||||
condition: Option<SomeCondition>,
|
||||
@@ -92,6 +103,20 @@ impl CommandStatus {
|
||||
Self::Skipped(_c) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stdout(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::Done(ret) => Some(ret.stdout.to_string()),
|
||||
Self::Skipped(_c) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stderr(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::Done(ret) => Some(ret.stdout.to_string()),
|
||||
Self::Skipped(_c) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return of a Command that was effectively run
|
||||
|
||||
+2
-1
@@ -79,6 +79,7 @@ impl PlaybookRun {
|
||||
|
||||
let module_name = cmd.module_name().to_string();
|
||||
let args = cmd.serialize_args();
|
||||
println!("Running module {} with args:{:#?}\n\n", &module_name, &args);
|
||||
|
||||
let res = cmd.run();
|
||||
|
||||
@@ -129,4 +130,4 @@ pub trait Module<A: Serialize, S: Serialize, E: Serialize> {
|
||||
|
||||
/// Declare module types
|
||||
pub use command::CommandModule as Command;
|
||||
|
||||
pub use package::{PackageModule as Package, PackageState};
|
||||
|
||||
+174
-8
@@ -1,17 +1,62 @@
|
||||
use crate::utils::cmd::Cmd;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use super::{PackageError, PackageList, SpecificPackageManager};
|
||||
use crate::utils::cmd::{Cmd, CmdOutput};
|
||||
|
||||
use super::{PackageError, PackageList, PackageManager, IntoPackageList};
|
||||
|
||||
/// This structure is mostly used as an indirection for testing purposes.
|
||||
/// But you can use it to build your own apt commands.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct AptCmd(pub Cmd);
|
||||
|
||||
impl AptCmd {
|
||||
pub fn new() -> AptCmd {
|
||||
AptCmd(
|
||||
Cmd::new("apt").env("DEBIAN_FRONTEND", "noninteractive")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update() -> AptCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("update");
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn install(list: PackageList) -> AptCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("install").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn remove(list: PackageList) -> AptCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("remove").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<CmdOutput, std::io::Error> {
|
||||
self.0.run()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebianPackageManager;
|
||||
pub struct AptManager;
|
||||
|
||||
impl SpecificPackageManager for DebianPackageManager {
|
||||
impl AptManager {
|
||||
fn is_installed_list(&self, list: &PackageList) -> Result<bool, PackageError> {
|
||||
let res = Cmd::new("dpkg").arg("-s").args(list.list()).run()?;
|
||||
|
||||
Ok(res.success)
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageManager for AptManager {
|
||||
fn name(&self) -> &'static str {
|
||||
"apt"
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("apt").arg("update").run()?;
|
||||
let res = AptCmd::update().run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
@@ -19,8 +64,27 @@ impl SpecificPackageManager for DebianPackageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_installed(&self, pkg: &str) -> Result<bool, PackageError> {
|
||||
let res = Cmd::new("dpkg").arg("-s").arg(pkg).run()?;
|
||||
|
||||
Ok(res.success)
|
||||
}
|
||||
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("apt").arg("install").args(list.list()).run()?;
|
||||
// Filter out already installed packages
|
||||
// let list: Vec<String> = list.list().par_iter().filter_map(|pkg| {
|
||||
// if ! self.is_installed(&pkg).unwrap() {
|
||||
// Some(pkg.to_string())
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }).collect();
|
||||
if self.is_installed_list(&list)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
//let res = AptCmd::install(list.into_package_list()).run()?;
|
||||
let res = AptCmd::install(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
@@ -28,7 +92,109 @@ impl SpecificPackageManager for DebianPackageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, _list: PackageList) -> Result<(), PackageError> {
|
||||
todo!()
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = AptCmd::remove(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::IntoPackageList;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
pub fn update_cmd() {
|
||||
let cmd = AptCmd::update();
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("update".to_string()),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd() {
|
||||
let cmd = AptCmd::install(vec!("sl").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("install", "sl").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd_multi() {
|
||||
let cmd = AptCmd::install(vec!("sl", "hello").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("install", "sl", "hello").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd() {
|
||||
let cmd = AptCmd::remove(vec!("sl").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("remove", "sl").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd_multi() {
|
||||
let cmd = AptCmd::remove(vec!("sl", "hello").into_package_list());
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "apt".to_string(),
|
||||
args: vec!("remove", "sl", "hello").iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{PackageArgs, IntoPackageList, PackageList, PackageState, PackageModule, PackageStatus, PackageError, SpecificPackageManager};
|
||||
use super::apt::DebianPackageManager;
|
||||
use super::pacman::ArchlinuxPackageManager;
|
||||
use super::{PackageArgs, IntoPackageList, PackageList, PackageState, PackageModule, PackageStatus, PackageError, PackageManager};
|
||||
use super::{apt::AptManager, pacman::PacmanManager};
|
||||
use std::boxed::Box;
|
||||
|
||||
use crate::Module;
|
||||
@@ -50,7 +49,7 @@ impl<Packages, Manager> PackageArgsBuilder<Packages, NoState, Manager> {
|
||||
}
|
||||
|
||||
impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
||||
pub fn with(self, manager: impl SpecificPackageManager + 'static) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
pub fn with(self, manager: impl PackageManager + 'static) -> PackageArgsBuilder<Packages, State, Box<dyn PackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
|
||||
PackageArgsBuilder {
|
||||
@@ -60,21 +59,21 @@ impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_apt(self) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
pub fn with_apt(self) -> PackageArgsBuilder<Packages, State, Box<dyn PackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager: Box::new(DebianPackageManager),
|
||||
manager: Box::new(AptManager),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_pacman(self) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
pub fn with_pacman(self) -> PackageArgsBuilder<Packages, State, Box<dyn PackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager: Box::new(ArchlinuxPackageManager),
|
||||
manager: Box::new(PacmanManager),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,14 +81,14 @@ impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
||||
|
||||
// We can only RUN the args when a package manager has been set. Alternatively,
|
||||
// when called inside a playbook, the package manager can be populated automatically from facts.
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManager>> {
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn PackageManager>> {
|
||||
pub fn run(self) -> Result<PackageStatus, PackageError> {
|
||||
self.build().run()
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManager>> {
|
||||
pub fn build(self) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn PackageManager>> {
|
||||
pub fn build(self) -> PackageModule<Box<dyn PackageManager>> {
|
||||
let Self { name, state, manager, .. } = self;
|
||||
|
||||
PackageModule {
|
||||
@@ -101,3 +100,17 @@ impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageArgsBuilder<PackageList, PackageState, NoManager> {
|
||||
pub fn build(self) -> PackageModule<NoManager> {
|
||||
let Self { name, state, .. } = self;
|
||||
|
||||
PackageModule {
|
||||
args: PackageArgs {
|
||||
name,
|
||||
state,
|
||||
},
|
||||
manager: NoManager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-89
@@ -1,110 +1,84 @@
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::facts::{Facts, os::OsFamily};
|
||||
use crate::utils::cmd::CmdOutput;
|
||||
use crate::utils::serde::string_or_seq_string;
|
||||
|
||||
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)]
|
||||
pub struct PackageArgs {
|
||||
name: PackageList,
|
||||
state: PackageState,
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct PackageList {
|
||||
#[serde(deserialize_with = "string_or_seq_string")]
|
||||
pub list: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum PackageState {
|
||||
Present,
|
||||
Absent,
|
||||
Latest,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PackageModule<Manager> {
|
||||
manager: Manager,
|
||||
args: PackageArgs,
|
||||
}
|
||||
|
||||
impl<Manager> Module<PackageArgs, (), PackageError> for PackageModule<Manager> {
|
||||
fn clone_args(&self) -> PackageArgs {
|
||||
self.args.clone()
|
||||
impl PackageList {
|
||||
pub fn list(&self) -> & [ String ] {
|
||||
&self.list
|
||||
}
|
||||
|
||||
fn module_name() -> &'static str {
|
||||
"package"
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
fn run(&self) -> Result<(), PackageError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
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,
|
||||
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 PackageModule {
|
||||
pub fn new() -> PackageArgsBuilder {
|
||||
PackageArgsBuilder::new()
|
||||
}
|
||||
|
||||
pub fn from_args_with_facts(args: PackageArgs, facts: &Facts) -> PackageModule {
|
||||
let manager = match facts.os.family() {
|
||||
OsFamily::Debian => PackageModule(Box::new(DebianPackageManager)),
|
||||
OsFamily::Archlinux => PackageModule(Box::new(ArchlinuxPackageManager)),
|
||||
};
|
||||
PackageModule {
|
||||
manager,
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_args_with(args: PackageArgs, manager: SpecificPackageManager) -> PackageManager {
|
||||
PackageModule {
|
||||
manager: Box::new(manager),
|
||||
args,
|
||||
impl IntoPackageList for &str {
|
||||
fn into_package_list(self) -> PackageList {
|
||||
PackageList {
|
||||
list: vec!(self.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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())
|
||||
impl IntoPackageList for String {
|
||||
fn into_package_list(self) -> PackageList {
|
||||
PackageList {
|
||||
list: vec!(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpecificPackageManager: std::fmt::Debug {
|
||||
fn name(&self) -> &'static str;
|
||||
fn update(&self) -> Result<(), PackageError>;
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
impl<const C: usize> IntoPackageList for & [ &str; C ] {
|
||||
fn into_package_list(self) -> PackageList {
|
||||
PackageList {
|
||||
list: self.iter().map(|x| x.to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+126
-44
@@ -1,4 +1,4 @@
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Deserialize, Deserializer};
|
||||
|
||||
use crate::{Module, ModuleSetup};
|
||||
use crate::facts::{Facts, os::OsFamily};
|
||||
@@ -6,40 +6,67 @@ use crate::utils::cmd::CmdOutput;
|
||||
|
||||
use std::boxed::Box;
|
||||
|
||||
pub mod list;
|
||||
pub use list::{PackageList, IntoPackageList};
|
||||
|
||||
pub mod pacman;
|
||||
use pacman::ArchlinuxPackageManager;
|
||||
use pacman::PacmanManager;
|
||||
pub mod apt;
|
||||
use apt::DebianPackageManager;
|
||||
use apt::AptManager;
|
||||
|
||||
pub mod builder;
|
||||
pub use builder::PackageArgsBuilder;
|
||||
pub use builder::{PackageArgsBuilder, NoManager};
|
||||
|
||||
pub type PackageStatus = ();
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PackageArgs {
|
||||
name: PackageList,
|
||||
state: PackageState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NoManager;
|
||||
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn PackageManager>> {
|
||||
fn serialize_args(&self) -> serde_json::Value {
|
||||
serde_json::to_value(&self.args).unwrap()
|
||||
}
|
||||
@@ -49,17 +76,27 @@ impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPac
|
||||
}
|
||||
|
||||
fn run(self) -> Result<(), PackageError> {
|
||||
Ok(())
|
||||
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 SpecificPackageManager>>, PackageArgs, (), PackageError> for PackageModule<NoManager> {
|
||||
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
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 SpecificPackageManager> = match facts.os.family() {
|
||||
OsFamily::Debian => Box::new(DebianPackageManager),
|
||||
OsFamily::Archlinux => Box::new(ArchlinuxPackageManager),
|
||||
let manager: Box<dyn PackageManager> = match facts.os.family() {
|
||||
OsFamily::Debian => Box::new(AptManager),
|
||||
OsFamily::Archlinux => Box::new(PacmanManager),
|
||||
};
|
||||
|
||||
PackageModule {
|
||||
@@ -69,6 +106,12 @@ impl ModuleSetup<PackageModule<Box<dyn SpecificPackageManager>>, PackageArgs, ()
|
||||
}
|
||||
}
|
||||
|
||||
// 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> {
|
||||
@@ -88,44 +131,83 @@ impl From<std::io::Error> for PackageError {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpecificPackageManager: std::fmt::Debug {
|
||||
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>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct PackageList {
|
||||
pub list: Vec<String>,
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl PackageList {
|
||||
pub fn list(&self) -> & [ String ] {
|
||||
&self.list
|
||||
#[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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add<T: IntoPackageList>(self, add: T) -> PackageList {
|
||||
let Self { mut list } = self;
|
||||
#[test]
|
||||
fn ensure_present_multi() {
|
||||
let pkg = PackageModule::new()
|
||||
.name(&[ "hello", "sl" ])
|
||||
.state(PackageState::Present)
|
||||
.with_apt()
|
||||
.build();
|
||||
|
||||
let extend_with = add.into_package_list();
|
||||
list.extend(extend_with.list);
|
||||
|
||||
PackageList {
|
||||
list
|
||||
}
|
||||
assert_eq!(
|
||||
pkg.serialize_args(),
|
||||
serde_json::json!({
|
||||
"name": vec!("hello", "sl"),
|
||||
"state": PackageState::Present,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoPackageList {
|
||||
fn into_package_list(self) -> PackageList;
|
||||
}
|
||||
#[test]
|
||||
fn ensure_absent() {
|
||||
let pkg = PackageModule::new()
|
||||
.name("hello")
|
||||
.state(PackageState::Absent)
|
||||
.with_apt()
|
||||
.build();
|
||||
|
||||
impl <T: Into<Vec<String>>> IntoPackageList for T {
|
||||
fn into_package_list(self) -> PackageList {
|
||||
PackageList {
|
||||
list: self.into()
|
||||
}
|
||||
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,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,59 @@
|
||||
use crate::utils::cmd::Cmd;
|
||||
use crate::utils::cmd::{Cmd, CmdOutput};
|
||||
|
||||
use super::{PackageError, PackageList, PackageManager};
|
||||
|
||||
/// This structure is mostly used as an indirection for testing purposes.
|
||||
/// But you can use it to build your own pacman commands.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct PacmanCmd(pub Cmd);
|
||||
|
||||
impl PacmanCmd {
|
||||
pub fn new() -> PacmanCmd {
|
||||
PacmanCmd(
|
||||
Cmd::new("pacman").arg("--noconfirm").arg("--needed")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update() -> PacmanCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("-Sy");
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn is_installed(pkg: &str) -> PacmanCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("-Qi").arg(pkg);
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn install(list: PackageList) -> PacmanCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("-S").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn remove(list: PackageList) -> PacmanCmd {
|
||||
let mut cmd = Self::new();
|
||||
cmd.0 = cmd.0.arg("-R").args(list.list());
|
||||
cmd
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<CmdOutput, std::io::Error> {
|
||||
self.0.run()
|
||||
}
|
||||
}
|
||||
|
||||
use super::{PackageError, PackageList, SpecificPackageManager};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ArchlinuxPackageManager;
|
||||
pub struct PacmanManager;
|
||||
|
||||
impl SpecificPackageManager for ArchlinuxPackageManager {
|
||||
impl PackageManager for PacmanManager {
|
||||
fn name(&self) -> &'static str {
|
||||
"pacman"
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("pacman").arg("-Sy").run()?;
|
||||
let res = PacmanCmd::update().run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
@@ -20,8 +62,14 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_installed(&self, pkg: &str) -> Result<bool, PackageError> {
|
||||
let res = PacmanCmd::is_installed(pkg).run()?;
|
||||
|
||||
Ok(res.success)
|
||||
}
|
||||
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("pacman").arg("-S").args(list.list()).run()?;
|
||||
let res = PacmanCmd::install(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
@@ -30,7 +78,110 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, _list: PackageList) -> Result<(), PackageError> {
|
||||
todo!()
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = PacmanCmd::remove(list).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::IntoPackageList;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
pub fn update_cmd() {
|
||||
let cmd = PacmanCmd::update();
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.push("-Sy");
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd() {
|
||||
let cmd = PacmanCmd::install(vec!("sl").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-S", "sl" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn install_cmd_multi() {
|
||||
let cmd = PacmanCmd::install(vec!("sl", "hello").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-S", "sl", "hello" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd() {
|
||||
let cmd = PacmanCmd::remove(vec!("sl").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-R", "sl" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_cmd_multi() {
|
||||
let cmd = PacmanCmd::remove(vec!("sl", "hello").into_package_list());
|
||||
let mut args: Vec<&str> = vec!("--noconfirm", "--needed");
|
||||
args.extend(& [ "-R", "sl", "hello" ]);
|
||||
|
||||
assert_eq!(
|
||||
cmd.0,
|
||||
Cmd {
|
||||
program: "pacman".to_string(),
|
||||
args: args.iter().map(|x| x.to_string()).collect(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+39
-7
@@ -1,13 +1,19 @@
|
||||
use duct::cmd as duct_cmd;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Cmd {
|
||||
program: String,
|
||||
#[serde(default)]
|
||||
args: Vec<String>,
|
||||
#[serde(default)]
|
||||
stdin: Option<String>,
|
||||
#[serde(default)]
|
||||
chdir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
@@ -17,11 +23,12 @@ impl Cmd {
|
||||
args: Vec::new(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
env: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
@@ -30,12 +37,13 @@ impl Cmd {
|
||||
args,
|
||||
stdin,
|
||||
chdir,
|
||||
env,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
args.push(arg.as_ref().to_string());
|
||||
@@ -46,10 +54,12 @@ impl Cmd {
|
||||
args,
|
||||
stdin,
|
||||
chdir,
|
||||
} }
|
||||
env,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
s.push_str(input.as_ref());
|
||||
@@ -63,25 +73,47 @@ impl Cmd {
|
||||
args,
|
||||
stdin,
|
||||
chdir,
|
||||
env,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chdir<T: AsRef<str>>(self, dir: T) -> Cmd {
|
||||
let Self { program, args, stdin, .. } = self;
|
||||
let Self { program, args, stdin, env, .. } = self;
|
||||
|
||||
Cmd {
|
||||
program,
|
||||
args,
|
||||
stdin,
|
||||
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> {
|
||||
let Self { program, args, stdin, chdir, .. } = self;
|
||||
let Self { program, args, stdin, chdir, env, .. } = self;
|
||||
|
||||
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 {
|
||||
cmd = cmd.stdin_bytes(stdin.as_bytes());
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod cmd;
|
||||
pub mod serde;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
|
||||
use serde::de::value::SeqAccessDeserializer;
|
||||
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// https://stackoverflow.com/questions/41151080/deserialize-a-json-string-or-array-of-strings-into-a-vec
|
||||
pub fn string_or_seq_string<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
struct StringOrVec(PhantomData<Vec<String>>);
|
||||
|
||||
impl<'de> Visitor<'de> for StringOrVec {
|
||||
type Value = Vec<String>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("string or list of strings")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where E: serde::de::Error
|
||||
{
|
||||
Ok(vec![value.to_owned()])
|
||||
}
|
||||
|
||||
fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
|
||||
where S: SeqAccess<'de>
|
||||
{
|
||||
Deserialize::deserialize(SeqAccessDeserializer::new(visitor))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(StringOrVec(PhantomData))
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
help() {
|
||||
echo "test.sh [SERVER]"
|
||||
echo "Start the rustible test suite on a certain machine (localhost by default)"
|
||||
echo "Requires lxc-ramdisk to be installed: https://kl.netlib.re/gitea/selfhoster1312/lxc-ramdisk"
|
||||
}
|
||||
|
||||
build() {
|
||||
if cargo build --release 2>&1 >/dev/null; then
|
||||
echo "$(pwd)"/target/release/rustible
|
||||
ret=0
|
||||
else
|
||||
echo "BUILD FAILED."
|
||||
ret=1
|
||||
fi
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
gen_rand_name() {
|
||||
echo rustible-"$RANDOM"
|
||||
}
|
||||
|
||||
execute_localhost() {
|
||||
while true; do
|
||||
TEST_NAME="$(gen_rand_name)"
|
||||
TEST_PATH=/tmp/"$TEST_NAME"
|
||||
if [ ! -e "$TEST_PATH" ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Start test $TEST_NAME"
|
||||
cp target/release/rustible "$TEST_PATH"
|
||||
}
|
||||
|
||||
execute_on_server() {
|
||||
SERVER="$1"
|
||||
if ssh "$SERVER" true >/dev/null 2>&1; then
|
||||
echo "Connection to "$1" successful"
|
||||
else
|
||||
echo "ERROR: Connection failed to "$1""
|
||||
return 1
|
||||
fi
|
||||
|
||||
while true; do
|
||||
TEST_NAME="$(gen_rand_name)"
|
||||
TEST_PATH=/tmp/"$TEST_NAME"
|
||||
if ! ssh "$1" ls "$TEST_PATH" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Start test "$TEST_NAME" ("$TEST_PATH")"
|
||||
if ! scp target/release/rustible "$SERVER":"$TEST_PATH"; then
|
||||
echo "ERROR: Copy to "$SERVER" failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Move to crate directory
|
||||
OLDDIR="$(pwd)"
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Figure out rustible test binary path
|
||||
BIN_PATH="$(build)"
|
||||
if [ ! $? -eq 0 ]; then
|
||||
# Compilation failed, abort
|
||||
echo "$BIN_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$1" = "" ]]; then
|
||||
execute_localhost
|
||||
ret=$?
|
||||
else
|
||||
case "$1" in
|
||||
"help" | "-h" | "--help")
|
||||
help
|
||||
ret=0
|
||||
;;
|
||||
*)
|
||||
execute_on_server "$1"
|
||||
ret=$?
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
cd "$OLDDIR"
|
||||
exit $ret
|
||||
@@ -0,0 +1,19 @@
|
||||
(
|
||||
modules: [
|
||||
(
|
||||
name: "install hello",
|
||||
module: "package",
|
||||
args: (
|
||||
name: [ "hello", "cmake", "firefox-esr", "libreoffice", "make", "gcc", "zstd", "zlib1g", "zlib1g-dev", "zenity", "zenity-common", "yt-dlp", "yelp", "yelp-xsl", "xz-utils", "xserver-xorg", "xtrans-dev" ],
|
||||
state: "present",
|
||||
)
|
||||
), (
|
||||
name: "say hello",
|
||||
module: "command",
|
||||
args: (
|
||||
program: "hello",
|
||||
args: [ "-g", "welcome to rustible" ],
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# Check that the playbook run returned "welcome to rustible"
|
||||
echo "$STDOUT" | tail -n 3 | grep -P "\^"Welcome to rustible!\""
|
||||
|
||||
# Check that hello is effectively installed
|
||||
hello -g "test" | grep -P "^test$"
|
||||
Reference in New Issue
Block a user