Compare commits
	
		
			2 Commits
		
	
	
		
			d2ef322761
			...
			89ef66a694
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 89ef66a694 | |||
| 7d9e3d63b7 | 
							
								
								
									
										142
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										142
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								examples/facts.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								examples/facts.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
use rustible::Facts;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let facts = Facts::new();
 | 
			
		||||
    println!("rustible running system: {}", facts.os.family().as_str());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								examples/hello.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								examples/hello.rs
									
									
									
									
									
										Normal file
									
								
							@ -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(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										84
									
								
								src/main.rs
									
									
									
									
									
								
							@ -1,11 +1,28 @@
 | 
			
		||||
use rustible::Facts;
 | 
			
		||||
use ron::value::Value;
 | 
			
		||||
use serde::{Serialize, Deserialize};
 | 
			
		||||
 | 
			
		||||
use std::env::args;
 | 
			
		||||
 | 
			
		||||
use rustible::Facts;
 | 
			
		||||
use rustible::modules::{
 | 
			
		||||
    Module, PlaybookRun,
 | 
			
		||||
    package::{PackageModule as Package, PackageState, PackageError},
 | 
			
		||||
    command::{CommandModule as Command, CommandError},
 | 
			
		||||
    Module, PlaybookRun, ModuleSetup,
 | 
			
		||||
    package::{PackageModule as Package, PackageState, PackageError, PackageArgs},
 | 
			
		||||
    command::{CommandModule as Command, CommandError, CommandArgs},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[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),
 | 
			
		||||
@ -26,48 +43,35 @@ impl From<PackageError> for PlaybookError {
 | 
			
		||||
 | 
			
		||||
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 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");
 | 
			
		||||
 | 
			
		||||
    let cmd = Command::new().program("echo").args(&vec!("lol".to_string())).build();
 | 
			
		||||
    playbook.run(cmd)?;
 | 
			
		||||
    let test_playbook_content = std::fs::read_to_string(&test_playbook).unwrap();
 | 
			
		||||
 | 
			
		||||
    let cmd = Command::new()
 | 
			
		||||
        .creates("/tmp/lol")
 | 
			
		||||
        .program("touch")
 | 
			
		||||
        .arg("/tmp/lol")
 | 
			
		||||
        .build();
 | 
			
		||||
    playbook.run(cmd)?;
 | 
			
		||||
    let playbook_ron: DeclarativePlaybook = ron::from_str(&test_playbook_content).unwrap();
 | 
			
		||||
 | 
			
		||||
    let pkg = Package::new()
 | 
			
		||||
        .name("hello")
 | 
			
		||||
        .state(PackageState::Present)
 | 
			
		||||
        .build();
 | 
			
		||||
    playbook.run(pkg)?;
 | 
			
		||||
 | 
			
		||||
    let pkg = Package::new()
 | 
			
		||||
        .name("hello")
 | 
			
		||||
        .state(PackageState::Absent)
 | 
			
		||||
        .build();
 | 
			
		||||
    playbook.run(pkg)?;
 | 
			
		||||
 | 
			
		||||
    let pkg = Package::new()
 | 
			
		||||
        .name(vec!("hello", "sl"))
 | 
			
		||||
        .state(PackageState::Present)
 | 
			
		||||
        .build();
 | 
			
		||||
    playbook.run(pkg)?;
 | 
			
		||||
 | 
			
		||||
    let pkg = Package::new()
 | 
			
		||||
        .name(&[ "hello", "sl" ])
 | 
			
		||||
        .state(PackageState::Absent)
 | 
			
		||||
        .build();
 | 
			
		||||
    playbook.run(pkg)?;
 | 
			
		||||
    playbook.print_json_pretty();
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
use rayon::prelude::*;
 | 
			
		||||
 | 
			
		||||
use crate::utils::cmd::{Cmd, CmdOutput};
 | 
			
		||||
 | 
			
		||||
use super::{PackageError, PackageList, PackageManager};
 | 
			
		||||
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.
 | 
			
		||||
@ -40,6 +42,14 @@ impl AptCmd {
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct AptManager;
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
@ -54,7 +64,26 @@ impl PackageManager for AptManager {
 | 
			
		||||
        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> {
 | 
			
		||||
        // 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 {
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,11 @@
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use serde::{Serialize, Deserialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
 | 
			
		||||
use crate::utils::serde::string_or_seq_string;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
 | 
			
		||||
#[serde(transparent)]
 | 
			
		||||
pub struct PackageList {
 | 
			
		||||
    #[serde(deserialize_with = "string_or_seq_string")]
 | 
			
		||||
    pub list: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use serde::{Serialize, Deserialize, Deserializer};
 | 
			
		||||
 | 
			
		||||
use crate::{Module, ModuleSetup};
 | 
			
		||||
use crate::facts::{Facts, os::OsFamily};
 | 
			
		||||
@ -19,12 +19,25 @@ pub use builder::{PackageArgsBuilder, NoManager};
 | 
			
		||||
 | 
			
		||||
pub type PackageStatus = ();
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
 | 
			
		||||
pub struct PackageArgs {
 | 
			
		||||
    name: PackageList,
 | 
			
		||||
    state: PackageState,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ModuleSetup<PackageModule<Box<dyn PackageManager>>, PackageArgs, (), PackageError> for PackageArgs {
 | 
			
		||||
    fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn PackageManager>> {
 | 
			
		||||
        let manager: Box<dyn PackageManager> = match facts.os.family() {
 | 
			
		||||
            OsFamily::Debian => Box::new(AptManager),
 | 
			
		||||
            OsFamily::Archlinux => Box::new(PacmanManager),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        PackageModule {
 | 
			
		||||
            args: self,
 | 
			
		||||
            manager,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
 | 
			
		||||
pub enum PackageState {
 | 
			
		||||
    Present,
 | 
			
		||||
@ -32,6 +45,21 @@ pub enum PackageState {
 | 
			
		||||
    Latest,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'de> Deserialize<'de> for PackageState {
 | 
			
		||||
    fn deserialize<D>(de: D) -> Result<Self, D::Error>
 | 
			
		||||
    where
 | 
			
		||||
        D: Deserializer<'de>,
 | 
			
		||||
    {
 | 
			
		||||
        let variant = String::deserialize(de)?;
 | 
			
		||||
        Ok(match variant.as_str() {
 | 
			
		||||
            "present" => PackageState::Present,
 | 
			
		||||
            "absent" => PackageState::Absent,
 | 
			
		||||
            "latest" => PackageState::Latest,
 | 
			
		||||
            _other => unimplemented!(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Eq)]
 | 
			
		||||
pub struct PackageModule<Manager> {
 | 
			
		||||
    manager: Manager,
 | 
			
		||||
@ -78,6 +106,12 @@ impl ModuleSetup<PackageModule<Box<dyn PackageManager>>, PackageArgs, (), Packag
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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> {
 | 
			
		||||
@ -100,6 +134,7 @@ impl From<std::io::Error> for PackageError {
 | 
			
		||||
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>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,12 @@ impl PacmanCmd {
 | 
			
		||||
        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());
 | 
			
		||||
@ -56,6 +62,12 @@ impl PackageManager for PacmanManager {
 | 
			
		||||
        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 = PacmanCmd::install(list).run()?;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,13 +3,17 @@ use duct::cmd as duct_cmd;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
 | 
			
		||||
pub struct Cmd {
 | 
			
		||||
    pub program: String,
 | 
			
		||||
    pub args: Vec<String>,
 | 
			
		||||
    pub stdin: Option<String>,
 | 
			
		||||
    pub chdir: Option<PathBuf>,
 | 
			
		||||
    pub env: HashMap<String, String>,
 | 
			
		||||
    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 {
 | 
			
		||||
 | 
			
		||||
@ -1 +1,2 @@
 | 
			
		||||
pub mod cmd;
 | 
			
		||||
pub mod serde;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								src/utils/serde.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/utils/serde.rs
									
									
									
									
									
										Normal file
									
								
							@ -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))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										91
									
								
								test.sh
									
									
									
									
									
										Executable file
									
								
							@ -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
 | 
			
		||||
							
								
								
									
										19
									
								
								tests/hello/playbook.ron
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								tests/hello/playbook.ron
									
									
									
									
									
										Normal file
									
								
							@ -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" ],
 | 
			
		||||
    ),
 | 
			
		||||
  )
 | 
			
		||||
]
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										7
									
								
								tests/hello/test.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/hello/test.sh
									
									
									
									
									
										Normal file
									
								
							@ -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$"
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user