diff --git a/rust/agama-lib/src/http/base_http_client.rs b/rust/agama-lib/src/http/base_http_client.rs index 1ceb64fa03..7958e0f866 100644 --- a/rust/agama-lib/src/http/base_http_client.rs +++ b/rust/agama-lib/src/http/base_http_client.rs @@ -34,10 +34,25 @@ pub enum BaseHTTPClientError { InvalidURL(#[from] url::ParseError), #[error(transparent)] InvalidJSON(#[from] serde_json::Error), - #[error("Backend call failed with status {0} and text '{1}'")] + #[error("Backend responded with code {} and the following message:\n\n{}", .0, format_backend_error(.1))] BackendError(u16, String), } +fn format_backend_error(error: &String) -> String { + let message: Result = serde_json::from_str(error); + + match message { + Ok(message) => { + if let Some(error) = message.get("error") { + error.to_string().replace("\\n", "\n") + } else { + format!("{:?}", error) + } + } + Err(_) => format!("{:?}", error), + } +} + /// Base that all HTTP clients should use. /// /// It provides several features including automatic base URL switching, diff --git a/rust/agama-lib/src/profile.rs b/rust/agama-lib/src/profile.rs index 5715f831c8..b53c05bf26 100644 --- a/rust/agama-lib/src/profile.rs +++ b/rust/agama-lib/src/profile.rs @@ -37,35 +37,49 @@ pub use http_client::ProfileHTTPClient; pub const DEFAULT_SCHEMA_DIR: &str = "/usr/share/agama/schema"; pub const DEFAULT_JSONNET_DIR: &str = "/usr/share/agama/jsonnet"; +#[derive(thiserror::Error, Debug)] +pub enum AutoyastError { + #[error("I/O error: {0}")] + IO(#[from] std::io::Error), + #[error("Failed to run the agama-autoyast script: {0}")] + Execute(#[source] std::io::Error), + #[error("Failed to convert the AutoYaST profile: {0}")] + Evaluation(String), + #[error("Unsupported AutoYaST format at {0}")] + UnsupportedFormat(Url), +} + /// Downloads and converts autoyast profile. pub struct AutoyastProfileImporter { pub content: String, } impl AutoyastProfileImporter { - pub async fn read(url: &Url) -> anyhow::Result { + pub async fn read(url: &Url) -> 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)); + return Err(AutoyastError::UnsupportedFormat(url.clone())); } const TMP_DIR_PREFIX: &str = "autoyast"; const AUTOINST_JSON: &str = "autoinst.json"; let tmp_dir = TempDir::with_prefix(TMP_DIR_PREFIX)?; - tokio::process::Command::new("agama-autoyast") + let result = tokio::process::Command::new("agama-autoyast") .env("YAST_SKIP_PROFILE_FETCH_ERROR", "1") .args([url.as_str(), &tmp_dir.path().to_string_lossy()]) - .status() + .output() .await - .context("Failed to run agama-autoyast")?; + .map_err(AutoyastError::Execute)?; + + if !result.status.success() { + return Err(AutoyastError::Evaluation( + String::from_utf8_lossy(&result.stderr).to_string(), + )); + } let autoinst_json = tmp_dir.path().join(AUTOINST_JSON); - let content = fs::read_to_string(&autoinst_json).context(format!( - "agama-autoyast did not produce {:?}", - autoinst_json - ))?; + let content = fs::read_to_string(&autoinst_json)?; Ok(Self { content }) } } diff --git a/rust/agama-lib/src/profile/http_client.rs b/rust/agama-lib/src/profile/http_client.rs index be05f1d464..fe21b5e505 100644 --- a/rust/agama-lib/src/profile/http_client.rs +++ b/rust/agama-lib/src/profile/http_client.rs @@ -18,7 +18,7 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use crate::http::BaseHTTPClient; +use crate::http::{BaseHTTPClient, BaseHTTPClientError}; use crate::profile::ValidationOutcome; use fluent_uri::Uri; use serde::Serialize; @@ -51,7 +51,7 @@ impl ProfileHTTPClient { /// Note that this client does not act on this *url*, it passes it as a parameter /// to our web backend. /// Return well-formed Agama JSON on success. - pub async fn from_autoyast(&self, url: &Uri) -> anyhow::Result { + pub async fn from_autoyast(&self, url: &Uri) -> Result { let mut map = HashMap::new(); map.insert(String::from("url"), url.to_string()); diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 9e57f921c6..69e6acbc72 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -29,12 +29,10 @@ use agama_utils::{ self, event, manager::{self, LicenseContent}, status::Stage, - Action, Config, Event, FinishMethod, Issue, IssueMap, Proposal, Scope, Status, SystemInfo, + Action, Config, Event, Issue, IssueMap, Proposal, Scope, Status, SystemInfo, }, arch::Arch, - issue, - kernel_cmdline::KernelCmdline, - licenses, + issue, licenses, products::{self, ProductSpec}, progress, question, }; @@ -42,7 +40,7 @@ use async_trait::async_trait; use merge::Merge; use network::NetworkSystemClient; use serde_json::Value; -use std::{collections::HashMap, str::FromStr, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; use tokio::sync::{broadcast, RwLock}; #[derive(Debug, thiserror::Error)] diff --git a/rust/agama-manager/src/tasks/runner.rs b/rust/agama-manager/src/tasks/runner.rs index ecaa8b705f..e871c00161 100644 --- a/rust/agama-manager/src/tasks/runner.rs +++ b/rust/agama-manager/src/tasks/runner.rs @@ -18,8 +18,6 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use std::str::FromStr; - use crate::{ actions::{FinishAction, InstallAction, SetConfigAction}, bootloader, files, hostname, iscsi, l10n, proxy, s390, security, service, software, storage, @@ -30,9 +28,7 @@ use agama_network::NetworkSystemClient; use agama_utils::{ actor::{Actor, Handler, MessageHandler}, api::{FinishMethod, Scope}, - issue, - kernel_cmdline::KernelCmdline, - progress, question, + issue, progress, question, }; use async_trait::async_trait; diff --git a/rust/agama-server/src/profile/web.rs b/rust/agama-server/src/profile/web.rs index 219839b045..40e6f728ed 100644 --- a/rust/agama-server/src/profile/web.rs +++ b/rust/agama-server/src/profile/web.rs @@ -18,6 +18,7 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. +use agama_lib::profile::AutoyastError; use agama_transfer::Transfer; use anyhow::Context; @@ -50,6 +51,19 @@ impl std::fmt::Display for ProfileServiceError { } } +impl From for ProfileServiceError { + fn from(e: AutoyastError) -> Self { + let http_status = match e { + AutoyastError::Execute(..) => StatusCode::INTERNAL_SERVER_ERROR, + _ => StatusCode::BAD_REQUEST, + }; + Self { + source: e.into(), + http_status, + } + } +} + // Make a 400 response // ``` // let r: Result = foo(); @@ -208,15 +222,6 @@ async fn autoyast(body: String) -> Result { } let url = Url::parse(profile.url.as_ref().unwrap()).map_err(anyhow::Error::new)?; - let importer_res = AutoyastProfileImporter::read(&url).await; - match importer_res { - Ok(importer) => Ok(importer.content), - Err(error) => { - // anyhow can be only displayed, not so nice - if format!("{}", error).contains("Failed to run") { - return Err(make_internal(error)); - } - Err(error.into()) - } - } + let importer = AutoyastProfileImporter::read(&url).await?; + Ok(importer.content) } diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 6e47d5f222..1518a1c774 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Mar 12 13:20:48 UTC 2026 - Imobach Gonzalez Sosa + +- Add error reporting when working with AutoYaST profiles (related + to bsc#1259434). + ------------------------------------------------------------------- Thu Mar 12 13:05:51 UTC 2026 - Ladislav Slezák diff --git a/service/bin/agama-autoyast b/service/bin/agama-autoyast index a28e5857ae..df50472b10 100755 --- a/service/bin/agama-autoyast +++ b/service/bin/agama-autoyast @@ -51,10 +51,12 @@ begin warn "Did not convert the profile (canceled by the user)." exit 2 end -rescue Agama::Commands::CouldNotFetchProfile - warn "Could not fetch the AutoYaST profile." +rescue Agama::Commands::CouldNotFetchProfile => e + warn "Could not fetch the AutoYaST profile:\n\n" + warn e.full_message exit 3 -rescue Agama::Commands::CouldNotWriteAgamaConfig - warn "Could not write the Agama configuration." +rescue Agama::Commands::CouldNotWriteAgamaConfig => e + warn "Could not write the Agama configuration:\n\n" + warn e.full_message exit 4 end diff --git a/service/lib/agama/autoyast/profile_reporter.rb b/service/lib/agama/autoyast/profile_reporter.rb index 24af3cbef6..52b67e7d8b 100644 --- a/service/lib/agama/autoyast/profile_reporter.rb +++ b/service/lib/agama/autoyast/profile_reporter.rb @@ -62,7 +62,7 @@ def report(elements) ) questions_client.ask(question) do |answer| - answer == :continue + answer.action == :continue end end diff --git a/service/lib/agama/autoyast/root_reader.rb b/service/lib/agama/autoyast/root_reader.rb index b93d4d5015..ecdb8b78c5 100755 --- a/service/lib/agama/autoyast/root_reader.rb +++ b/service/lib/agama/autoyast/root_reader.rb @@ -38,11 +38,19 @@ def read root_user = config.users.find { |u| u.name == "root" } return {} unless root_user - hsh = { "password" => root_user.password.value.to_s } - hsh["hashedPassword"] = true if root_user.password.value.encrypted? + hsh = {} + password = root_user.password + + if password + hsh["password"] = password.value.to_s + hsh["hashedPassword"] = true if password.value.encrypted? + end public_key = root_user.authorized_keys.first hsh["sshPublicKey"] = public_key if public_key + + return {} if hsh.empty? + { "root" => hsh } end diff --git a/service/lib/agama/autoyast/user_reader.rb b/service/lib/agama/autoyast/user_reader.rb index ca0f8417a6..37c3ea0c7d 100755 --- a/service/lib/agama/autoyast/user_reader.rb +++ b/service/lib/agama/autoyast/user_reader.rb @@ -39,11 +39,14 @@ def read hsh = { "userName" => user.name, - "fullName" => user.gecos.first.to_s, - "password" => user.password.value.to_s + "fullName" => user.gecos.first.to_s } - hsh["hashedPassword"] = true if user.password.value.encrypted? + password = user.password + if password + hsh["password"] = password.value.to_s + hsh["hashedPassword"] = true if password.value.encrypted? + end { "user" => hsh } end diff --git a/service/package/rubygem-agama-yast.changes b/service/package/rubygem-agama-yast.changes index fdd22e3190..de73603e95 100644 --- a/service/package/rubygem-agama-yast.changes +++ b/service/package/rubygem-agama-yast.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Thu Mar 12 13:03:36 UTC 2026 - Imobach Gonzalez Sosa + +- Add errors to the output of agama-autoyast (related to bsc#1259434). + ------------------------------------------------------------------- Thu Mar 12 08:35:23 UTC 2026 - Ancor Gonzalez Sosa diff --git a/service/test/agama/autoyast/root_reader_test.rb b/service/test/agama/autoyast/root_reader_test.rb index 7e916306ff..0fd156a726 100644 --- a/service/test/agama/autoyast/root_reader_test.rb +++ b/service/test/agama/autoyast/root_reader_test.rb @@ -74,6 +74,20 @@ "password" => "123456", "sshPublicKey" => "ssh-key 1" ) end + + context "but does not contain password or SSH public key" do + let(:root) do + { + "username" => "root", + "user_password" => nil, + "encrypted" => nil + } + end + + it "returns an empty hash" do + expect(subject.read).to be_empty + end + end end end end diff --git a/service/test/agama/autoyast/user_reader_test.rb b/service/test/agama/autoyast/user_reader_test.rb index 286a67266a..a3573c7618 100644 --- a/service/test/agama/autoyast/user_reader_test.rb +++ b/service/test/agama/autoyast/user_reader_test.rb @@ -77,5 +77,24 @@ ) end end + + context "when the password is nil" do + let(:user) do + { + "username" => "suse", + "fullname" => "SUSE", + "user_password" => nil, + "encrypted" => nil + } + end + + it "does not include password information" do + user = subject.read["user"] + expect(user).to eq( + "userName" => "suse", + "fullName" => "SUSE" + ) + end + end end end