Skip to content

Commit

Permalink
feat(rust): probe after running pre-scripts (agama-project#1735)
Browse files Browse the repository at this point in the history
## Problem

The typical use cases for pre-scripts are:

* Modifying the profile.
* Extending the installation media (adding some repository).
* Activating some hardware.

About the first use case, customers coming from AutoYaST can still use
their old profiles because the pre-scripts will continue to work. For
Agama, we could find a better way to modify the profile in place.

However, about the other use cases, we might need the scripts to run
before system probing. The problem is that, in the single-product
scenario, Agama performs the probing from the very beginning. It does
not wait for a profile or the UI to start.

## Solution

Run the system probing again after running pre-scripts only if the
system is already in the `config` phase.

## Drawback

As a side effect, importing a profile containing a pre-script causes the
storage proposal to be lost. However, that's not a problem if the
profile already contains a storage section, so it is kind of a corner
case.

Moreover, the storage service should be responsible for keeping the
proposal, which is out of this PR's scope.

## Testing

- *Tested manually*
  • Loading branch information
imobachgs authored Nov 7, 2024
2 parents 788c34b + c6e74e8 commit 976f695
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 32 deletions.
6 changes: 5 additions & 1 deletion rust/agama-cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 16 additions & 4 deletions rust/agama-cli/src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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}")
}
}
}
Expand All @@ -107,6 +116,9 @@ fn evaluate(path: &Path) -> anyhow::Result<()> {
}

async fn import(url_string: String, dir: Option<PathBuf>) -> 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();
Expand Down
19 changes: 17 additions & 2 deletions rust/agama-lib/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
pub mod http_client;
pub use http_client::ManagerHTTPClient;
use serde::{Deserialize, Serialize};

use crate::error::ServiceError;
use crate::proxies::ServiceStatusProxy;
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;

Expand All @@ -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.
Expand Down
14 changes: 12 additions & 2 deletions rust/agama-lib/src/manager/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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`
Expand Down Expand Up @@ -82,4 +85,11 @@ impl ManagerHTTPClient {
pub async fn list(&self) -> Result<LogsLists, ServiceError> {
Ok(self.client.get("/manager/logs/list").await?)
}

/// Returns the installer status.
pub async fn status(&self) -> Result<InstallerStatus, ServiceError> {
self.client
.get::<InstallerStatus>("/manager/installer")
.await
}
}
1 change: 1 addition & 0 deletions rust/agama-lib/src/scripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod model;
mod settings;
mod store;

pub use client::ScriptsClient;
pub use error::ScriptError;
pub use model::*;
pub use settings::*;
Expand Down
37 changes: 33 additions & 4 deletions rust/agama-lib/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -43,6 +45,8 @@ pub struct Store {
storage: StorageStore,
localization: LocalizationStore,
scripts: ScriptsStore,
manager_client: ManagerHTTPClient,
http_client: BaseHTTPClient,
}

impl Store {
Expand All @@ -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,
})
}

Expand All @@ -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 {
Expand All @@ -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(())
}
}
19 changes: 2 additions & 17 deletions rust/agama-server/src/manager/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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;
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-server/src/web/docs/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl ApiDocBuilder for ManagerApiDocBuilder {
fn components(&self) -> utoipa::openapi::Components {
ComponentsBuilder::new()
.schema_from::<agama_lib::manager::InstallationPhase>()
.schema_from::<crate::manager::web::InstallerStatus>()
.schema_from::<agama_lib::manager::InstallerStatus>()
.build()
}
}
6 changes: 6 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Thu Nov 7 14:20:48 UTC 2024 - Imobach Gonzalez Sosa <[email protected]>

- Perform a system re-probing after executing pre-scripts
(gh#agama-project/agama#1735).

-------------------------------------------------------------------
Mon Nov 4 07:51:55 UTC 2024 - Michal Filka <[email protected]>

Expand Down

0 comments on commit 976f695

Please sign in to comment.