diff --git a/rust/agama-cli/src/config.rs b/rust/agama-cli/src/config.rs index d1f11fd100..6a0cf65bc8 100644 --- a/rust/agama-cli/src/config.rs +++ b/rust/agama-cli/src/config.rs @@ -76,7 +76,11 @@ pub async fn run(http_client: BaseHTTPClient, subcommand: ConfigCommands) -> any let mut contents = String::new(); stdin.read_to_string(&mut contents)?; let result: InstallSettings = serde_json::from_str(&contents)?; - Ok(store.store(&result).await?) + tokio::spawn(async move { + show_progress().await.unwrap(); + }); + store.store(&result).await?; + Ok(()) } ConfigCommands::Edit { editor } => { let model = store.load().await?; diff --git a/rust/agama-cli/src/lib.rs b/rust/agama-cli/src/lib.rs index af02837a51..f4fa82312d 100644 --- a/rust/agama-cli/src/lib.rs +++ b/rust/agama-cli/src/lib.rs @@ -133,7 +133,7 @@ async fn show_progress() -> Result<(), ServiceError> { // wait 1 second to give other task chance to start, so progress can display something tokio::time::sleep(Duration::from_secs(1)).await; let conn = agama_lib::connection().await?; - let mut monitor = ProgressMonitor::new(conn).await.unwrap(); + let mut monitor = ProgressMonitor::new(conn).await?; let presenter = InstallerProgress::new(); monitor .run(presenter) diff --git a/rust/agama-cli/src/profile.rs b/rust/agama-cli/src/profile.rs index b6950f7dfd..abe13305f8 100644 --- a/rust/agama-cli/src/profile.rs +++ b/rust/agama-cli/src/profile.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 crate::show_progress; use agama_lib::{ base_http_client::BaseHTTPClient, install_settings::InstallSettings, @@ -27,6 +28,7 @@ use agama_lib::{ }; use anyhow::Context; use clap::Subcommand; +use console::style; use std::os::unix::process::CommandExt; use std::{ fs::File, @@ -80,18 +82,25 @@ pub enum ProfileCommands { fn validate(path: &PathBuf) -> anyhow::Result<()> { let validator = ProfileValidator::default_schema()?; - // let path = Path::new(&path); let result = validator .validate_file(path) .context(format!("Could not validate the profile {:?}", path))?; match result { ValidationResult::Valid => { - println!("The profile is valid") + println!( + "{} {}", + style("\u{2713}").bold().green(), + "The profile is valid." + ); } ValidationResult::NotValid(errors) => { - eprintln!("The profile is not valid. Please, check the following errors:\n"); + eprintln!( + "{} {}", + style("\u{2717}").bold().red(), + "The profile is not valid. Please, check the following errors:\n" + ); for error in errors { - println!("* {error}") + println!("\t* {error}") } } } @@ -107,6 +116,9 @@ fn evaluate(path: &Path) -> anyhow::Result<()> { } 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 path = url.path(); diff --git a/rust/agama-lib/src/manager.rs b/rust/agama-lib/src/manager.rs index cd05634be8..d59e79c1f7 100644 --- a/rust/agama-lib/src/manager.rs +++ b/rust/agama-lib/src/manager.rs @@ -22,6 +22,7 @@ pub mod http_client; pub use http_client::ManagerHTTPClient; +use serde::{Deserialize, Serialize}; use crate::error::ServiceError; use crate::proxies::ServiceStatusProxy; @@ -29,7 +30,7 @@ use crate::{ progress::Progress, proxies::{Manager1Proxy, ProgressProxy}, }; -use serde_repr::Serialize_repr; +use serde_repr::{Deserialize_repr, Serialize_repr}; use tokio_stream::StreamExt; use zbus::Connection; @@ -41,9 +42,23 @@ pub struct ManagerClient<'a> { status_proxy: ServiceStatusProxy<'a>, } +/// Holds information about the manager's status. +#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct InstallerStatus { + /// Current installation phase. + pub phase: InstallationPhase, + /// Whether the service is busy. + pub is_busy: bool, + /// Whether Agama is running on Iguana. + pub use_iguana: bool, + /// Whether it is possible to start the installation. + pub can_install: bool, +} + /// Represents the installation phase. /// NOTE: does this conversion have any value? -#[derive(Clone, Copy, Debug, PartialEq, Serialize_repr, utoipa::ToSchema)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize_repr, Deserialize_repr, utoipa::ToSchema)] #[repr(u32)] pub enum InstallationPhase { /// Start up phase. diff --git a/rust/agama-lib/src/manager/http_client.rs b/rust/agama-lib/src/manager/http_client.rs index 30245bc954..c994f0500f 100644 --- a/rust/agama-lib/src/manager/http_client.rs +++ b/rust/agama-lib/src/manager/http_client.rs @@ -18,8 +18,10 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use crate::logs::LogsLists; -use crate::{base_http_client::BaseHTTPClient, error::ServiceError}; +use crate::{ + base_http_client::BaseHTTPClient, error::ServiceError, logs::LogsLists, + manager::InstallerStatus, +}; use reqwest::header::CONTENT_ENCODING; use std::io::Cursor; use std::path::{Path, PathBuf}; @@ -33,6 +35,7 @@ impl ManagerHTTPClient { Self { client: base } } + /// Starts a "probing". pub async fn probe(&self) -> Result<(), ServiceError> { // BaseHTTPClient did not anticipate POST without request body // so we pass () which is rendered as `null` @@ -82,4 +85,11 @@ impl ManagerHTTPClient { pub async fn list(&self) -> Result { Ok(self.client.get("/manager/logs/list").await?) } + + /// Returns the installer status. + pub async fn status(&self) -> Result { + self.client + .get::("/manager/installer") + .await + } } diff --git a/rust/agama-lib/src/scripts.rs b/rust/agama-lib/src/scripts.rs index 2636b6f61c..b9f6f23736 100644 --- a/rust/agama-lib/src/scripts.rs +++ b/rust/agama-lib/src/scripts.rs @@ -26,6 +26,7 @@ mod model; mod settings; mod store; +pub use client::ScriptsClient; pub use error::ScriptError; pub use model::*; pub use settings::*; diff --git a/rust/agama-lib/src/store.rs b/rust/agama-lib/src/store.rs index e9a7fb4488..ef2c24dbea 100644 --- a/rust/agama-lib/src/store.rs +++ b/rust/agama-lib/src/store.rs @@ -24,6 +24,8 @@ use crate::base_http_client::BaseHTTPClient; use crate::error::ServiceError; use crate::install_settings::InstallSettings; +use crate::manager::{InstallationPhase, ManagerHTTPClient}; +use crate::scripts::{ScriptsClient, ScriptsGroup}; use crate::{ localization::LocalizationStore, network::NetworkStore, product::ProductStore, scripts::ScriptsStore, software::SoftwareStore, storage::StorageStore, users::UsersStore, @@ -43,6 +45,8 @@ pub struct Store { storage: StorageStore, localization: LocalizationStore, scripts: ScriptsStore, + manager_client: ManagerHTTPClient, + http_client: BaseHTTPClient, } impl Store { @@ -54,7 +58,9 @@ impl Store { product: ProductStore::new(http_client.clone())?, software: SoftwareStore::new(http_client.clone())?, storage: StorageStore::new(http_client.clone())?, - scripts: ScriptsStore::new(http_client), + scripts: ScriptsStore::new(http_client.clone()), + manager_client: ManagerHTTPClient::new(http_client.clone()), + http_client, }) } @@ -79,13 +85,24 @@ impl Store { } /// Stores the given installation settings in the D-Bus service + /// + /// As part of the process it runs pre-scripts and forces a probe if the installation phase is + /// "config". It causes the storage proposal to be reset. This behavior should be revisited in + /// the future but it might be the storage service the responsible for dealing with this. + /// + /// * `settings`: installation settings. pub async fn store(&self, settings: &InstallSettings) -> Result<(), ServiceError> { - if let Some(network) = &settings.network { - self.network.store(network).await?; - } if let Some(scripts) = &settings.scripts { self.scripts.store(scripts).await?; } + + if settings.scripts.as_ref().is_some_and(|s| !s.pre.is_empty()) { + self.run_pre_scripts().await?; + } + + if let Some(network) = &settings.network { + self.network.store(network).await?; + } // order is important here as network can be critical for connection // to registration server and selecting product is important for rest if let Some(product) = &settings.product { @@ -107,4 +124,16 @@ impl Store { Ok(()) } + + /// Runs the pre-installation scripts and forces a probe if the installation phase is "config". + async fn run_pre_scripts(&self) -> Result<(), ServiceError> { + let scripts_client = ScriptsClient::new(self.http_client.clone()); + scripts_client.run_scripts(ScriptsGroup::Pre).await?; + + let status = self.manager_client.status().await; + if status.is_ok_and(|s| s.phase == InstallationPhase::Config) { + self.manager_client.probe().await?; + } + Ok(()) + } } diff --git a/rust/agama-server/src/manager/web.rs b/rust/agama-server/src/manager/web.rs index 8f59b0adee..052336bfb7 100644 --- a/rust/agama-server/src/manager/web.rs +++ b/rust/agama-server/src/manager/web.rs @@ -25,10 +25,10 @@ //! * `manager_service` which returns the Axum service. //! * `manager_stream` which offers an stream that emits the manager events coming from D-Bus. -use agama_lib::logs::{list as list_logs, store as store_logs, LogsLists, DEFAULT_COMPRESSION}; use agama_lib::{ error::ServiceError, - manager::{InstallationPhase, ManagerClient}, + logs::{list as list_logs, store as store_logs, LogsLists, DEFAULT_COMPRESSION}, + manager::{InstallationPhase, InstallerStatus, ManagerClient}, proxies::Manager1Proxy, }; use axum::{ @@ -39,7 +39,6 @@ use axum::{ routing::{get, post}, Json, Router, }; -use serde::Serialize; use std::pin::Pin; use tokio_stream::{Stream, StreamExt}; use tokio_util::io::ReaderStream; @@ -58,20 +57,6 @@ pub struct ManagerState<'a> { manager: ManagerClient<'a>, } -/// Holds information about the manager's status. -#[derive(Clone, Serialize, utoipa::ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct InstallerStatus { - /// Current installation phase. - phase: InstallationPhase, - /// Whether the service is busy. - is_busy: bool, - /// Whether Agama is running on Iguana. - use_iguana: bool, - /// Whether it is possible to start the installation. - can_install: bool, -} - /// Returns a stream that emits manager related events coming from D-Bus. /// /// It emits the Event::InstallationPhaseChanged event. diff --git a/rust/agama-server/src/web/docs/manager.rs b/rust/agama-server/src/web/docs/manager.rs index 7df91f2803..089107827f 100644 --- a/rust/agama-server/src/web/docs/manager.rs +++ b/rust/agama-server/src/web/docs/manager.rs @@ -21,7 +21,7 @@ impl ApiDocBuilder for ManagerApiDocBuilder { fn components(&self) -> utoipa::openapi::Components { ComponentsBuilder::new() .schema_from::() - .schema_from::() + .schema_from::() .build() } } diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 31591bc2a9..9c13557a3a 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Nov 7 14:20:48 UTC 2024 - Imobach Gonzalez Sosa + +- Perform a system re-probing after executing pre-scripts + (gh#agama-project/agama#1735). + ------------------------------------------------------------------- Mon Nov 4 07:51:55 UTC 2024 - Michal Filka