From aeaae17547f704e4a1e5f21616e93c5722b1a7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 12 Dec 2024 11:32:13 +0000 Subject: [PATCH 01/11] fix(rust): follow redirections in Transfer::get --- rust/agama-lib/src/transfer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/agama-lib/src/transfer.rs b/rust/agama-lib/src/transfer.rs index 2566c93c7b..3693a42d9f 100644 --- a/rust/agama-lib/src/transfer.rs +++ b/rust/agama-lib/src/transfer.rs @@ -27,6 +27,7 @@ impl Transfer { /// * `out_fd`: where to write the data. pub fn get(url: &str, mut out_fd: impl Write) -> TransferResult<()> { let mut handle = Easy::new(); + handle.follow_location(true)?; handle.url(url)?; let mut transfer = handle.transfer(); From eb4e3bbd73bf637a6ab3bc041c5e9a4de8911e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 12 Dec 2024 13:46:35 +0000 Subject: [PATCH 02/11] fix(rust): add a module to detect file formats --- rust/agama-cli/src/lib.rs | 2 +- rust/agama-cli/src/profile.rs | 2 +- rust/agama-lib/src/error.rs | 2 +- rust/agama-lib/src/lib.rs | 2 +- rust/agama-lib/src/scripts/error.rs | 2 +- rust/agama-lib/src/scripts/model.rs | 2 +- rust/agama-lib/src/utils.rs | 27 +++++ rust/agama-lib/src/utils/file_format.rs | 123 +++++++++++++++++++++ rust/agama-lib/src/{ => utils}/transfer.rs | 20 ++++ 9 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 rust/agama-lib/src/utils.rs create mode 100644 rust/agama-lib/src/utils/file_format.rs rename rust/agama-lib/src/{ => utils}/transfer.rs (57%) diff --git a/rust/agama-cli/src/lib.rs b/rust/agama-cli/src/lib.rs index f4fa82312d..836a9b787e 100644 --- a/rust/agama-cli/src/lib.rs +++ b/rust/agama-cli/src/lib.rs @@ -32,7 +32,7 @@ mod questions; use crate::error::CliError; use agama_lib::base_http_client::BaseHTTPClient; use agama_lib::{ - error::ServiceError, manager::ManagerClient, progress::ProgressMonitor, transfer::Transfer, + error::ServiceError, manager::ManagerClient, progress::ProgressMonitor, utils::Transfer, }; use auth::run as run_auth_cmd; use commands::Commands; diff --git a/rust/agama-cli/src/profile.rs b/rust/agama-cli/src/profile.rs index 1e7018cdac..847efcf86b 100644 --- a/rust/agama-cli/src/profile.rs +++ b/rust/agama-cli/src/profile.rs @@ -23,7 +23,7 @@ use agama_lib::{ base_http_client::BaseHTTPClient, install_settings::InstallSettings, profile::{AutoyastProfile, ProfileEvaluator, ProfileValidator, ValidationResult}, - transfer::Transfer, + utils::Transfer, Store as SettingsStore, }; use anyhow::Context; diff --git a/rust/agama-lib/src/error.rs b/rust/agama-lib/src/error.rs index 380aaafd8d..8d9a31790c 100644 --- a/rust/agama-lib/src/error.rs +++ b/rust/agama-lib/src/error.rs @@ -23,7 +23,7 @@ use std::io; use thiserror::Error; use zbus::{self, zvariant}; -use crate::transfer::TransferError; +use crate::utils::TransferError; #[derive(Error, Debug)] pub enum ServiceError { diff --git a/rust/agama-lib/src/lib.rs b/rust/agama-lib/src/lib.rs index a2213c2cad..d7aab9c5c1 100644 --- a/rust/agama-lib/src/lib.rs +++ b/rust/agama-lib/src/lib.rs @@ -66,7 +66,7 @@ pub use store::Store; pub mod openapi; pub mod questions; pub mod scripts; -pub mod transfer; +pub mod utils; use crate::error::ServiceError; use reqwest::{header, Client}; diff --git a/rust/agama-lib/src/scripts/error.rs b/rust/agama-lib/src/scripts/error.rs index 575981aeb7..fec37981ee 100644 --- a/rust/agama-lib/src/scripts/error.rs +++ b/rust/agama-lib/src/scripts/error.rs @@ -21,7 +21,7 @@ use std::io; use thiserror::Error; -use crate::transfer::TransferError; +use crate::utils::TransferError; #[derive(Error, Debug)] pub enum ScriptError { diff --git a/rust/agama-lib/src/scripts/model.rs b/rust/agama-lib/src/scripts/model.rs index ddf3c651f2..3ad9343712 100644 --- a/rust/agama-lib/src/scripts/model.rs +++ b/rust/agama-lib/src/scripts/model.rs @@ -28,7 +28,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::transfer::Transfer; +use crate::utils::Transfer; use super::ScriptError; diff --git a/rust/agama-lib/src/utils.rs b/rust/agama-lib/src/utils.rs new file mode 100644 index 0000000000..f6fb4f9020 --- /dev/null +++ b/rust/agama-lib/src/utils.rs @@ -0,0 +1,27 @@ +// Copyright (c) [2024] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +//! Utility module for Agama. + +mod file_format; +mod transfer; + +pub use file_format::*; +pub use transfer::*; diff --git a/rust/agama-lib/src/utils/file_format.rs b/rust/agama-lib/src/utils/file_format.rs new file mode 100644 index 0000000000..174e2ea7c7 --- /dev/null +++ b/rust/agama-lib/src/utils/file_format.rs @@ -0,0 +1,123 @@ +// Copyright (c) [2024] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + +//! File format detection for Agama. +//! +//! It implements a simple API to detect the file formats that are relevent for Agama. + +use std::{ + io::Write, + process::{Command, Stdio}, +}; + +/// File formats +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum FileFormat { + Json, + Jsonnet, + Script, + Unknown, +} + +const JSONNETFMT_PATH: &str = "/usr/bin/jsonnetfmt"; + +impl FileFormat { + pub fn from_file(file_path: &str) -> Self { + let Ok(content) = std::fs::read_to_string(file_path) else { + return Self::Unknown; + }; + Self::from_string(&content) + } + + pub fn from_string(content: &str) -> Self { + if Self::is_json(content) { + return Self::Json; + } else if Self::is_jsonnet(content) { + return Self::Jsonnet; + } else if Self::is_script(content) { + return Self::Script; + } + + Self::Unknown + } + + fn is_json(content: &str) -> bool { + let json = serde_json::from_str::(content); + json.is_ok() + } + + fn is_jsonnet(content: &str) -> bool { + let child = Command::new(JSONNETFMT_PATH) + .args(["-"]) + .stdin(Stdio::piped()) + .spawn(); + + let Ok(mut child) = child else { + return false; + }; + + let Some(mut stdin) = child.stdin.take() else { + return false; + }; + + stdin + .write_all(content.as_bytes()) + .expect("Failed to write to stdin"); + drop(stdin); + + child.wait().is_ok_and(|s| s.success()) + } + + fn is_script(content: &str) -> bool { + content.starts_with("#!") + } +} + +#[cfg(test)] +mod tests { + use super::FileFormat; + + #[test] + fn test_json() { + let content = r#" + { "name:": "value"} + "#; + + assert_eq!(FileFormat::from_string(content), FileFormat::Json); + } + + #[test] + fn test_jsonnet() { + let content = r#" + { name: "value" } + "#; + + assert_eq!(FileFormat::from_string(content), FileFormat::Jsonnet); + } + + #[test] + fn test_script() { + let content = r#"#!/bin/bash + echo "Hello World" + "#; + + assert_eq!(FileFormat::from_string(content), FileFormat::Script); + } +} diff --git a/rust/agama-lib/src/transfer.rs b/rust/agama-lib/src/utils/transfer.rs similarity index 57% rename from rust/agama-lib/src/transfer.rs rename to rust/agama-lib/src/utils/transfer.rs index 3693a42d9f..29b92502c7 100644 --- a/rust/agama-lib/src/transfer.rs +++ b/rust/agama-lib/src/utils/transfer.rs @@ -1,3 +1,23 @@ +// Copyright (c) [2024] SUSE LLC +// +// All Rights Reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, contact SUSE LLC. +// +// To contact SUSE LLC about this file by physical or electronic mail, you may +// find current contact information at www.suse.com. + //! File transfer API for Agama. //! //! Implement a file transfer API which, in the future, will support Agama specific URLs. Check the From 9e0dbeb7e1e7d19df6f40d6432a63495c8a1d0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 12 Dec 2024 16:38:10 +0000 Subject: [PATCH 03/11] fix(rust): detect the profile format by its content --- rust/agama-cli/src/profile.rs | 82 ++++++++++++++----------- rust/agama-lib/src/profile.rs | 50 ++++++++------- rust/agama-lib/src/utils/file_format.rs | 22 +++++-- 3 files changed, 93 insertions(+), 61 deletions(-) diff --git a/rust/agama-cli/src/profile.rs b/rust/agama-cli/src/profile.rs index 847efcf86b..b1d95e1794 100644 --- a/rust/agama-cli/src/profile.rs +++ b/rust/agama-cli/src/profile.rs @@ -22,7 +22,8 @@ use crate::show_progress; use agama_lib::{ base_http_client::BaseHTTPClient, install_settings::InstallSettings, - profile::{AutoyastProfile, ProfileEvaluator, ProfileValidator, ValidationResult}, + profile::{AutoyastProfileImporter, ProfileEvaluator, ProfileValidator, ValidationResult}, + utils::FileFormat, utils::Transfer, Store as SettingsStore, }; @@ -114,48 +115,59 @@ async fn import(url_string: String, dir: Option) -> anyhow::Result<()> tokio::spawn(async move { show_progress().await.unwrap(); }); + let url = Url::parse(&url_string)?; let tmpdir = TempDir::new()?; // TODO: create it only if dir is not passed + let work_dir = dir.unwrap_or_else(|| tmpdir.into_path()); + let profile_path = work_dir.join("profile.json"); + + // Specific AutoYaST handling let path = url.path(); - let output_file = if path.ends_with(".sh") { - "profile.sh" - } else if path.ends_with(".jsonnet") { - "profile.jsonnet" - } else { - "profile.json" - }; - let output_dir = dir.unwrap_or_else(|| tmpdir.into_path()); - let mut output_path = output_dir.join(output_file); - let output_fd = File::create(output_path.clone())?; if path.ends_with(".xml") || path.ends_with(".erb") || path.ends_with('/') { - // autoyast specific download and convert to json - AutoyastProfile::new(&url)?.read_into(output_fd)?; + // AutoYaST specific download and convert to JSON + AutoyastProfileImporter::read(&url)?.write_file(&profile_path)?; } else { - // just download profile - Transfer::get(&url_string, output_fd)?; + pre_process_profile(&url_string, &profile_path)?; } - // exec shell scripts - if output_file.ends_with(".sh") { - let err = Command::new("bash") - .args([output_path.to_str().context("Wrong path to shell script")?]) - .exec(); - eprintln!("Exec failed: {}", err); - } + validate(&profile_path)?; + store_settings(&profile_path).await?; - // evaluate jsonnet profiles - if output_file.ends_with(".jsonnet") { - let fd = File::create(output_dir.join("profile.json"))?; - let evaluator = ProfileEvaluator {}; - evaluator - .evaluate(&output_path, fd) - .context("Could not evaluate the profile".to_string())?; - output_path = output_dir.join("profile.json"); - } + Ok(()) +} - validate(&output_path)?; - store_settings(&output_path).await?; +// Preprocess the profile. +// +// The profile can be a JSON or a Jsonnet file or a script. +// +// * If it is a JSON file, no preprocessing is needed. +// * If it is a Jsonnet file, it is converted to JSON. +// * If it is a script, it is executed. +fn pre_process_profile>(url_string: &str, path: P) -> anyhow::Result<()> { + let work_dir = path.as_ref().parent().unwrap(); + let tmp_profile_path = work_dir.join("profile.temp"); + let tmp_file = File::create(&tmp_profile_path)?; + Transfer::get(url_string, tmp_file)?; + match FileFormat::from_file(&tmp_profile_path) { + FileFormat::Jsonnet => { + let file = File::create(path)?; + let evaluator = ProfileEvaluator {}; + evaluator + .evaluate(&tmp_profile_path, file) + .context("Could not evaluate the profile".to_string())?; + } + FileFormat::Script => { + let err = Command::new("bash").args([path.as_ref()]).exec(); + eprintln!("Exec failed: {}", err); + } + FileFormat::Json => { + std::fs::rename(&tmp_profile_path, path.as_ref())?; + } + _ => { + return Err(anyhow::Error::msg("Unsupported file format")); + } + } Ok(()) } @@ -168,8 +180,8 @@ async fn store_settings>(path: P) -> anyhow::Result<()> { fn autoyast(url_string: String) -> anyhow::Result<()> { let url = Url::parse(&url_string)?; - let reader = AutoyastProfile::new(&url)?; - reader.read_into(std::io::stdout())?; + let importer = AutoyastProfileImporter::read(&url)?; + importer.write(std::io::stdout())?; Ok(()) } diff --git a/rust/agama-lib/src/profile.rs b/rust/agama-lib/src/profile.rs index d05a91abd6..b1adffef3b 100644 --- a/rust/agama-lib/src/profile.rs +++ b/rust/agama-lib/src/profile.rs @@ -23,43 +23,49 @@ use anyhow::Context; use jsonschema::JSONSchema; use log::info; use serde_json; -use std::{fs, io::Write, path::Path, process::Command}; +use std::{ + fs::{self, File}, + io::Write, + path::Path, + process::Command, +}; use tempfile::{tempdir, TempDir}; use url::Url; /// Downloads and converts autoyast profile. -pub struct AutoyastProfile { - url: Url, +pub struct AutoyastProfileImporter { + content: String, } -impl AutoyastProfile { - pub fn new(url: &Url) -> anyhow::Result { - Ok(Self { url: url.clone() }) - } - - pub fn read_into(&self, mut out_fd: impl Write) -> anyhow::Result<()> { - let path = self.url.path(); - if path.ends_with(".xml") || path.ends_with(".erb") || path.ends_with('/') { - let content = self.read_from_autoyast()?; - out_fd.write_all(content.as_bytes())?; - Ok(()) - } else { - let msg = format!("Unsupported AutoYaST format at {}", self.url); - Err(anyhow::Error::msg(msg)) +impl AutoyastProfileImporter { + pub fn read(url: &Url) -> anyhow::Result { + let path = url.path(); + if !path.ends_with(".xml") && !path.ends_with(".erb") && !path.ends_with('/') { + let msg = format!("Unsupported AutoYaST format at {}", url); + return Err(anyhow::Error::msg(msg)); } - } - fn read_from_autoyast(&self) -> anyhow::Result { const TMP_DIR_PREFIX: &str = "autoyast"; const AUTOINST_JSON: &str = "autoinst.json"; let tmp_dir = TempDir::with_prefix(TMP_DIR_PREFIX)?; Command::new("agama-autoyast") - .args([self.url.as_str(), &tmp_dir.path().to_string_lossy()]) + .args([url.as_str(), &tmp_dir.path().to_string_lossy()]) .status()?; let autoinst_json = tmp_dir.path().join(AUTOINST_JSON); - Ok(fs::read_to_string(autoinst_json)?) + let content = fs::read_to_string(autoinst_json)?; + Ok(Self { content }) + } + + pub fn write(&self, mut file: impl Write) -> anyhow::Result<()> { + file.write_all(self.content.as_bytes())?; + Ok(()) + } + + pub fn write_file>(&self, path: P) -> anyhow::Result<()> { + let mut file = File::create(path)?; + self.write(&mut file) } } @@ -170,7 +176,7 @@ impl ProfileEvaluator { .args(["-json"]) .output() .context("Failed to run lshw")?; - let helpers = fs::read_to_string("agama.libsonnet") + let helpers = fs::read_to_string("share/agama.libsonnet") .or_else(|_| fs::read_to_string("/usr/share/agama-cli/agama.libsonnet")) .context("Failed to read agama.libsonnet")?; let mut file = fs::File::create(path)?; diff --git a/rust/agama-lib/src/utils/file_format.rs b/rust/agama-lib/src/utils/file_format.rs index 174e2ea7c7..f470b2584a 100644 --- a/rust/agama-lib/src/utils/file_format.rs +++ b/rust/agama-lib/src/utils/file_format.rs @@ -24,10 +24,11 @@ use std::{ io::Write, + path::Path, process::{Command, Stdio}, }; -/// File formats +/// Relevant file formats for Agama. #[derive(Debug, PartialEq, Copy, Clone)] pub enum FileFormat { Json, @@ -36,16 +37,18 @@ pub enum FileFormat { Unknown, } -const JSONNETFMT_PATH: &str = "/usr/bin/jsonnetfmt"; +const JSONNETFMT_BIN: &str = "jsonnetfmt"; impl FileFormat { - pub fn from_file(file_path: &str) -> Self { + /// Tries to guess the file format from the content of a file. + pub fn from_file>(file_path: P) -> Self { let Ok(content) = std::fs::read_to_string(file_path) else { return Self::Unknown; }; Self::from_string(&content) } + /// Tries to guess the file format from a string. pub fn from_string(content: &str) -> Self { if Self::is_json(content) { return Self::Json; @@ -58,15 +61,23 @@ impl FileFormat { Self::Unknown } + /// Whether the format is JSON. + /// + /// It tries to parse the content as JSON and returns `true` if it succeeds. fn is_json(content: &str) -> bool { let json = serde_json::from_str::(content); json.is_ok() } + /// Whether the format is Jsonnet. + /// + /// It tries to process the content with the jsonnetfmt tool and returns `true` if it succeeds. fn is_jsonnet(content: &str) -> bool { - let child = Command::new(JSONNETFMT_PATH) + let child = Command::new(JSONNETFMT_BIN) .args(["-"]) .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .spawn(); let Ok(mut child) = child else { @@ -85,6 +96,9 @@ impl FileFormat { child.wait().is_ok_and(|s| s.success()) } + /// Whether is is a script. + /// + /// It returns `true` if the content starts with a shebang. fn is_script(content: &str) -> bool { content.starts_with("#!") } From 4532fd1fd150221f311e2e05f3d180db6fae9632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 12 Dec 2024 16:43:41 +0000 Subject: [PATCH 04/11] doc(rust): update changes file --- rust/package/agama.changes | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 4024e507a9..e9b252f690 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,12 @@ +------------------------------------------------------------------- +Thu Dec 12 16:42:18 UTC 2024 - Imobach Gonzalez Sosa + +- Fix profile URL handling (bsc#1234362): + - Follow redirections. + - Determine the file format from the content instead of the + extension. It does not apply to AutoYaST profiles, where it still + uses the extension in the URL for backward compatibility. + ------------------------------------------------------------------- Tue Dec 3 12:58:48 UTC 2024 - Imobach Gonzalez Sosa From b00e1b1f3b7becdac98e482fa4acac7bd3e9e8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 12 Dec 2024 17:01:58 +0000 Subject: [PATCH 05/11] fix(ci): add Jsonnet for Rust tests --- .github/workflows/ci-rust.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-rust.yml b/.github/workflows/ci-rust.yml index 8b053d6238..138de166bf 100644 --- a/.github/workflows/ci-rust.yml +++ b/.github/workflows/ci-rust.yml @@ -71,6 +71,7 @@ jobs: run: zypper --non-interactive install clang-devel dbus-1-daemon + golang-github-google-jsonnet jq libopenssl-3-devel openssl-3 From e55074f76b521cfa691e4769a6b30f21e2dac99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 13 Dec 2024 06:44:39 +0000 Subject: [PATCH 06/11] fix(rust): FileFormat reports read_to_string problems --- rust/agama-cli/src/profile.rs | 2 +- rust/agama-lib/src/utils/file_format.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/rust/agama-cli/src/profile.rs b/rust/agama-cli/src/profile.rs index b1d95e1794..57951faf5b 100644 --- a/rust/agama-cli/src/profile.rs +++ b/rust/agama-cli/src/profile.rs @@ -149,7 +149,7 @@ fn pre_process_profile>(url_string: &str, path: P) -> anyhow::Res let tmp_file = File::create(&tmp_profile_path)?; Transfer::get(url_string, tmp_file)?; - match FileFormat::from_file(&tmp_profile_path) { + match FileFormat::from_file(&tmp_profile_path)? { FileFormat::Jsonnet => { let file = File::create(path)?; let evaluator = ProfileEvaluator {}; diff --git a/rust/agama-lib/src/utils/file_format.rs b/rust/agama-lib/src/utils/file_format.rs index f470b2584a..6c6892062e 100644 --- a/rust/agama-lib/src/utils/file_format.rs +++ b/rust/agama-lib/src/utils/file_format.rs @@ -41,11 +41,9 @@ const JSONNETFMT_BIN: &str = "jsonnetfmt"; impl FileFormat { /// Tries to guess the file format from the content of a file. - pub fn from_file>(file_path: P) -> Self { - let Ok(content) = std::fs::read_to_string(file_path) else { - return Self::Unknown; - }; - Self::from_string(&content) + pub fn from_file>(file_path: P) -> Result { + let content = std::fs::read_to_string(file_path)?; + Ok(Self::from_string(&content)) } /// Tries to guess the file format from a string. From 46ceda369368c8847a518d16ed48c5e892cb5691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 13 Dec 2024 06:45:13 +0000 Subject: [PATCH 07/11] fix(rust): Transfer::get reports fails on errors --- rust/agama-lib/src/utils/transfer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/agama-lib/src/utils/transfer.rs b/rust/agama-lib/src/utils/transfer.rs index 29b92502c7..a9c9475387 100644 --- a/rust/agama-lib/src/utils/transfer.rs +++ b/rust/agama-lib/src/utils/transfer.rs @@ -48,6 +48,7 @@ impl Transfer { pub fn get(url: &str, mut out_fd: impl Write) -> TransferResult<()> { let mut handle = Easy::new(); handle.follow_location(true)?; + handle.fail_on_error(true)?; handle.url(url)?; let mut transfer = handle.transfer(); From ceaff899e1d64ad372a60a5397f6fff14e86dd45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 13 Dec 2024 06:53:51 +0000 Subject: [PATCH 08/11] fix(rust): fix shell handling in "profile import" --- rust/agama-cli/src/profile.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/agama-cli/src/profile.rs b/rust/agama-cli/src/profile.rs index 57951faf5b..0251a2c38b 100644 --- a/rust/agama-cli/src/profile.rs +++ b/rust/agama-cli/src/profile.rs @@ -158,7 +158,7 @@ fn pre_process_profile>(url_string: &str, path: P) -> anyhow::Res .context("Could not evaluate the profile".to_string())?; } FileFormat::Script => { - let err = Command::new("bash").args([path.as_ref()]).exec(); + let err = Command::new("bash").args([&tmp_profile_path]).exec(); eprintln!("Exec failed: {}", err); } FileFormat::Json => { From 5aeaf53e4d591797b654c1cb1752d4f7a2c560df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 16 Dec 2024 10:37:12 +0000 Subject: [PATCH 09/11] feat(rust): allow using any script in agama.auto --- rust/agama-cli/src/profile.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/agama-cli/src/profile.rs b/rust/agama-cli/src/profile.rs index 0251a2c38b..9f2cbcce0d 100644 --- a/rust/agama-cli/src/profile.rs +++ b/rust/agama-cli/src/profile.rs @@ -30,7 +30,7 @@ use agama_lib::{ use anyhow::Context; use clap::Subcommand; use console::style; -use std::os::unix::process::CommandExt; +use std::os::unix::{fs::PermissionsExt, process::CommandExt}; use std::{ fs::File, io::stdout, @@ -158,7 +158,10 @@ fn pre_process_profile>(url_string: &str, path: P) -> anyhow::Res .context("Could not evaluate the profile".to_string())?; } FileFormat::Script => { - let err = Command::new("bash").args([&tmp_profile_path]).exec(); + let mut perms = std::fs::metadata(&tmp_profile_path)?.permissions(); + perms.set_mode(0o750); + std::fs::set_permissions(&tmp_profile_path, perms)?; + let err = Command::new(&tmp_profile_path).exec(); eprintln!("Exec failed: {}", err); } FileFormat::Json => { From ecbfb32d2b67fa98fc05fe6ece8d8e52414cccf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 16 Dec 2024 11:36:05 +0000 Subject: [PATCH 10/11] fix(rust): improve detection of jsonnet files --- rust/agama-lib/src/utils/file_format.rs | 29 +++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/rust/agama-lib/src/utils/file_format.rs b/rust/agama-lib/src/utils/file_format.rs index 6c6892062e..258796fb1c 100644 --- a/rust/agama-lib/src/utils/file_format.rs +++ b/rust/agama-lib/src/utils/file_format.rs @@ -50,10 +50,10 @@ impl FileFormat { pub fn from_string(content: &str) -> Self { if Self::is_json(content) { return Self::Json; - } else if Self::is_jsonnet(content) { - return Self::Jsonnet; } else if Self::is_script(content) { return Self::Script; + } else if Self::is_jsonnet(content) { + return Self::Jsonnet; } Self::Unknown @@ -98,7 +98,10 @@ impl FileFormat { /// /// It returns `true` if the content starts with a shebang. fn is_script(content: &str) -> bool { - content.starts_with("#!") + let Some(first_line) = content.lines().next() else { + return false; + }; + first_line.starts_with("#!") && !first_line.contains("jsonnet") } } @@ -111,7 +114,6 @@ mod tests { let content = r#" { "name:": "value"} "#; - assert_eq!(FileFormat::from_string(content), FileFormat::Json); } @@ -120,7 +122,11 @@ mod tests { let content = r#" { name: "value" } "#; + assert_eq!(FileFormat::from_string(content), FileFormat::Jsonnet); + let content = r#"#!/usr/bin/jsonnet + print + "#; assert_eq!(FileFormat::from_string(content), FileFormat::Jsonnet); } @@ -129,7 +135,22 @@ mod tests { let content = r#"#!/bin/bash echo "Hello World" "#; + assert_eq!(FileFormat::from_string(content), FileFormat::Script); + let content = r#"#!/usr/bin/python3 + print + "#; assert_eq!(FileFormat::from_string(content), FileFormat::Script); } + + #[test] + fn test_unknown() { + let empty = ""; + assert_eq!(FileFormat::from_string(empty), FileFormat::Unknown); + + let text = r#" + Some text content. + "#; + assert_eq!(FileFormat::from_string(text), FileFormat::Unknown); + } } From cb8a56e6e5bf1e357a9092d136a3314868d01c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 16 Dec 2024 12:28:32 +0000 Subject: [PATCH 11/11] doc(rust): explain why 'jsonnet' is not handle as a script --- rust/agama-lib/src/utils/file_format.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/agama-lib/src/utils/file_format.rs b/rust/agama-lib/src/utils/file_format.rs index 258796fb1c..48c857e3c3 100644 --- a/rust/agama-lib/src/utils/file_format.rs +++ b/rust/agama-lib/src/utils/file_format.rs @@ -96,7 +96,9 @@ impl FileFormat { /// Whether is is a script. /// - /// It returns `true` if the content starts with a shebang. + /// It returns `true` if the content starts with a shebang. However, it excludes the + /// case of a "jsonnet" script because it needs special handling: injecting the hardware + /// information and processing its output. fn is_script(content: &str) -> bool { let Some(first_line) = content.lines().next() else { return false;