Initial POC

This commit is contained in:
Zykino 2023-01-07 17:08:44 +01:00
commit 7c765fd48e
8 changed files with 537 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

187
Cargo.lock generated Normal file
View File

@ -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"

15
Cargo.toml Normal file
View File

@ -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"

35
examples/openSUSE.yml Normal file
View File

@ -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

208
src/command.rs Normal file
View File

@ -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<System>,
steps: UpdateSteps,
nice: Option<i32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct System {
fetch: Option<Fetch>,
compile: Option<Compile>,
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<String>,
current_dir: Option<PathBuf>,
env: HashMap<String, String>,
}
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<Updater> {
// 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.")
}
}
}

58
src/errors.rs Normal file
View File

@ -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<Error>,
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<Error>, 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()),
}
}
}

20
src/lib.rs Normal file
View File

@ -0,0 +1,20 @@
mod command;
mod errors;
use command::*;
use errors::*;
use std::path::PathBuf;
use std::result;
pub type Result<T> = result::Result<T, Error>;
pub struct Opt {
pub config_file: PathBuf,
}
pub fn run(opt: &Opt) {
let updater = Updater::parse_config(opt).unwrap();
dbg!(updater).update_all().unwrap();
}

13
src/main.rs Normal file
View File

@ -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);
}