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