Compare commits
5 Commits
0f85701cc4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 89ef66a694 | |||
| 7d9e3d63b7 | |||
| d2ef322761 | |||
| 1245db813b | |||
| f26f1c1bf2 |
Generated
+142
@@ -11,6 +11,73 @@ dependencies = [
|
|||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "ctor"
|
name = "ctor"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -39,6 +106,12 @@ dependencies = [
|
|||||||
"shared_child",
|
"shared_child",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erased-serde"
|
name = "erased-serde"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
@@ -65,6 +138,15 @@ version = "0.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
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]]
|
[[package]]
|
||||||
name = "inventory"
|
name = "inventory"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@@ -93,6 +175,25 @@ version = "2.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
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]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.1"
|
version = "1.17.1"
|
||||||
@@ -127,6 +228,28 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"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]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.7.3"
|
version = "1.7.3"
|
||||||
@@ -144,12 +267,25 @@ version = "0.6.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
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]]
|
[[package]]
|
||||||
name = "rustible"
|
name = "rustible"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"duct",
|
"duct",
|
||||||
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"snafu",
|
"snafu",
|
||||||
@@ -162,6 +298,12 @@ version = "1.0.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.160"
|
version = "1.0.160"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ typetag = "0.2"
|
|||||||
# Error management
|
# Error management
|
||||||
#thiserror = "1"
|
#thiserror = "1"
|
||||||
snafu = "0.7"
|
snafu = "0.7"
|
||||||
|
ron = "0.8"
|
||||||
|
rayon = "1.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "rustible"
|
name = "rustible"
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
WIP ansible-like system for sysadmin in rust
|
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::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();
|
let facts = Facts::new();
|
||||||
println!("Hello, world! Running system {}", facts.os.family().as_str());
|
|
||||||
|
|
||||||
let mut playbook = PlaybookRun::new();
|
let mut playbook = PlaybookRun::new();
|
||||||
|
|
||||||
let cmd = Command::new().program("echo").args(&vec!("lol".to_string())).build();
|
let mut cli_args = args();
|
||||||
let res = cmd.run()?;
|
let test_playbook = cli_args.nth(1).expect("First argument should be playbook.ron");
|
||||||
println!("{}", res.success());
|
let test_shell = cli_args.nth(0).expect("Second argument should be test.sh");
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ use crate::utils::cmd::Cmd;
|
|||||||
pub struct NoCmd;
|
pub struct NoCmd;
|
||||||
|
|
||||||
pub struct NoCondition;
|
pub struct NoCondition;
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct CreatesCondition(pub PathBuf);
|
pub struct CreatesCondition(pub PathBuf);
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct RemovesCondition(pub PathBuf);
|
pub struct RemovesCondition(pub PathBuf);
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct BothConditions(pub CreatesCondition, pub RemovesCondition);
|
pub struct BothConditions(pub CreatesCondition, pub RemovesCondition);
|
||||||
|
|
||||||
/// Build a [`CommandModule`].
|
/// Build a [`CommandModule`].
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::builder::{NoCondition, CreatesCondition, RemovesCondition, BothConditions};
|
use super::builder::{NoCondition, CreatesCondition, RemovesCondition, BothConditions};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum SomeCondition {
|
pub enum SomeCondition {
|
||||||
#[serde(rename="creates")]
|
#[serde(rename="creates")]
|
||||||
Creates(CreatesCondition),
|
Creates(CreatesCondition),
|
||||||
|
|||||||
@@ -12,13 +12,24 @@ use crate::{Module, ModuleSetup, Facts};
|
|||||||
|
|
||||||
/// A copy of the argument passed to the [`CommandBuilder`],
|
/// A copy of the argument passed to the [`CommandBuilder`],
|
||||||
/// as returned serialized in the task run output
|
/// as returned serialized in the task run output
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct CommandArgs {
|
pub struct CommandArgs {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub args: Cmd,
|
pub args: Cmd,
|
||||||
pub condition: Option<SomeCondition>,
|
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 {
|
pub struct CommandModule {
|
||||||
cmd: Cmd,
|
cmd: Cmd,
|
||||||
condition: Option<SomeCondition>,
|
condition: Option<SomeCondition>,
|
||||||
@@ -92,6 +103,20 @@ impl CommandStatus {
|
|||||||
Self::Skipped(_c) => true,
|
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
|
/// 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 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();
|
||||||
|
|
||||||
@@ -129,4 +130,4 @@ pub trait Module<A: Serialize, S: Serialize, E: Serialize> {
|
|||||||
|
|
||||||
/// Declare module types
|
/// Declare module types
|
||||||
pub use command::CommandModule as Command;
|
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)]
|
#[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 {
|
fn name(&self) -> &'static str {
|
||||||
"apt"
|
"apt"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self) -> Result<(), PackageError> {
|
fn update(&self) -> Result<(), PackageError> {
|
||||||
let res = Cmd::new("apt").arg("update").run()?;
|
let res = AptCmd::update().run()?;
|
||||||
|
|
||||||
if ! res.success {
|
if ! res.success {
|
||||||
return Err(PackageError::CmdFail(res));
|
return Err(PackageError::CmdFail(res));
|
||||||
@@ -19,8 +64,27 @@ impl SpecificPackageManager for DebianPackageManager {
|
|||||||
Ok(())
|
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> {
|
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 {
|
if ! res.success {
|
||||||
return Err(PackageError::CmdFail(res));
|
return Err(PackageError::CmdFail(res));
|
||||||
@@ -28,7 +92,109 @@ impl SpecificPackageManager for DebianPackageManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, _list: PackageList) -> Result<(), PackageError> {
|
fn remove(&self, list: PackageList) -> Result<(), PackageError> {
|
||||||
todo!()
|
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::{PackageArgs, IntoPackageList, PackageList, PackageState, PackageModule, PackageStatus, PackageError, PackageManager};
|
||||||
use super::apt::DebianPackageManager;
|
use super::{apt::AptManager, pacman::PacmanManager};
|
||||||
use super::pacman::ArchlinuxPackageManager;
|
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
|
|
||||||
use crate::Module;
|
use crate::Module;
|
||||||
@@ -50,7 +49,7 @@ impl<Packages, Manager> PackageArgsBuilder<Packages, NoState, Manager> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
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;
|
let Self { name, state, .. } = self;
|
||||||
|
|
||||||
PackageArgsBuilder {
|
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;
|
let Self { name, state, .. } = self;
|
||||||
PackageArgsBuilder {
|
PackageArgsBuilder {
|
||||||
name,
|
name,
|
||||||
state,
|
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;
|
let Self { name, state, .. } = self;
|
||||||
PackageArgsBuilder {
|
PackageArgsBuilder {
|
||||||
name,
|
name,
|
||||||
state,
|
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,
|
// 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.
|
// 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> {
|
pub fn run(self) -> Result<PackageStatus, PackageError> {
|
||||||
self.build().run()
|
self.build().run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManager>> {
|
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn PackageManager>> {
|
||||||
pub fn build(self) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
pub fn build(self) -> PackageModule<Box<dyn PackageManager>> {
|
||||||
let Self { name, state, manager, .. } = self;
|
let Self { name, state, manager, .. } = self;
|
||||||
|
|
||||||
PackageModule {
|
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::serde::string_or_seq_string;
|
||||||
use crate::utils::cmd::CmdOutput;
|
|
||||||
|
|
||||||
use std::boxed::Box;
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
pub mod pacman;
|
pub struct PackageList {
|
||||||
use pacman::ArchlinuxPackageManager;
|
#[serde(deserialize_with = "string_or_seq_string")]
|
||||||
pub mod apt;
|
pub list: Vec<String>,
|
||||||
use apt::DebianPackageManager;
|
|
||||||
|
|
||||||
pub mod builder;
|
|
||||||
use builder::{NoPackage, PackageArgsBuilder};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct PackageArgs {
|
|
||||||
name: PackageList,
|
|
||||||
state: PackageState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
impl PackageList {
|
||||||
pub enum PackageState {
|
pub fn list(&self) -> & [ String ] {
|
||||||
Present,
|
&self.list
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module_name() -> &'static str {
|
|
||||||
"package"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
|
pub fn add<T: IntoPackageList>(self, add: T) -> PackageList {
|
||||||
fn run(&self) -> Result<(), PackageError> {
|
let Self { mut list } = self;
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let extend_with = add.into_package_list();
|
||||||
|
list.extend(extend_with.list);
|
||||||
|
|
||||||
impl Module<PackageArgs, (), PackageError> for PackageModule<NoManager> {
|
PackageList {
|
||||||
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
list
|
||||||
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.
|
||||||
|
/// 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 {
|
impl IntoPackageList for &str {
|
||||||
pub fn new() -> PackageArgsBuilder {
|
fn into_package_list(self) -> PackageList {
|
||||||
PackageArgsBuilder::new()
|
PackageList {
|
||||||
}
|
list: vec!(self.to_string()),
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-44
@@ -1,4 +1,4 @@
|
|||||||
use serde::Serialize;
|
use serde::{Serialize, Deserialize, Deserializer};
|
||||||
|
|
||||||
use crate::{Module, ModuleSetup};
|
use crate::{Module, ModuleSetup};
|
||||||
use crate::facts::{Facts, os::OsFamily};
|
use crate::facts::{Facts, os::OsFamily};
|
||||||
@@ -6,40 +6,67 @@ 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::PacmanManager;
|
||||||
pub mod apt;
|
pub mod apt;
|
||||||
use apt::DebianPackageManager;
|
use apt::AptManager;
|
||||||
|
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub use builder::PackageArgsBuilder;
|
pub use builder::{PackageArgsBuilder, NoManager};
|
||||||
|
|
||||||
pub type PackageStatus = ();
|
pub type PackageStatus = ();
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct PackageArgs {
|
pub struct PackageArgs {
|
||||||
name: PackageList,
|
name: PackageList,
|
||||||
state: PackageState,
|
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 {
|
pub enum PackageState {
|
||||||
Present,
|
Present,
|
||||||
Absent,
|
Absent,
|
||||||
Latest,
|
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> {
|
pub struct PackageModule<Manager> {
|
||||||
manager: Manager,
|
manager: Manager,
|
||||||
args: PackageArgs,
|
args: PackageArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn PackageManager>> {
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NoManager;
|
|
||||||
|
|
||||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
|
|
||||||
fn serialize_args(&self) -> serde_json::Value {
|
fn serialize_args(&self) -> serde_json::Value {
|
||||||
serde_json::to_value(&self.args).unwrap()
|
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> {
|
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> {
|
impl ModuleSetup<PackageModule<Box<dyn PackageManager>>, PackageArgs, (), PackageError> for PackageModule<NoManager> {
|
||||||
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn PackageManager>> {
|
||||||
let Self { args, .. } = self;
|
let Self { args, .. } = self;
|
||||||
|
|
||||||
let manager: Box<dyn SpecificPackageManager> = match facts.os.family() {
|
let manager: Box<dyn PackageManager> = match facts.os.family() {
|
||||||
OsFamily::Debian => Box::new(DebianPackageManager),
|
OsFamily::Debian => Box::new(AptManager),
|
||||||
OsFamily::Archlinux => Box::new(ArchlinuxPackageManager),
|
OsFamily::Archlinux => Box::new(PacmanManager),
|
||||||
};
|
};
|
||||||
|
|
||||||
PackageModule {
|
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> {
|
impl PackageModule<NoManager> {
|
||||||
pub fn new() -> PackageArgsBuilder<builder::NoPackage, builder::NoState, builder::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 name(&self) -> &'static str;
|
||||||
fn update(&self) -> Result<(), PackageError>;
|
fn update(&self) -> Result<(), PackageError>;
|
||||||
|
fn is_installed(&self, pkg: &str) -> Result<bool, PackageError>;
|
||||||
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)]
|
#[cfg(test)]
|
||||||
pub struct PackageList {
|
mod tests {
|
||||||
pub list: Vec<String>,
|
use super::*;
|
||||||
}
|
|
||||||
|
|
||||||
impl PackageList {
|
#[test]
|
||||||
pub fn list(&self) -> & [ String ] {
|
fn ensure_present() {
|
||||||
&self.list
|
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 {
|
#[test]
|
||||||
let Self { mut list } = self;
|
fn ensure_present_multi() {
|
||||||
|
let pkg = PackageModule::new()
|
||||||
|
.name(&[ "hello", "sl" ])
|
||||||
|
.state(PackageState::Present)
|
||||||
|
.with_apt()
|
||||||
|
.build();
|
||||||
|
|
||||||
let extend_with = add.into_package_list();
|
assert_eq!(
|
||||||
list.extend(extend_with.list);
|
pkg.serialize_args(),
|
||||||
|
serde_json::json!({
|
||||||
PackageList {
|
"name": vec!("hello", "sl"),
|
||||||
list
|
"state": PackageState::Present,
|
||||||
}
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IntoPackageList {
|
#[test]
|
||||||
fn into_package_list(self) -> PackageList;
|
fn ensure_absent() {
|
||||||
}
|
let pkg = PackageModule::new()
|
||||||
|
.name("hello")
|
||||||
|
.state(PackageState::Absent)
|
||||||
|
.with_apt()
|
||||||
|
.build();
|
||||||
|
|
||||||
impl <T: Into<Vec<String>>> IntoPackageList for T {
|
assert_eq!(
|
||||||
fn into_package_list(self) -> PackageList {
|
pkg.serialize_args(),
|
||||||
PackageList {
|
serde_json::json!({
|
||||||
list: self.into()
|
"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)]
|
#[derive(Debug)]
|
||||||
pub struct ArchlinuxPackageManager;
|
pub struct PacmanManager;
|
||||||
|
|
||||||
impl SpecificPackageManager for ArchlinuxPackageManager {
|
impl PackageManager for PacmanManager {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"pacman"
|
"pacman"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&self) -> Result<(), PackageError> {
|
fn update(&self) -> Result<(), PackageError> {
|
||||||
let res = Cmd::new("pacman").arg("-Sy").run()?;
|
let res = PacmanCmd::update().run()?;
|
||||||
|
|
||||||
if ! res.success {
|
if ! res.success {
|
||||||
return Err(PackageError::CmdFail(res));
|
return Err(PackageError::CmdFail(res));
|
||||||
@@ -20,8 +62,14 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
|
|||||||
Ok(())
|
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> {
|
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 {
|
if ! res.success {
|
||||||
return Err(PackageError::CmdFail(res));
|
return Err(PackageError::CmdFail(res));
|
||||||
@@ -30,7 +78,110 @@ impl SpecificPackageManager for ArchlinuxPackageManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, _list: PackageList) -> Result<(), PackageError> {
|
fn remove(&self, list: PackageList) -> Result<(), PackageError> {
|
||||||
todo!()
|
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 duct::cmd as duct_cmd;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Cmd {
|
pub struct Cmd {
|
||||||
program: String,
|
program: String,
|
||||||
|
#[serde(default)]
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
stdin: Option<String>,
|
stdin: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
chdir: Option<PathBuf>,
|
chdir: Option<PathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
env: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cmd {
|
impl Cmd {
|
||||||
@@ -17,11 +23,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 +37,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 +54,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 +73,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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub mod cmd;
|
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