Why does it not compile?
This commit is contained in:
commit
74bfea3434
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
366
Cargo.lock
generated
Normal file
366
Cargo.lock
generated
Normal file
|
@ -0,0 +1,366 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd4056f63fce3b82d852c3da92b08ea59959890813a7f4ce9c0ff85b10cf301b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "duct"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ae3fc31835f74c2a7ceda3aeede378b0ae2e74c8f1c36559fcc9ae2a4e7d3e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"os_pipe",
|
||||
"shared_child",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghost"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e77ac7b51b8e6313251737fcef4b1c01a2ea102bde68415b62c0ee9268fec357"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7741301a6d6a9b28ce77c0fb77a4eb116b6bc8f3bef09923f7743d059c4157d3"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"ghost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a53dbb20faf34b16087a931834cba2d7a73cc74af2b7ef345a4c8324e2409a12"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "rustible"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"duct",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snafu",
|
||||
"typetag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.160"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typetag"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edc3ebbaab23e6cc369cb48246769d031f5bd85f1b28141f32982e3c0c7b33cf"
|
||||
dependencies = [
|
||||
"erased-serde",
|
||||
"inventory",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"typetag-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typetag-impl"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb01b60fcc3f5e17babb1a9956263f3ccd2cadc3e52908400231441683283c1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "rustible"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
regex = "1"
|
||||
duct = "0.13"
|
||||
#erased-serde = "0.3"
|
||||
serde_json = "1"
|
||||
typetag = "0.2"
|
||||
# Error management
|
||||
#thiserror = "1"
|
||||
snafu = "0.7"
|
||||
|
||||
[lib]
|
||||
name = "rustible"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rustible"
|
||||
path = "src/main.rs"
|
||||
|
19
src/facts/mod.rs
Normal file
19
src/facts/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
pub mod os;
|
||||
use os::OsFact;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Facts {
|
||||
pub os: OsFact,
|
||||
}
|
||||
|
||||
impl Facts {
|
||||
pub fn new() -> Facts {
|
||||
Facts {
|
||||
os: OsFact::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn os(&self) -> &OsFact {
|
||||
&self.os
|
||||
}
|
||||
}
|
155
src/facts/os.rs
Normal file
155
src/facts/os.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
use regex::Regex;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum OsFamily {
|
||||
Debian,
|
||||
Archlinux,
|
||||
}
|
||||
|
||||
impl OsFamily {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Debian => "debian",
|
||||
Self::Archlinux => "archlinux",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct OsFact {
|
||||
family: OsFamily,
|
||||
version_int: Option<usize>,
|
||||
version_str: Option<String>,
|
||||
}
|
||||
|
||||
impl OsFact {
|
||||
/// Returns an [`OsFact`] from the standard `/etc/os-release` file
|
||||
pub fn new() -> OsFact {
|
||||
let content = std::fs::read_to_string("/etc/os-release").expect("Failed to read `/etc/os-release file.`");
|
||||
OsFact::from_str(&content)
|
||||
}
|
||||
|
||||
/// Returns an [`OsFact`] from the passed string slice (used for tests)
|
||||
pub fn from_str(os_release: &str) -> OsFact {
|
||||
let re = Regex::new(r#"(?m)^ID=(.*)"#).unwrap();
|
||||
if let Some(captures) = re.captures(&os_release) {
|
||||
if let Some(id) = captures.get(1) {
|
||||
match id.as_str() {
|
||||
"debian" => {
|
||||
let re = Regex::new(r#"(?m)^VERSION="(\d+) \((.*)\)"$"#).unwrap();
|
||||
if let Some(captures) = re.captures(&os_release) {
|
||||
let (v_int, v_str) = (captures.get(1), captures.get(2));
|
||||
match (v_int, v_str) {
|
||||
(Some(v_int), Some(v_str)) => {
|
||||
let v_int = usize::from_str(v_int.as_str()).expect(
|
||||
&format!("The OS version from /etc/os-release {} could not be parsed as integer.", v_int.as_str())
|
||||
);
|
||||
let v_str = v_str.as_str().to_string();
|
||||
OsFact {
|
||||
family: OsFamily::Debian,
|
||||
version_int: Some(v_int),
|
||||
version_str: Some(v_str),
|
||||
}
|
||||
}, _ => {
|
||||
panic!("OS was detected as Debian but some versioning info is missing. Integer: {:?}, String: {:?}", v_int, v_str);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
panic!("OS was detected as Debian but version information could not be acquired.");
|
||||
}
|
||||
}, "arch" => OsFact {
|
||||
family: OsFamily::Archlinux,
|
||||
version_int: None,
|
||||
version_str: None,
|
||||
}, _ => {
|
||||
panic!("OS Family not recognized: {}", id.as_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("regex capture `ID` does not exist in /etc/os-release");
|
||||
}
|
||||
} else {
|
||||
panic!("/etc/os-release does not contain `ID` line");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn family(&self) -> &OsFamily {
|
||||
&self.family
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{OsFact, OsFamily};
|
||||
|
||||
#[test]
|
||||
fn can_parse_debian_bookworm() {
|
||||
let os_release = r#"
|
||||
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
|
||||
NAME="Debian GNU/Linux"
|
||||
VERSION_ID="12"
|
||||
VERSION="12 (bookworm)"
|
||||
VERSION_CODENAME=bookworm
|
||||
ID=debian
|
||||
HOME_URL="https://www.debian.org/"
|
||||
SUPPORT_URL="https://www.debian.org/support"
|
||||
BUG_REPORT_URL="https://bugs.debian.org/"
|
||||
"#;
|
||||
let fact = OsFact::from_str(os_release);
|
||||
assert_eq!(fact, OsFact {
|
||||
family: OsFamily::Debian,
|
||||
version_int: Some(12),
|
||||
version_str: Some("bookworm".to_string())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn can_parse_debian_bullseye() {
|
||||
let os_release = r#"
|
||||
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
|
||||
NAME="Debian GNU/Linux"
|
||||
VERSION_ID="11"
|
||||
VERSION="11 (bullseye)"
|
||||
VERSION_CODENAME=bullseye
|
||||
ID=debian
|
||||
HOME_URL="https://www.debian.org/"
|
||||
SUPPORT_URL="https://www.debian.org/support"
|
||||
BUG_REPORT_URL="https://bugs.debian.org/"
|
||||
"#;
|
||||
let fact = OsFact::from_str(os_release);
|
||||
assert_eq!(fact, OsFact {
|
||||
family: OsFamily::Debian,
|
||||
version_int: Some(11),
|
||||
version_str: Some("bullseye".to_string())
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_parse_archlinux() {
|
||||
let os_release = r#"
|
||||
NAME="Arch Linux"
|
||||
PRETTY_NAME="Arch Linux"
|
||||
ID=arch
|
||||
BUILD_ID=rolling
|
||||
ANSI_COLOR="38;2;23;147;209"
|
||||
HOME_URL="https://archlinux.org/"
|
||||
DOCUMENTATION_URL="https://wiki.archlinux.org/"
|
||||
SUPPORT_URL="https://bbs.archlinux.org/"
|
||||
BUG_REPORT_URL="https://bugs.archlinux.org/"
|
||||
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
|
||||
LOGO=archlinux-logo
|
||||
"#;
|
||||
let fact = OsFact::from_str(os_release);
|
||||
assert_eq!(fact, OsFact {
|
||||
family: OsFamily::Archlinux,
|
||||
version_int: None,
|
||||
version_str: None
|
||||
});
|
||||
}
|
||||
|
||||
}
|
7
src/lib.rs
Normal file
7
src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
#[macro_use] extern crate serde;
|
||||
|
||||
pub mod modules;
|
||||
pub use modules::{Module, ModuleSetup};
|
||||
pub mod facts;
|
||||
pub use facts::Facts;
|
||||
pub mod utils;
|
28
src/main.rs
Normal file
28
src/main.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use rustible::Facts;
|
||||
use rustible::modules::{PlaybookRun, Module, Command};
|
||||
|
||||
fn main() -> Result<(), rustible::modules::command::CommandError> {
|
||||
let facts = Facts::new();
|
||||
println!("Hello, world! Running system {}", facts.os.family().as_str());
|
||||
|
||||
let mut playbook = PlaybookRun::new();
|
||||
|
||||
let cmd = Command::new().program("echo").args(&vec!("lol".to_string())).build();
|
||||
let res = cmd.run()?;
|
||||
println!("{}", res.success());
|
||||
|
||||
let cmd = Command::new().program("echo").args(&vec!("lol".to_string())).build();
|
||||
playbook.run(cmd)?;
|
||||
|
||||
let cmd = Command::new()
|
||||
.creates("/tmp/lol")
|
||||
.program("touch")
|
||||
.arg("/tmp/lol")
|
||||
.build();
|
||||
playbook.run(cmd)?;
|
||||
//let res = cmd.run
|
||||
|
||||
playbook.print_json_pretty();
|
||||
|
||||
Ok(())
|
||||
}
|
142
src/modules/command/builder.rs
Normal file
142
src/modules/command/builder.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::modules::Module;
|
||||
use super::{CommandModule, CommandError, CommandStatus};
|
||||
use super::condition::{SomeCondition};
|
||||
|
||||
use crate::utils::cmd::Cmd;
|
||||
|
||||
pub struct NoCmd;
|
||||
|
||||
pub struct NoCondition;
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct CreatesCondition(pub PathBuf);
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct RemovesCondition(pub PathBuf);
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct BothConditions(pub CreatesCondition, pub RemovesCondition);
|
||||
|
||||
/// Build a [`CommandModule`].
|
||||
/// You need to define the program/args before you can set the directory,
|
||||
/// or populate stdin.
|
||||
pub struct CommandBuilder<Program, Condition> {
|
||||
cmd: Program,
|
||||
condition: Option<Condition>,
|
||||
}
|
||||
|
||||
impl CommandBuilder<NoCmd, NoCondition> {
|
||||
pub fn new() -> Self {
|
||||
CommandBuilder {
|
||||
cmd: NoCmd,
|
||||
condition: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Condition> CommandBuilder<NoCmd, Condition> {
|
||||
pub fn program<T: AsRef<str>>(self, program: T) -> CommandBuilder<Cmd, Condition> {
|
||||
let Self { condition, .. } = self;
|
||||
|
||||
CommandBuilder {
|
||||
cmd: Cmd::new(program),
|
||||
condition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Condition> CommandBuilder<Cmd, Condition> {
|
||||
pub fn arg<T: AsRef<str>>(self, arg: T) -> CommandBuilder<Cmd, Condition> {
|
||||
let Self { cmd, condition, .. } = self;
|
||||
|
||||
Self {
|
||||
cmd: cmd.arg(arg),
|
||||
condition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn args<T: AsRef<str>>(self, args: & [ T ]) -> CommandBuilder<Cmd, Condition> {
|
||||
let Self { cmd, condition, .. } = self;
|
||||
|
||||
Self {
|
||||
cmd: cmd.args(args),
|
||||
condition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stdin<T: AsRef<str>>(self, input: T) -> CommandBuilder<Cmd, Condition> {
|
||||
let Self { cmd, condition, .. } = self;
|
||||
|
||||
CommandBuilder {
|
||||
cmd: cmd.stdin(input),
|
||||
condition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chdir<T: AsRef<str>>(self, dir: T) -> CommandBuilder<Cmd, Condition> {
|
||||
let Self { cmd, condition, .. } = self;
|
||||
|
||||
CommandBuilder {
|
||||
cmd: cmd.chdir(dir),
|
||||
condition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Args> CommandBuilder<Args, NoCondition> {
|
||||
pub fn creates<T: AsRef<Path>>(self, path: T) -> CommandBuilder<Args, CreatesCondition> {
|
||||
let Self { cmd, .. } = self;
|
||||
|
||||
CommandBuilder {
|
||||
cmd,
|
||||
condition: Some(CreatesCondition(path.as_ref().to_path_buf())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn removes<T: AsRef<Path>>(self, path: T) -> CommandBuilder<Args, RemovesCondition> {
|
||||
let Self { cmd, .. } = self;
|
||||
|
||||
CommandBuilder {
|
||||
cmd,
|
||||
condition: Some(RemovesCondition(path.as_ref().to_path_buf())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Args> CommandBuilder<Args, CreatesCondition> {
|
||||
pub fn removes<T: AsRef<Path>>(self, path: T) -> CommandBuilder<Args, BothConditions> {
|
||||
let Self {cmd, condition, .. } = self;
|
||||
|
||||
CommandBuilder {
|
||||
cmd,
|
||||
condition: Some(BothConditions(condition.unwrap(), RemovesCondition(path.as_ref().to_path_buf()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Args> CommandBuilder<Args, RemovesCondition> {
|
||||
pub fn creates<T: AsRef<Path>>(self, path: T) -> CommandBuilder<Args, BothConditions> {
|
||||
let Self { cmd, condition, .. } = self;
|
||||
|
||||
CommandBuilder {
|
||||
cmd,
|
||||
condition: Some(BothConditions(CreatesCondition(path.as_ref().to_path_buf()), condition.unwrap())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Condition: Into<Option<SomeCondition>>> CommandBuilder<Cmd, Condition> {
|
||||
pub fn build(self) -> CommandModule {
|
||||
let Self { cmd, condition, .. } = self;
|
||||
|
||||
let condition: Option<SomeCondition> = condition.map(|x| x.into()).flatten();
|
||||
|
||||
CommandModule {
|
||||
cmd,
|
||||
condition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<CommandStatus, CommandError> {
|
||||
self.build().run().into()
|
||||
}
|
||||
}
|
49
src/modules/command/condition.rs
Normal file
49
src/modules/command/condition.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use super::builder::{NoCondition, CreatesCondition, RemovesCondition, BothConditions};
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum SomeCondition {
|
||||
#[serde(rename="creates")]
|
||||
Creates(CreatesCondition),
|
||||
#[serde(rename="removes")]
|
||||
Removes(RemovesCondition),
|
||||
#[serde(rename="creates_and_removes")]
|
||||
Both(BothConditions),
|
||||
}
|
||||
|
||||
impl SomeCondition {
|
||||
pub fn should_run(&self) -> bool {
|
||||
match self {
|
||||
Self::Creates(c) => {
|
||||
! c.0.exists()
|
||||
}, Self::Removes(c) => {
|
||||
c.0.exists()
|
||||
}, Self::Both(c) => {
|
||||
! c.0.0.exists() || c.1.0.exists()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CreatesCondition> for Option<SomeCondition> {
|
||||
fn from(c: CreatesCondition) -> Option<SomeCondition> {
|
||||
Some(SomeCondition::Creates(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RemovesCondition> for Option<SomeCondition> {
|
||||
fn from(c: RemovesCondition) -> Option<SomeCondition> {
|
||||
Some(SomeCondition::Removes(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BothConditions> for Option<SomeCondition> {
|
||||
fn from(c: BothConditions) -> Option<SomeCondition> {
|
||||
Some(SomeCondition::Both(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NoCondition> for Option<SomeCondition> {
|
||||
fn from(_c: NoCondition) -> Option<SomeCondition> {
|
||||
None
|
||||
}
|
||||
}
|
117
src/modules/command/mod.rs
Normal file
117
src/modules/command/mod.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
use crate::utils::cmd::Cmd;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod builder;
|
||||
pub mod condition;
|
||||
|
||||
use builder::{CommandBuilder, NoCondition, NoCmd};
|
||||
use condition::SomeCondition;
|
||||
|
||||
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)]
|
||||
pub struct CommandArgs {
|
||||
#[serde(flatten)]
|
||||
pub args: Cmd,
|
||||
pub condition: Option<SomeCondition>,
|
||||
}
|
||||
|
||||
pub struct CommandModule {
|
||||
cmd: Cmd,
|
||||
condition: Option<SomeCondition>,
|
||||
}
|
||||
|
||||
impl CommandModule {
|
||||
pub fn new() -> CommandBuilder<NoCmd, NoCondition> {
|
||||
CommandBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleSetup<CommandArgs, CommandStatus, CommandError> for CommandModule {
|
||||
fn with_facts(self, _facts: &Facts) -> Box<dyn Module<CommandArgs, CommandStatus, CommandError>> {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<CommandArgs, CommandStatus, CommandError> for CommandModule {
|
||||
|
||||
fn serialize_args(&self) -> serde_json::Value {
|
||||
serde_json::to_value(&CommandArgs {
|
||||
args: self.cmd.clone(),
|
||||
condition: self.condition.clone(),
|
||||
}).unwrap()
|
||||
}
|
||||
|
||||
fn module_name(&self) -> &'static str {
|
||||
"command"
|
||||
}
|
||||
|
||||
fn run(self) -> Result<CommandStatus, CommandError> {
|
||||
// The command was built successfully, run it
|
||||
let Self { cmd, condition, .. } = self;
|
||||
|
||||
// Check conditions
|
||||
if let Some(condition) = condition {
|
||||
if ! condition.should_run() {
|
||||
return Ok(CommandStatus::Skipped(condition));
|
||||
}
|
||||
}
|
||||
|
||||
let res = cmd.run()?;
|
||||
|
||||
Ok(CommandStatus::Done(
|
||||
CommandReturn {
|
||||
success: res.success,
|
||||
stdin: res.stdin,
|
||||
dir: res.dir,
|
||||
stdout: res.stdout,
|
||||
stderr: res.stderr,
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Command module has inner parameters to determine whether the command should run (`creates` and `removes`). Because of this,
|
||||
/// it returns a special [`CommandStatus`] that can be Done or Skipped. These variants are flattened into the textual/JSON output
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag="status")]
|
||||
pub enum CommandStatus {
|
||||
#[serde(rename="done")]
|
||||
Done(CommandReturn),
|
||||
#[serde(rename="skip")]
|
||||
Skipped(SomeCondition),
|
||||
}
|
||||
|
||||
impl CommandStatus {
|
||||
pub fn success(&self) -> bool {
|
||||
match self {
|
||||
Self::Done(ret) => ret.success,
|
||||
Self::Skipped(_c) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return of a Command that was effectively run
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct CommandReturn {
|
||||
pub success: bool,
|
||||
pub stdin: Option<String>,
|
||||
pub dir: Option<PathBuf>,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CommandSkipped(SomeCondition);
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct CommandError(String);
|
||||
|
||||
impl From<std::io::Error> for CommandError {
|
||||
fn from(e: std::io::Error) -> CommandError {
|
||||
CommandError(e.to_string())
|
||||
}
|
||||
}
|
132
src/modules/mod.rs
Normal file
132
src/modules/mod.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
//use erased_serde::Serialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::Facts;
|
||||
|
||||
pub mod package;
|
||||
pub mod command;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct TaskRun {
|
||||
/// the arguments passed to the module
|
||||
module_args: serde_json::Value,
|
||||
//name: String,
|
||||
/// the name of the module
|
||||
module: String,
|
||||
#[serde(flatten)]
|
||||
/// the status of the task run (ok, skip, fail)
|
||||
status: TaskStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(tag="status", content = "return")]
|
||||
pub enum TaskStatus {
|
||||
#[serde(rename="ok")]
|
||||
Ok(serde_json::Value),
|
||||
#[serde(rename="skip")]
|
||||
Skip(serde_json::Value),
|
||||
#[serde(rename="fail")]
|
||||
Fail(serde_json::Value),
|
||||
}
|
||||
|
||||
impl TaskStatus {
|
||||
pub fn ok<T: Serialize>(val: T) -> TaskStatus {
|
||||
TaskStatus::Ok(serde_json::to_value(val).unwrap())
|
||||
}
|
||||
|
||||
pub fn skip<T: Serialize>(val: T) -> TaskStatus {
|
||||
TaskStatus::Skip(serde_json::to_value(val).unwrap())
|
||||
}
|
||||
|
||||
pub fn fail<T: Serialize>(val: T) -> TaskStatus {
|
||||
TaskStatus::Fail(serde_json::to_value(val).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct PlaybookRun {
|
||||
pub facts: Facts,
|
||||
pub steps: Vec<TaskRun>,
|
||||
}
|
||||
|
||||
impl PlaybookRun {
|
||||
pub fn new() -> PlaybookRun {
|
||||
PlaybookRun {
|
||||
facts: Facts::new(),
|
||||
steps: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_step(&mut self, step: TaskRun) {
|
||||
self.steps.push(step);
|
||||
}
|
||||
|
||||
pub fn print_json(&self) {
|
||||
println!("{}", serde_json::to_string(&self).unwrap());
|
||||
}
|
||||
|
||||
pub fn print_json_pretty(&self) {
|
||||
println!("{}", serde_json::to_string_pretty(&self).unwrap());
|
||||
}
|
||||
|
||||
pub fn run<MS: ModuleSetup<A, S, E>, A: Serialize, S: Serialize, E: Serialize>(&mut self, cmd: MS) -> Result<S, E> {
|
||||
let module = cmd.with_facts(&self.facts);
|
||||
self.run_inner(module)
|
||||
}
|
||||
|
||||
pub fn run_inner<A: Serialize, S: Serialize, E: Serialize>(&mut self, cmd: Box<dyn Module<A, S, E>>) -> Result<S, E> {
|
||||
// TODO: Check conditions for skip
|
||||
|
||||
let module_name = cmd.module_name().to_string();
|
||||
let args = cmd.serialize_args();
|
||||
|
||||
let res = cmd.run();
|
||||
|
||||
let status = match &res {
|
||||
Ok(s) => {
|
||||
//self.add_step(s)
|
||||
TaskStatus::ok(s)
|
||||
}, Err(e) => {
|
||||
TaskStatus::fail(e)
|
||||
}
|
||||
};
|
||||
|
||||
let run = TaskRun {
|
||||
module_args: args,
|
||||
module: module_name,
|
||||
status,
|
||||
};
|
||||
|
||||
self.add_step(run);
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
// pub trait Loopable {
|
||||
// fn loop_with<T>(self, looped: dyn Fn(Self) -> Self, with: Vec<T>);
|
||||
// }
|
||||
|
||||
/// A trait for modules to be run in the context of a playbook, by gathering facts
|
||||
pub trait ModuleSetup<A, S, E> {
|
||||
// type module: Module<A, S, E>;
|
||||
|
||||
fn with_facts(self, facts: &Facts) -> Box<dyn Module<A, S, E>>;
|
||||
}
|
||||
|
||||
/// A Module takes some arguments which can be serialized back to the playbook run, and can be run to produce
|
||||
/// a certain result.
|
||||
pub trait Module<A: Serialize, S: Serialize, E: Serialize>: Sized {
|
||||
/// Return the module name as string slice
|
||||
fn module_name(&self) -> &'static str;
|
||||
|
||||
/// Clone the arguments to be stored in the playbook's task run
|
||||
fn serialize_args(&self) -> serde_json::Value;
|
||||
|
||||
/// Run the module, producing a result
|
||||
fn run(self) -> Result<S, E>;
|
||||
}
|
||||
|
||||
/// Declare module types
|
||||
pub use command::CommandModule as Command;
|
||||
|
34
src/modules/package/apt.rs
Normal file
34
src/modules/package/apt.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use crate::utils::cmd::Cmd;
|
||||
|
||||
use super::{PackageError, PackageList, SpecificPackageManager};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebianPackageManager;
|
||||
|
||||
impl SpecificPackageManager for DebianPackageManager {
|
||||
fn name(&self) -> &'static str {
|
||||
"apt"
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("apt").arg("update").run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("apt").arg("install").args(list.list()).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, _list: PackageList) -> Result<(), PackageError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
103
src/modules/package/builder.rs
Normal file
103
src/modules/package/builder.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
use super::{PackageArgs, IntoPackageList, PackageList, PackageState, PackageModule, PackageStatus, PackageError, SpecificPackageManager};
|
||||
use super::apt::DebianPackageManager;
|
||||
use super::pacman::ArchlinuxPackageManager;
|
||||
use std::boxed::Box;
|
||||
|
||||
use crate::Module;
|
||||
|
||||
pub struct PackageArgsBuilder<Packages, State, Manager> {
|
||||
name: Packages,
|
||||
state: State,
|
||||
manager: Manager,
|
||||
}
|
||||
|
||||
pub struct NoPackage;
|
||||
pub struct NoState;
|
||||
pub struct NoManager;
|
||||
|
||||
impl PackageArgsBuilder<NoPackage, NoState, NoManager> {
|
||||
pub fn new() -> PackageArgsBuilder<NoPackage, NoState, NoManager> {
|
||||
PackageArgsBuilder {
|
||||
name: NoPackage,
|
||||
state: NoState,
|
||||
manager: NoManager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<State, Manager> PackageArgsBuilder<NoPackage, State, Manager> {
|
||||
pub fn name<T: IntoPackageList>(self, list: T) -> PackageArgsBuilder<PackageList, State, Manager> {
|
||||
let Self { state, manager, .. } = self;
|
||||
|
||||
PackageArgsBuilder {
|
||||
name: list.into_package_list(),
|
||||
state,
|
||||
manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Packages, Manager> PackageArgsBuilder<Packages, NoState, Manager> {
|
||||
pub fn state(self, state: PackageState) -> PackageArgsBuilder<Packages, PackageState, Manager> {
|
||||
let Self { name, manager, .. } = self;
|
||||
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Packages, State> PackageArgsBuilder<Packages, State, NoManager> {
|
||||
pub fn with(self, manager: impl SpecificPackageManager + 'static) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager: Box::new(manager),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_apt(self) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager: Box::new(DebianPackageManager),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_pacman(self) -> PackageArgsBuilder<Packages, State, Box<dyn SpecificPackageManager>> {
|
||||
let Self { name, state, .. } = self;
|
||||
PackageArgsBuilder {
|
||||
name,
|
||||
state,
|
||||
manager: Box::new(ArchlinuxPackageManager),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We can only RUN the args when a package manager has been set. Alternatively,
|
||||
// when called inside a playbook, the package manager can be populated automatically from facts.
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManager>> {
|
||||
pub fn run(self) -> Result<PackageStatus, PackageError> {
|
||||
self.build().run()
|
||||
}
|
||||
}
|
||||
|
||||
impl PackageArgsBuilder<PackageList, PackageState, Box<dyn SpecificPackageManager>> {
|
||||
pub fn build(self) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
let Self { name, state, manager, .. } = self;
|
||||
|
||||
PackageModule {
|
||||
args: PackageArgs {
|
||||
name,
|
||||
state,
|
||||
},
|
||||
manager,
|
||||
}
|
||||
}
|
||||
}
|
110
src/modules/package/list.rs
Normal file
110
src/modules/package/list.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::facts::{Facts, os::OsFamily};
|
||||
use crate::utils::cmd::CmdOutput;
|
||||
|
||||
use std::boxed::Box;
|
||||
|
||||
pub mod pacman;
|
||||
use pacman::ArchlinuxPackageManager;
|
||||
pub mod apt;
|
||||
use apt::DebianPackageManager;
|
||||
|
||||
pub mod builder;
|
||||
use builder::{NoPackage, PackageArgsBuilder};
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct PackageArgs {
|
||||
name: PackageList,
|
||||
state: PackageState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum PackageState {
|
||||
Present,
|
||||
Absent,
|
||||
Latest,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PackageModule<Manager> {
|
||||
manager: Manager,
|
||||
args: PackageArgs,
|
||||
}
|
||||
|
||||
impl<Manager> Module<PackageArgs, (), PackageError> for PackageModule<Manager> {
|
||||
fn clone_args(&self) -> PackageArgs {
|
||||
self.args.clone()
|
||||
}
|
||||
|
||||
fn module_name() -> &'static str {
|
||||
"package"
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
fn run(&self) -> Result<(), PackageError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<NoManager> {
|
||||
fn with_facts(self, facts: &Facts) -> PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
let Self { args, .. } = self;
|
||||
|
||||
let manager = match facts.os.family() {
|
||||
OsFamily::Debian => Box::new(DebianPackageManager),
|
||||
OsFamily::Archlinux => Box::new(ArchlinuxPackageManager),
|
||||
};
|
||||
|
||||
PackageModule {
|
||||
args,
|
||||
manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl PackageModule {
|
||||
pub fn new() -> PackageArgsBuilder {
|
||||
PackageArgsBuilder::new()
|
||||
}
|
||||
|
||||
pub fn from_args_with_facts(args: PackageArgs, facts: &Facts) -> PackageModule {
|
||||
let manager = match facts.os.family() {
|
||||
OsFamily::Debian => PackageModule(Box::new(DebianPackageManager)),
|
||||
OsFamily::Archlinux => PackageModule(Box::new(ArchlinuxPackageManager)),
|
||||
};
|
||||
PackageModule {
|
||||
manager,
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_args_with(args: PackageArgs, manager: SpecificPackageManager) -> PackageManager {
|
||||
PackageModule {
|
||||
manager: Box::new(manager),
|
||||
args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum PackageError {
|
||||
IoError(String),
|
||||
CmdFail(CmdOutput),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for PackageError {
|
||||
fn from(e: std::io::Error) -> PackageError {
|
||||
PackageError::IoError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpecificPackageManager: std::fmt::Debug {
|
||||
fn name(&self) -> &'static str;
|
||||
fn update(&self) -> Result<(), PackageError>;
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
}
|
131
src/modules/package/mod.rs
Normal file
131
src/modules/package/mod.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{Module, ModuleSetup};
|
||||
use crate::facts::{Facts, os::OsFamily};
|
||||
use crate::utils::cmd::CmdOutput;
|
||||
|
||||
use std::boxed::Box;
|
||||
|
||||
pub mod pacman;
|
||||
use pacman::ArchlinuxPackageManager;
|
||||
pub mod apt;
|
||||
use apt::DebianPackageManager;
|
||||
|
||||
pub mod builder;
|
||||
pub use builder::PackageArgsBuilder;
|
||||
|
||||
pub type PackageStatus = ();
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct PackageArgs {
|
||||
name: PackageList,
|
||||
state: PackageState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum PackageState {
|
||||
Present,
|
||||
Absent,
|
||||
Latest,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PackageModule<Manager> {
|
||||
manager: Manager,
|
||||
args: PackageArgs,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NoManager;
|
||||
|
||||
impl Module<PackageArgs, (), PackageError> for PackageModule<Box<dyn SpecificPackageManager>> {
|
||||
fn serialize_args(&self) -> serde_json::Value {
|
||||
serde_json::to_value(&self.args).unwrap()
|
||||
}
|
||||
|
||||
fn module_name(&self) -> &'static str {
|
||||
"package"
|
||||
}
|
||||
|
||||
fn run(self) -> Result<(), PackageError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleSetup<PackageArgs, (), PackageError> for PackageModule<NoManager> {
|
||||
fn with_facts(self, facts: &Facts) -> Box<dyn Module<PackageArgs, (), PackageError>> {
|
||||
let Self { args, .. } = self;
|
||||
|
||||
let manager: Box<dyn SpecificPackageManager> = match facts.os.family() {
|
||||
OsFamily::Debian => Box::new(DebianPackageManager),
|
||||
OsFamily::Archlinux => Box::new(ArchlinuxPackageManager),
|
||||
};
|
||||
|
||||
Box::new(PackageModule {
|
||||
args,
|
||||
manager,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl PackageModule<NoManager> {
|
||||
pub fn new() -> PackageArgsBuilder<builder::NoPackage, builder::NoState, builder::NoManager> {
|
||||
PackageArgsBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum PackageError {
|
||||
IoError(String),
|
||||
CmdFail(CmdOutput),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for PackageError {
|
||||
fn from(e: std::io::Error) -> PackageError {
|
||||
PackageError::IoError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpecificPackageManager: std::fmt::Debug {
|
||||
fn name(&self) -> &'static str;
|
||||
fn update(&self) -> Result<(), PackageError>;
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
fn remove(&self, list: PackageList) -> Result<(), PackageError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct PackageList {
|
||||
pub list: Vec<String>,
|
||||
}
|
||||
|
||||
impl PackageList {
|
||||
pub fn list(&self) -> & [ String ] {
|
||||
&self.list
|
||||
}
|
||||
|
||||
pub fn add<T: IntoPackageList>(self, add: T) -> PackageList {
|
||||
let Self { mut list } = self;
|
||||
|
||||
let extend_with = add.into_package_list();
|
||||
list.extend(extend_with.list);
|
||||
|
||||
PackageList {
|
||||
list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoPackageList {
|
||||
fn into_package_list(self) -> PackageList;
|
||||
}
|
||||
|
||||
impl <T: Into<Vec<String>>> IntoPackageList for T {
|
||||
fn into_package_list(self) -> PackageList {
|
||||
PackageList {
|
||||
list: self.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
36
src/modules/package/pacman.rs
Normal file
36
src/modules/package/pacman.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::utils::cmd::Cmd;
|
||||
|
||||
use super::{PackageError, PackageList, SpecificPackageManager};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ArchlinuxPackageManager;
|
||||
|
||||
impl SpecificPackageManager for ArchlinuxPackageManager {
|
||||
fn name(&self) -> &'static str {
|
||||
"pacman"
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("pacman").arg("-Sy").run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install(&self, list: PackageList) -> Result<(), PackageError> {
|
||||
let res = Cmd::new("pacman").arg("-S").args(list.list()).run()?;
|
||||
|
||||
if ! res.success {
|
||||
return Err(PackageError::CmdFail(res));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, _list: PackageList) -> Result<(), PackageError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
119
src/utils/cmd.rs
Normal file
119
src/utils/cmd.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use duct::cmd as duct_cmd;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Cmd {
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
stdin: Option<String>,
|
||||
chdir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
pub fn new<T: AsRef<str>>(program: T) -> Cmd {
|
||||
Cmd {
|
||||
program: program.as_ref().to_string(),
|
||||
args: Vec::new(),
|
||||
stdin: None,
|
||||
chdir: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arg<T: AsRef<str>>(self, arg: T) -> Cmd {
|
||||
let Self { program, mut args, stdin, chdir, .. } = self;
|
||||
|
||||
args.push(arg.as_ref().to_string());
|
||||
|
||||
Cmd {
|
||||
program,
|
||||
args,
|
||||
stdin,
|
||||
chdir,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn args<T: AsRef<str>>(self, new_args: & [ T ]) -> Cmd {
|
||||
|
||||
let Self { program, mut args, stdin, chdir, .. } = self;
|
||||
|
||||
for arg in new_args {
|
||||
args.push(arg.as_ref().to_string());
|
||||
}
|
||||
|
||||
Cmd {
|
||||
program,
|
||||
args,
|
||||
stdin,
|
||||
chdir,
|
||||
} }
|
||||
|
||||
pub fn stdin<T: AsRef<str>>(self, input: T) -> Cmd {
|
||||
let Self { program, args, mut stdin, chdir, .. } = self;
|
||||
|
||||
stdin = if let Some(mut s) = stdin {
|
||||
s.push_str(input.as_ref());
|
||||
Some(s)
|
||||
} else {
|
||||
Some(input.as_ref().to_string())
|
||||
};
|
||||
|
||||
Cmd {
|
||||
program,
|
||||
args,
|
||||
stdin,
|
||||
chdir,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chdir<T: AsRef<str>>(self, dir: T) -> Cmd {
|
||||
let Self { program, args, stdin, .. } = self;
|
||||
|
||||
Cmd {
|
||||
program,
|
||||
args,
|
||||
stdin,
|
||||
chdir: Some(PathBuf::from(dir.as_ref())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<CmdOutput, std::io::Error> {
|
||||
let Self { program, args, stdin, chdir, .. } = self;
|
||||
|
||||
let mut cmd = duct_cmd(&program, &args);
|
||||
|
||||
if let Some(stdin) = &stdin {
|
||||
cmd = cmd.stdin_bytes(stdin.as_bytes());
|
||||
}
|
||||
|
||||
if let Some(chdir) = &chdir {
|
||||
cmd = cmd.dir(chdir);
|
||||
}
|
||||
|
||||
let res = cmd.unchecked()
|
||||
.stdout_capture()
|
||||
.stderr_capture()
|
||||
.run()?;
|
||||
|
||||
Ok(CmdOutput {
|
||||
program,
|
||||
args,
|
||||
success: res.status.success(),
|
||||
stdin,
|
||||
dir: chdir,
|
||||
stdout: String::from_utf8(res.stdout).unwrap(),
|
||||
stderr: String::from_utf8(res.stderr).unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct CmdOutput {
|
||||
pub program: String,
|
||||
pub args: Vec<String>,
|
||||
pub success: bool,
|
||||
pub stdin: Option<String>,
|
||||
pub dir: Option<PathBuf>,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
1
src/utils/mod.rs
Normal file
1
src/utils/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod cmd;
|
Loading…
Reference in New Issue
Block a user