From 74078c635c1bf138e91c6778b968883d02f18638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 28 Nov 2024 22:20:50 +0000 Subject: [PATCH 1/9] refactor(rust): use an enum-based approach to handle scripts --- rust/agama-lib/src/scripts/client.rs | 2 +- rust/agama-lib/src/scripts/error.rs | 2 + rust/agama-lib/src/scripts/model.rs | 220 ++++++++++++++++++------- rust/agama-lib/src/scripts/settings.rs | 27 +-- rust/agama-lib/src/scripts/store.rs | 45 ++--- rust/agama-server/src/scripts/web.rs | 4 +- 6 files changed, 190 insertions(+), 110 deletions(-) diff --git a/rust/agama-lib/src/scripts/client.rs b/rust/agama-lib/src/scripts/client.rs index f71c82fbac..dfb05ab703 100644 --- a/rust/agama-lib/src/scripts/client.rs +++ b/rust/agama-lib/src/scripts/client.rs @@ -35,7 +35,7 @@ impl ScriptsClient { /// Adds a script to the given group. /// /// * `script`: script's definition. - pub async fn add_script(&self, script: &Script) -> Result<(), ServiceError> { + pub async fn add_script(&self, script: Script) -> Result<(), ServiceError> { self.client.post_void("/scripts", &script).await } diff --git a/rust/agama-lib/src/scripts/error.rs b/rust/agama-lib/src/scripts/error.rs index d6dbc3b3b2..575981aeb7 100644 --- a/rust/agama-lib/src/scripts/error.rs +++ b/rust/agama-lib/src/scripts/error.rs @@ -29,4 +29,6 @@ pub enum ScriptError { Unreachable(#[from] TransferError), #[error("I/O error: '{0}'")] InputOutputError(#[from] io::Error), + #[error("Wrong script type")] + WrongScriptType, } diff --git a/rust/agama-lib/src/scripts/model.rs b/rust/agama-lib/src/scripts/model.rs index 4c6df019f1..951aab940b 100644 --- a/rust/agama-lib/src/scripts/model.rs +++ b/rust/agama-lib/src/scripts/model.rs @@ -43,6 +43,34 @@ pub enum ScriptsGroup { Init, } +#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema)] +pub struct BaseScript { + pub name: String, + #[serde(flatten)] + pub source: ScriptSource, +} + +impl BaseScript { + fn write>(&self, workdir: P) -> Result<(), ScriptError> { + let script_path = workdir.as_ref().join(&self.name); + std::fs::create_dir_all(&script_path.parent().unwrap())?; + + let mut file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .mode(0o500) + .open(&script_path)?; + + match &self.source { + ScriptSource::Text { body } => write!(file, "{}", &body)?, + ScriptSource::Remote { url } => Transfer::get(url, file)?, + }; + + Ok(()) + } +} + #[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema)] #[serde(untagged)] pub enum ScriptSource { @@ -53,54 +81,139 @@ pub enum ScriptSource { } /// Represents a script to run as part of the installation process. +/// +/// There are different types of scripts that can run at different stages of the installation. #[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema)] -pub struct Script { - /// Script's name. - pub name: String, - #[serde(flatten)] - pub source: ScriptSource, - /// Script's group - pub group: ScriptsGroup, +#[serde(tag = "type")] +pub enum Script { + Pre(PreScript), + Post(PostScript), + Init(InitScript), } impl Script { - /// Runs the script and returns the output. + fn base(&self) -> &BaseScript { + match self { + Script::Pre(inner) => &inner.base, + Script::Post(inner) => &inner.base, + Script::Init(inner) => &inner.base, + } + } + + /// Returns the name of the script. + pub fn name(&self) -> &str { + self.base().name.as_str() + } + + /// Writes the script to the given work directory. /// - /// * `workdir`: where to write assets (script, logs and exit code). - pub async fn run(&self, workdir: &Path) -> Result<(), ScriptError> { - let dir = workdir.join(self.group.to_string()); - let path = dir.join(&self.name); - let output = process::Command::new(&path).output()?; + /// The name of the script depends on the work directory and the script's group. + pub fn write>(&self, workdir: P) -> Result<(), ScriptError> { + let path = workdir.as_ref().join(&self.group().to_string()); + self.base().write(&path) + } - let stdout_log = dir.join(format!("{}.log", &self.name)); - fs::write(stdout_log, output.stdout)?; + /// Script's group. + /// + /// It determines whether the script runs. + pub fn group(&self) -> ScriptsGroup { + match self { + Script::Pre(_) => ScriptsGroup::Pre, + Script::Post(_) => ScriptsGroup::Post, + Script::Init(_) => ScriptsGroup::Init, + } + } - let stderr_log = dir.join(format!("{}.err", &self.name)); - fs::write(stderr_log, output.stderr)?; + /// Runs the script in the given work directory. + /// + /// It saves the logs and the exit status of the execution. + /// + /// * `workdir`: where to run the script. + pub fn run>(&self, workdir: P) -> Result<(), ScriptError> { + let path = workdir + .as_ref() + .join(&self.group().to_string()) + .join(self.name()); + let output = process::Command::new(&path).output()?; - let status_file = dir.join(format!("{}.out", &self.name)); - fs::write(status_file, output.status.to_string())?; + fs::write(path.with_extension("log"), output.stdout)?; + fs::write(path.with_extension("err"), output.stderr)?; + fs::write(path.with_extension("out"), output.status.to_string())?; Ok(()) } +} - /// Writes the script to the file system. - /// - /// * `path`: path to write the script to. - async fn write>(&self, path: P) -> Result<(), ScriptError> { - let mut file = fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .mode(0o500) - .open(&path)?; +/// Represents a script that runs before the installation starts. +#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema)] +pub struct PreScript { + #[serde(flatten)] + pub base: BaseScript, +} - match &self.source { - ScriptSource::Text { body } => write!(file, "{}", &body)?, - ScriptSource::Remote { url } => Transfer::get(url, file)?, - }; +impl From for Script { + fn from(value: PreScript) -> Self { + Self::Pre(value) + } +} - Ok(()) +impl TryFrom