From 7c765fd48e407b44bd005685bfdd32042885fea5 Mon Sep 17 00:00:00 2001 From: Zykino Date: Sat, 7 Jan 2023 17:08:44 +0100 Subject: [PATCH] Initial POC --- .gitignore | 1 + Cargo.lock | 187 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 15 +++ examples/openSUSE.yml | 35 +++++++ src/command.rs | 208 ++++++++++++++++++++++++++++++++++++++++++ src/errors.rs | 58 ++++++++++++ src/lib.rs | 20 ++++ src/main.rs | 13 +++ 8 files changed, 537 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 examples/openSUSE.yml create mode 100644 src/command.rs create mode 100644 src/errors.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cc38f23 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,187 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "enumset" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-updater" +version = "0.1.0" +dependencies = [ + "enumset", + "serde", + "serde_yaml", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8d849bb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "system-updater" +version = "0.1.0" +edition = "2021" + +[[bin]] +path = "src/main.rs" +name = "sup" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +enumset = { version = "1.0" , feature = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9" diff --git a/examples/openSUSE.yml b/examples/openSUSE.yml new file mode 100644 index 0000000..a3c8864 --- /dev/null +++ b/examples/openSUSE.yml @@ -0,0 +1,35 @@ +systems: +- fetch: + command: + exe: sudo + params: + - zypper + - refresh + current_dir: null + env: {} + compile: null + install: + command: + exe: sudo + params: + - zypper + - dup + current_dir: null + env: {} +- install: + command: + exe: rustup + params: + - update + current_dir: null + env: {} +- install: + command: + exe: cargo + params: + - install-update + - -a + current_dir: null + env: {} +steps: Install +nice: null diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..b89828e --- /dev/null +++ b/src/command.rs @@ -0,0 +1,208 @@ +use crate::*; +use enumset::EnumSetType; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt; +use std::path::PathBuf; +use std::process::Command; + +#[derive(Debug, Serialize, Deserialize, EnumSetType)] +//#[enumset(serialize_as_list)] // TODO: use it or not? +enum UpdateSteps { + Fetch, + Compile, + Install, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Updater { + systems: Vec, + steps: UpdateSteps, + nice: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct System { + fetch: Option, + compile: Option, + install: Install, + // deps or rDeps : Tree + // exclusive_with : List +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Fetch { + command: Cmd, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Compile { + command: Cmd, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Install { + command: Cmd, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Cmd { + exe: String, + params: Vec, + current_dir: Option, + env: HashMap, +} + +impl Updater { + fn new() -> Updater { + let mut up = Updater { + systems: vec![], + steps: UpdateSteps::Fetch, + nice: None, + }; + up.systems.push(System { + fetch: None, + compile: None, + install: Install { + command: Cmd::new(), + }, + }); + up + } + + /// To create a sample config from code + #[doc(hidden)] + fn write_config(&self, opt: &Opt) { + use ::std::io::Write; + use std::fs::OpenOptions; + + let mut f = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&opt.config_file) + .unwrap(); + + f.write_all(serde_yaml::to_string(&self).unwrap().as_bytes()) + .unwrap(); + } + + pub fn parse_config(opt: &Opt) -> Result { + // let u = Updater::new(); + // u.write_config(opt); + + let file = std::fs::read_to_string(&opt.config_file).unwrap(); + Ok(serde_yaml::from_str(&file).unwrap()) + // TODO: + } + + pub fn update_all(&self) -> Result<()> { + let mut errors = vec![]; + for sys in &self.systems { + if let Err(err) = self.update(sys) { + eprintln!("Error catched {}", err); + errors.push(err); + } + } + + // TODO: + if errors.len() == 0 { + Ok(()) + } else { + Err(Error::new( + errors, + ErrorKind::Fetch, // TODO: Why should I choose here? + )) + } + } + + fn update(&self, sys: &System) -> Result<()> { + if self.steps == UpdateSteps::Fetch { + sys.fetch()?; + } + if self.steps == UpdateSteps::Compile { + sys.compile()?; + } + if self.steps == UpdateSteps::Install { + sys.install()?; + } + + Ok(()) + } +} + +impl System { + pub fn fetch(&self) -> Result<()> { + if let Some(fetch) = &self.fetch { + fetch.command.execute()?; + } + Ok(()) + } + + pub fn compile(&self) -> Result<()> { + if let Some(compile) = &self.compile { + compile.command.execute()?; + } + Ok(()) + } + + pub fn install(&self) -> Result<()> { + self.install.command.execute()?; + Ok(()) + } +} + +impl Cmd { + fn new() -> Cmd { + Cmd { + exe: "".into(), + params: vec![], + current_dir: None, + env: HashMap::new(), + } + } + + fn execute(&self) -> Result<()> { + let mut cmd = Command::new(&self.exe); + cmd.args(&self.params) + .env_clear() + .envs(&self.env) + // .stdout(cfg) + // .stderr(cfg) + ; + + if let Some(cdir) = &self.current_dir { + cmd.current_dir(std::fs::canonicalize(cdir).unwrap()); + } + + println!("Executing {}", self); + // TODO: Ask if ok or + if cmd.status().unwrap().success() { + println!("Youpi !"); + // Other checks? + } + println!("Exécutée"); + + Ok(()) + } +} + +impl fmt::Display for Cmd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let command = if self.params.is_empty() { + self.exe.clone() + } else { + format!("{} {}", &self.exe, &self.params.join(" ")) + }; + write!(f, "`{}`", command)?; + if let Some(cdir) = &self.current_dir { + write!(f, " in {:?}", cdir)?; + } + if !self.env.is_empty() { + writeln!(f, " with the following environment variable:")?; + writeln!(f, "{:?}", self.env) + } else { + writeln!(f, " without any environment variable.") + } + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..804de72 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,58 @@ +use std::error; +use std::fmt; + +/// An error that can occur in this crate. +/// +/// Generally, this error corresponds to problems with underlying process. +#[derive(Debug)] +pub struct Error { + source: Vec, + kind: ErrorKind, +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum ErrorKind { + Config, + Fetch, // TODO: merge into "Update" or "Command" type of error? => Have this as an other level of error? + Compile, // TODO: merge into "Update" or "Command" type of error? => Have this as an other level of error? + Install, // TODO: merge into "Update" or "Command" type of error? => Have this as an other level of error? +} + +impl Error { + pub(crate) fn new(source: Vec, kind: ErrorKind) -> Error { + Error { source, kind } + } + + /// Return the kind of this error. + pub fn kind(&self) -> &ErrorKind { + &self.kind + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + if self.source.is_empty() { + None + } else { + Some(&self.source[0]) + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::error::Error; + + match self.kind { + ErrorKind::Config => write!( + f, + "Could not read configuration file: {}", + self.source().unwrap() + ), + ErrorKind::Fetch => write!(f, "Could not install: {}", self.source().unwrap()), + ErrorKind::Compile => write!(f, "Could not install: {}", self.source().unwrap()), + ErrorKind::Install => write!(f, "Could not install: {}", self.source().unwrap()), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8ffddb8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +mod command; +mod errors; + +use command::*; +use errors::*; + +use std::path::PathBuf; +use std::result; + +pub type Result = result::Result; + +pub struct Opt { + pub config_file: PathBuf, +} + +pub fn run(opt: &Opt) { + let updater = Updater::parse_config(opt).unwrap(); + + dbg!(updater).update_all().unwrap(); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c47e1e7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,13 @@ +use system_updater::*; + +fn main() { + let opt = Opt { + // + One config file? + // + A config subFolder and execute in alphabetical order? + // - A master config file that list the sub/real files? no if it mean parsing 2 differents formats + // + // Hardcoded for now + config_file: "examples/openSUSE.yml".into(), // Default to something like -> "~/.config/system-updater/list.yml".into(), + }; + run(&opt); +}