Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(software): Reload repositories after failure #1894

Merged
merged 18 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion rust/agama-lib/src/software/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// find current contact information at www.suse.com.

use super::{
model::ResolvableType,
model::{Repository, ResolvableType},
proxies::{ProposalProxy, Software1Proxy},
};
use crate::error::ServiceError;
Expand All @@ -28,6 +28,7 @@ use serde_repr::Serialize_repr;
use std::collections::HashMap;
use zbus::Connection;

// TODO: move it to model?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. But there is not need to do it right now.

/// Represents a software product
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct Pattern {
Expand Down Expand Up @@ -88,6 +89,28 @@ impl<'a> SoftwareClient<'a> {
})
}

/// Returns list of defined repositories
pub async fn repositories(&self) -> Result<Vec<Repository>, ServiceError> {
let repositories: Vec<Repository> = self
.software_proxy
.list_repositories()
.await?
.into_iter()
.map(
|(id, alias, name, url, product_dir, enabled, loaded)| Repository {
id,
alias,
name,
url,
product_dir,
enabled,
loaded,
},
)
.collect();
Ok(repositories)
}

/// Returns the available patterns
pub async fn patterns(&self, filtered: bool) -> Result<Vec<Pattern>, ServiceError> {
let patterns: Vec<Pattern> = self
Expand Down
20 changes: 20 additions & 0 deletions rust/agama-lib/src/software/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,23 @@ pub struct ResolvableParams {
/// Whether the resolvables are optional or not.
pub optional: bool,
}

/// Repository list specification.
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Repository {
/// repository identifier
pub id: i32,
/// repository alias. Has to be unique
pub alias: String,
/// repository name
pub name: String,
/// Repository url (raw format without expanded variables)
pub url: String,
/// product directory (currently not used, valid only for multiproduct DVDs)
pub product_dir: String,
/// Whether the repository is enabled
pub enabled: bool,
/// Whether the repository is loaded
pub loaded: bool,
}
5 changes: 5 additions & 0 deletions rust/agama-lib/src/software/proxies/software.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ use zbus::proxy;
/// * Order.
pub type PatternsMap = std::collections::HashMap<String, (String, String, String, String, String)>;

pub type Repository = (i32, String, String, String, String, bool, bool);

#[proxy(
default_service = "org.opensuse.Agama.Software1",
default_path = "/org/opensuse/Agama/Software1",
Expand All @@ -77,6 +79,9 @@ pub trait Software1 {
/// ListPatterns method
fn list_patterns(&self, filtered: bool) -> zbus::Result<PatternsMap>;

/// ListRepositories method
fn list_repositories(&self) -> zbus::Result<Vec<Repository>>;

/// Probe method
fn probe(&self) -> zbus::Result<()>;

Expand Down
22 changes: 21 additions & 1 deletion rust/agama-server/src/software/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use agama_lib::{
product::{proxies::RegistrationProxy, Product, ProductClient},
software::{
model::{
RegistrationError, RegistrationInfo, RegistrationParams, ResolvableParams,
RegistrationError, RegistrationInfo, RegistrationParams, Repository, ResolvableParams,
SoftwareConfig,
},
proxies::{Software1Proxy, SoftwareProductProxy},
Expand Down Expand Up @@ -195,6 +195,7 @@ pub async fn software_service(dbus: zbus::Connection) -> Result<Router, ServiceE
let state = SoftwareState { product, software };
let router = Router::new()
.route("/patterns", get(patterns))
.route("/repositories", get(repositories))
.route("/products", get(products))
.route(
"/registration",
Expand Down Expand Up @@ -229,6 +230,25 @@ async fn products(State(state): State<SoftwareState<'_>>) -> Result<Json<Vec<Pro
Ok(Json(products))
}

/// Returns the list of defined repositories.
///
/// * `state`: service state.
#[utoipa::path(
get,
path = "/repositories",
context_path = "/api/software",
responses(
(status = 200, description = "List of known repositories", body = Vec<Repository>),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn repositories(
State(state): State<SoftwareState<'_>>,
) -> Result<Json<Vec<Repository>>, Error> {
let repositories = state.software.repositories().await?;
Ok(Json(repositories))
}

/// returns registration info
///
/// * `state`: service state.
Expand Down
6 changes: 6 additions & 0 deletions rust/agama-server/src/web/docs/software.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ impl ApiDocBuilder for SoftwareApiDocBuilder {

fn paths(&self) -> Paths {
PathsBuilder::new()
.path_from::<crate::software::web::__path_deregister>()
.path_from::<crate::software::web::__path_get_config>()
.path_from::<crate::software::web::__path_get_registration>()
.path_from::<crate::software::web::__path_patterns>()
.path_from::<crate::software::web::__path_probe>()
.path_from::<crate::software::web::__path_products>()
.path_from::<crate::software::web::__path_proposal>()
.path_from::<crate::software::web::__path_repositories>()
.path_from::<crate::software::web::__path_register>()
.path_from::<crate::software::web::__path_set_config>()
.path_from::<crate::software::web::__path_set_resolvables>()
.build()
Expand All @@ -51,10 +55,12 @@ impl ApiDocBuilder for SoftwareApiDocBuilder {
.schema_from::<agama_lib::software::Pattern>()
.schema_from::<agama_lib::software::model::RegistrationInfo>()
.schema_from::<agama_lib::software::model::RegistrationParams>()
.schema_from::<agama_lib::software::model::RegistrationError>()
.schema_from::<agama_lib::software::model::ResolvableParams>()
.schema_from::<agama_lib::software::model::ResolvableType>()
.schema_from::<agama_lib::software::SelectedBy>()
.schema_from::<agama_lib::software::model::SoftwareConfig>()
.schema_from::<agama_lib::software::model::Repository>()
.schema_from::<crate::software::web::SoftwareProposal>()
.schema_from::<crate::web::common::Issue>()
.build()
Expand Down
7 changes: 7 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Fri Jan 17 15:50:15 UTC 2025 - Ladislav Slezák <[email protected]>

- The web server provides /api/software/repositories endpoint
for reading the currently configured repositories,
related to (gh#agama-project/agama#1894)

-------------------------------------------------------------------
Thu Jan 16 13:10:54 UTC 2025 - Imobach Gonzalez Sosa <[email protected]>

Expand Down
18 changes: 18 additions & 0 deletions service/lib/agama/dbus/software/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ def issues
private_constant :SOFTWARE_INTERFACE

dbus_interface SOFTWARE_INTERFACE do
# array of repository properties: pkg-bindings ID, alias, name, URL, product dir, enabled
# and loaded flag
dbus_method :ListRepositories, "out Result:a(issssbb)" do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In storage we are tending to other kind of interfaces. The storage API returns JSON data instead of D-Bus structs, dicts, etc. At the end, we are using D-Bus as a transport layer, and probably we will replace it in the future by a HTTP API. Having JSON data has 2 advandanges in our case:

  • Replacing D-Bus by HTTP will be almost transparent.
  • The rust code is only a proxy that defines an endpoint (it does not need to interpret the data coming from D-Bus).

I guess it is fine to continue with this approach using structs in software. Anyway, we want to rewrite this service in rust.

backend.repositories.repositories.map do |repo|
[
[
repo.repo_id,
repo.repo_alias,
repo.name,
repo.raw_url.uri.to_s,
repo.product_dir,
repo.enabled?,
!!repo.loaded?
]
]
end
end

# value of result hash is category, description, icon, summary and order
dbus_method :ListPatterns, "in Filtered:b, out Result:a{s(sssss)}" do |filtered|
[
Expand Down
3 changes: 2 additions & 1 deletion service/lib/agama/software/repositories_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ def disabled
# Loads the repository metadata
#
# As a side effect, it disables those repositories that cannot be read.
# The intentation is to prevent the proposal from trying to read them
# The intent is to prevent the proposal from trying to read them
# again.
def load
repositories.each do |repo|
if repo.probe
repo.enable!
repo.refresh
else
repo.disable!
end
Expand Down
8 changes: 8 additions & 0 deletions service/lib/agama/software/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ def probe
type = Yast::Pkg.RepositoryProbe(url.to_s, product_dir)
!!type && type != "NONE"
end

def loaded?
@loaded
end

def refresh
@loaded = !!super
end
end
end
end
6 changes: 6 additions & 0 deletions service/package/rubygem-agama-yast.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Fri Jan 17 15:47:39 UTC 2025 - Ladislav Slezák <[email protected]>

- The software service provides DBus API for reading the currently
configured repositories, related to (gh#agama-project/agama#1894)

-------------------------------------------------------------------
Thu Jan 16 17:30:03 UTC 2025 - Ladislav Slezák <[email protected]>

Expand Down
4 changes: 2 additions & 2 deletions service/test/agama/software/repositories_manager_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
describe Agama::Software::RepositoriesManager do
let(:repo) do
instance_double(
Agama::Software::Repository, enable!: nil, probe: true, enabled?: true
Agama::Software::Repository, enable!: nil, probe: true, enabled?: true, refresh: false
)
end

Expand All @@ -47,7 +47,7 @@
describe "#load" do
let(:repo1) do
instance_double(
Agama::Software::Repository, disable!: nil, probe: false
Agama::Software::Repository, disable!: nil, probe: false, refresh: false
)
end

Expand Down
7 changes: 7 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Fri Jan 17 15:51:15 UTC 2025 - Ladislav Slezák <[email protected]>

- The software page displays a link for reloading the repositories
again if some repository failed to load
(gh#agama-project/agama#1894)

-------------------------------------------------------------------
Thu Jan 16 13:11:06 UTC 2025 - Imobach Gonzalez Sosa <[email protected]>

Expand Down
13 changes: 13 additions & 0 deletions web/src/api/software.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
Product,
SoftwareConfig,
RegistrationInfo,
Repository,
SoftwareProposal,
} from "~/types/software";
import { get, post, put } from "~/api/http";
Expand Down Expand Up @@ -54,13 +55,23 @@ const fetchRegistration = (): Promise<RegistrationInfo> => get("/api/software/re
*/
const fetchPatterns = (): Promise<Pattern[]> => get("/api/software/patterns");

/**
* Returns the list of configured repositories
*/
const fetchRepositories = (): Promise<Repository[]> => get("/api/software/repositories");

/**
* Updates the software configuration
*
* @param config - New software configuration
*/
const updateConfig = (config: SoftwareConfig) => put("/api/software/config", config);

/**
* Updates the software configuration
*/
const probe = () => post("/api/software/probe");

/**
* Request registration of selected product with given key
*/
Expand All @@ -73,6 +84,8 @@ export {
fetchProposal,
fetchProducts,
fetchRegistration,
fetchRepositories,
updateConfig,
probe,
register,
};
1 change: 0 additions & 1 deletion web/src/api/storage/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
Expand Down
2 changes: 2 additions & 0 deletions web/src/components/software/SoftwarePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jest.mock("~/queries/software", () => ({
usePatterns: () => testingPatterns,
useProposal: () => testingProposal,
useProposalChanges: jest.fn(),
useRepositories: () => [],
useRepositoryMutation: () => ({ mutate: jest.fn() }),
}));

describe("SoftwarePage", () => {
Expand Down
Loading
Loading