From 2ccbed492b484831865f14bab8a16f88350b77b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 13 Dec 2024 17:05:58 +0000 Subject: [PATCH] feat(rust): implements the /patterns endpoint --- rust/agama-server/src/software_ng/backend.rs | 3 + .../src/software_ng/backend/client.rs | 9 ++- .../src/software_ng/backend/server.rs | 72 +++++++++++++++---- rust/agama-server/src/software_ng/web.rs | 24 ++++++- 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/rust/agama-server/src/software_ng/backend.rs b/rust/agama-server/src/software_ng/backend.rs index 138350e362..5317b06ec1 100644 --- a/rust/agama-server/src/software_ng/backend.rs +++ b/rust/agama-server/src/software_ng/backend.rs @@ -83,6 +83,9 @@ pub enum SoftwareServiceError { #[error("Failed to load the repositories: {0}")] LoadSourcesFailed(#[source] ZyppError), + + #[error("Listing patterns failed: {0}")] + ListPatternsFailed(#[source] ZyppError), } /// Builds and starts the software service. diff --git a/rust/agama-server/src/software_ng/backend/client.rs b/rust/agama-server/src/software_ng/backend/client.rs index 0750b89bf3..85f4b50e1d 100644 --- a/rust/agama-server/src/software_ng/backend/client.rs +++ b/rust/agama-server/src/software_ng/backend/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 agama_lib::{product::Product, progress::ProgressSummary}; +use agama_lib::{product::Product, progress::ProgressSummary, software::Pattern}; use tokio::sync::oneshot; use crate::common::backend::service_status::ServiceStatusClient; @@ -48,6 +48,13 @@ impl SoftwareServiceClient { Ok(rx.await?) } + /// Returns the list of known patterns. + pub async fn get_patterns(&self) -> Result, SoftwareServiceError> { + let (tx, rx) = oneshot::channel(); + self.actions.send(SoftwareAction::GetPatterns(tx))?; + Ok(rx.await?) + } + pub async fn select_product(&self, product_id: &str) -> Result<(), SoftwareServiceError> { self.actions .send(SoftwareAction::SelectProduct(product_id.to_string()))?; diff --git a/rust/agama-server/src/software_ng/backend/server.rs b/rust/agama-server/src/software_ng/backend/server.rs index c6350b9613..3798e64a4b 100644 --- a/rust/agama-server/src/software_ng/backend/server.rs +++ b/rust/agama-server/src/software_ng/backend/server.rs @@ -20,12 +20,12 @@ use std::{path::Path, sync::Arc}; -use agama_lib::product::Product; +use agama_lib::{product::Product, software::Pattern}; use tokio::sync::{mpsc, oneshot, Mutex}; use crate::{ common::backend::service_status::{ServiceStatusClient, ServiceStatusManager}, - products::ProductsRegistry, + products::{ProductSpec, ProductsRegistry}, web::EventsSender, }; @@ -38,6 +38,7 @@ const GPG_KEYS: &str = "/usr/lib/rpm/gnupg/keys/gpg-*"; pub enum SoftwareAction { Probe, GetProducts(oneshot::Sender>), + GetPatterns(oneshot::Sender>), SelectProduct(String), } @@ -108,6 +109,10 @@ impl SoftwareServiceServer { self.get_products(tx).await?; } + SoftwareAction::GetPatterns(tx) => { + self.get_patterns(tx).await?; + } + SoftwareAction::SelectProduct(product_id) => { self.select_product(product_id).await?; } @@ -142,17 +147,7 @@ impl SoftwareServiceServer { ]) .await; - // FIXME: it holds the mutex too much. We could use a RwLock mutex. - let products = self.products.lock().await; - - let Some(product_id) = &self.selected_product else { - return Err(SoftwareServiceError::NoSelectedProduct); - }; - - let Some(product) = products.find(product_id) else { - return Err(SoftwareServiceError::UnknownProduct(product_id.clone())); - }; - + let product = self.find_selected_product().await?; let repositories = product.software.repositories(); for (idx, repo) in repositories.iter().enumerate() { // TODO: we should add a repository ID in the configuration file. @@ -198,6 +193,41 @@ impl SoftwareServiceServer { Ok(()) } + async fn get_patterns( + &self, + tx: oneshot::Sender>, + ) -> Result<(), SoftwareServiceError> { + let product = self.find_selected_product().await?; + + let mandatory_patterns = product.software.mandatory_patterns.iter(); + let optional_patterns = product.software.optional_patterns.unwrap_or(vec![]); + let optional_patterns = optional_patterns.iter(); + let pattern_names: Vec<&str> = vec![mandatory_patterns, optional_patterns] + .into_iter() + .flatten() + .map(String::as_str) + .collect(); + + let patterns = zypp_agama::patterns_info(pattern_names) + .map_err(SoftwareServiceError::ListPatternsFailed)?; + + let patterns = patterns + .into_iter() + .map(|info| Pattern { + name: info.name, + category: info.category, + description: info.description, + icon: info.icon, + summary: info.summary, + order: info.order, + }) + .collect(); + + tx.send(patterns) + .map_err(|_| SoftwareServiceError::ResponseChannelClosed)?; + Ok(()) + } + fn initialize_target_dir(&self) -> Result<(), SoftwareServiceError> { let target_dir = Path::new(TARGET_DIR); if target_dir.exists() { @@ -229,4 +259,20 @@ impl SoftwareServiceServer { } } } + + // Returns the spec of the selected product. + // + // It causes the spec to be cloned, so we should find a better way to do this. + async fn find_selected_product(&self) -> Result { + let products = self.products.lock().await; + let Some(product_id) = &self.selected_product else { + return Err(SoftwareServiceError::NoSelectedProduct); + }; + + let Some(product) = products.find(product_id) else { + return Err(SoftwareServiceError::UnknownProduct(product_id.clone())); + }; + + Ok(product.clone()) + } } diff --git a/rust/agama-server/src/software_ng/web.rs b/rust/agama-server/src/software_ng/web.rs index e3372daf69..4839af0500 100644 --- a/rust/agama-server/src/software_ng/web.rs +++ b/rust/agama-server/src/software_ng/web.rs @@ -19,8 +19,10 @@ // find current contact information at www.suse.com. use agama_lib::{ - error::ServiceError, product::Product, progress::ProgressSummary, - software::model::SoftwareConfig, + error::ServiceError, + product::Product, + progress::ProgressSummary, + software::{model::SoftwareConfig, Pattern}, }; use axum::{ extract::State, @@ -40,6 +42,7 @@ struct SoftwareState { pub async fn software_router(client: SoftwareServiceClient) -> Result { let state = SoftwareState { client }; let router = Router::new() + .route("/patterns", get(get_patterns)) .route("/products", get(get_products)) // FIXME: it should be PATCH (using PUT just for backward compatibility). .route("/config", put(set_config)) @@ -67,6 +70,23 @@ async fn get_products(State(state): State) -> Result), + (status = 400, description = "Cannot read the list of patterns") + ) +)] +async fn get_patterns(State(state): State) -> Result>, Error> { + let products = state.client.get_patterns().await?; + Ok(Json(products)) +} + /// Sets the software configuration. /// /// * `state`: service state.