From 6abef5d0516f0e376ddedb62d552b15052e141d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Tue, 3 Dec 2024 22:15:14 +0000
Subject: [PATCH 01/47] feat(products): add a SLES 15.6 definition
* Only for testing purposes.
---
products.d/agama-products.spec | 1 +
products.d/sles_156.yaml | 162 +++++++++++++++++++++++++++++++++
2 files changed, 163 insertions(+)
create mode 100644 products.d/sles_156.yaml
diff --git a/products.d/agama-products.spec b/products.d/agama-products.spec
index 10e6448cc2..4db27c60a9 100644
--- a/products.d/agama-products.spec
+++ b/products.d/agama-products.spec
@@ -65,6 +65,7 @@ Definition of SLE-based products (e.g., SUSE Linux Enterprise Server) for the Ag
%license LICENSE
%dir %{_datadir}/agama
%dir %{_datadir}/agama/products.d
+%{_datadir}/agama/products.d/sles_156.yaml
%{_datadir}/agama/products.d/sles_160.yaml
%{_datadir}/agama/products.d/sles_sap_160.yaml
diff --git a/products.d/sles_156.yaml b/products.d/sles_156.yaml
new file mode 100644
index 0000000000..cf242e68a0
--- /dev/null
+++ b/products.d/sles_156.yaml
@@ -0,0 +1,162 @@
+id: SLES
+name: SUSE Linux Enterprise Server 15.6
+# ------------------------------------------------------------------------------
+# WARNING: When changing the product description delete the translations located
+# at the at translations/description key below to avoid using obsolete
+# translations!!
+# ------------------------------------------------------------------------------
+description: "An open, reliable, compliant, and future-proof Linux Server choice
+ that ensures the enterprise's business continuity. It is the secure and
+ adaptable OS for long-term supported, innovation-ready infrastructure running
+ business-critical workloads on-premises, in the cloud, and at the edge."
+icon: SUSE.svg
+# Do not manually change any translations! See README.md for more details.
+translations:
+ description:
+ ca: Una opció de servidor de Linux oberta, fiable, compatible i a prova del
+ futur que garanteix la continuïtat del negoci de l'empresa. És el sistema
+ operatiu segur i adaptable per a una infraestructura amb suport a llarg
+ termini i preparada per a la innovació que executa càrregues de treball
+ crítiques per a l'empresa a les instal·lacions, al núvol i a l'última.
+ cs:
+ Otevřená, spolehlivá, kompatibilní a perspektivní volba linuxového serveru,
+ která zajišťuje kontinuitu podnikání podniku. Je to bezpečný a
+ přizpůsobivý operační systém pro dlouhodobě podporovanou infrastrukturu
+ připravenou na inovace, na které běží kritické podnikové úlohy v lokálním
+ prostředí, v cloudu i na okraji sítě.
+ es:
+ Una opción de servidor Linux abierta, confiable, compatible y preparada para
+ el futuro que garantiza la continuidad del negocio de la empresa. Es el
+ sistema operativo seguro y adaptable para una infraestructura lista para
+ la innovación y con soporte a largo plazo que ejecuta cargas de trabajo
+ críticas para el negocio en las instalaciones, en la nube y en el borde.
+ ja:
+ オープンで信頼性が高く、各種の標準にも準拠し、将来性とビジネスの継続性を支援する Linux
+ サーバです。長期のサポートが提供されていることから、安全性と順応性に優れ、オンプレミスからクラウド、エッジ環境に至るまで、様々な場所で重要なビジネス処理をこなすことのできる革新性の高いインフラストラクチャです。
+ pt_BR:
+ Uma escolha de servidor Linux aberta, confiável, compatível e à prova do
+ futuro que garante a continuidade dos negócios da empresa. É o SO seguro e
+ adaptável para infraestrutura com suporte de longo prazo e pronta para
+ inovação, executando cargas de trabalho críticas para os negócios no
+ local, na nuvem e na borda.
+ sv: Ett öppet, pålitligt, kompatibelt och framtidssäkert Linux-serverval som
+ säkerställer företagets affärskontinuitet. Det är det säkra och
+ anpassningsbara operativsystemet för långsiktigt stödd, innovationsfärdig
+ infrastruktur som kör affärskritiska arbetsbelastningar på plats, i molnet
+ och vid kanten.
+ tr:
+ İşletmenin iş sürekliliğini garanti eden açık, güvenilir, uyumlu ve geleceğe
+ dönük bir Linux Sunucu seçeneği. Uzun vadeli desteklenen, inovasyona hazır
+ altyapı için güvenli ve uyarlanabilir işletim sistemidir. Şirket içinde,
+ bulutta ve uçta iş açısından kritik iş yüklerini çalıştırır.
+software:
+ installation_repositories: []
+ mandatory_patterns:
+ - base_traditional
+ optional_patterns: null # no optional pattern shared
+ user_patterns: []
+ mandatory_packages:
+ - NetworkManager
+ optional_packages: null
+ base_product: SLES
+ version: "15-6"
+
+security:
+ lsm: selinux
+ available_lsms:
+ selinux:
+ patterns:
+ - selinux
+ policy: enforcing
+ none:
+ patterns: null
+
+storage:
+ space_policy: delete
+ volumes:
+ - "/"
+ - "swap"
+ volume_templates:
+ - mount_path: "/"
+ filesystem: btrfs
+ btrfs:
+ snapshots: true
+ read_only: false
+ default_subvolume: "@"
+ subvolumes:
+ - path: home
+ - path: opt
+ - path: root
+ - path: srv
+ - path: usr/local
+ # Unified var subvolume - https://lists.opensuse.org/opensuse-packaging/2017-11/msg00017.html
+ - path: var
+ copy_on_write: false
+ # Architecture specific subvolumes
+ - path: boot/grub2/arm64-efi
+ archs: aarch64
+ - path: boot/grub2/arm-efi
+ archs: arm
+ - path: boot/grub2/i386-pc
+ archs: x86_64
+ - path: boot/grub2/powerpc-ieee1275
+ archs: ppc,!board_powernv
+ - path: boot/grub2/s390x-emu
+ archs: s390
+ - path: boot/grub2/x86_64-efi
+ archs: x86_64
+ - path: boot/grub2/riscv64-efi
+ archs: riscv64
+ size:
+ auto: true
+ outline:
+ required: true
+ filesystems:
+ - btrfs
+ - ext2
+ - ext3
+ - ext4
+ - xfs
+ auto_size:
+ base_min: 5 GiB
+ base_max: 15 GiB
+ snapshots_increment: 250%
+ max_fallback_for:
+ - "/home"
+ snapshots_configurable: true
+ - mount_path: "swap"
+ filesystem: swap
+ size:
+ min: 1 GiB
+ max: 2 GiB
+ outline:
+ required: false
+ filesystems:
+ - swap
+ - mount_path: "/home"
+ filesystem: xfs
+ size:
+ auto: false
+ min: 10 GiB
+ max: unlimited
+ outline:
+ required: false
+ filesystems:
+ - btrfs
+ - ext2
+ - ext3
+ - ext4
+ - xfs
+ - filesystem: xfs
+ size:
+ auto: false
+ min: 1 GiB
+ outline:
+ required: false
+ filesystems:
+ - btrfs
+ - ext2
+ - ext3
+ - ext4
+ - xfs
+ - vfat
From c593d0fe1a891cefa01564a5cd4f326a3be6d16f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Tue, 3 Dec 2024 22:16:08 +0000
Subject: [PATCH 02/47] fix(service): fix wrong method name
---
service/lib/agama/software/manager.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb
index 63f4675af5..f76046ea7a 100644
--- a/service/lib/agama/software/manager.rb
+++ b/service/lib/agama/software/manager.rb
@@ -339,7 +339,7 @@ def registration
# rubocop:disable Metrics/AbcSize
def add_service(service)
# init repos, so we are sure we operate on "/" and have GPG imported
- initialize_target_repos
+ initialize_target
# save repositories before refreshing added services (otherwise
# pkg-bindings will treat them as removed by the service refresh and
# unload them)
From fc4da539cf951d014524ec91d37520a138967864 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 11:36:02 +0000
Subject: [PATCH 03/47] feat(service): include registration requirement in the
product
---
service/lib/agama/dbus/software/product.rb | 5 +++--
service/lib/agama/software/product.rb | 9 +++++++++
service/lib/agama/software/product_builder.rb | 1 +
3 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/service/lib/agama/dbus/software/product.rb b/service/lib/agama/dbus/software/product.rb
index 53209ff0a3..db2838a944 100644
--- a/service/lib/agama/dbus/software/product.rb
+++ b/service/lib/agama/dbus/software/product.rb
@@ -58,8 +58,9 @@ def available_products
product.id,
product.display_name,
{
- "description" => product.localized_description,
- "icon" => product.icon
+ "description" => product.localized_description,
+ "icon" => product.icon,
+ "registration" => product.registration
}
]
end
diff --git a/service/lib/agama/software/product.rb b/service/lib/agama/software/product.rb
index 3fe27489a7..ff18bdc9f0 100644
--- a/service/lib/agama/software/product.rb
+++ b/service/lib/agama/software/product.rb
@@ -19,6 +19,8 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.
+require "agama/registration"
+
module Agama
module Software
# Represents a product that Agama can install.
@@ -90,6 +92,12 @@ class Product
# @return [Array]
attr_accessor :user_patterns
+ # Determines if the product should be registered.
+ #
+ # @see Agama::Registration::Requirement
+ # @return [String]
+ attr_accessor :registration
+
# Product translations.
#
# @example
@@ -115,6 +123,7 @@ def initialize(id)
@optional_patterns = []
# nil = display all visible patterns, [] = display no patterns
@user_patterns = nil
+ @registration = Agama::Registration::Requirement::NOT_REQUIRED
@translations = {}
end
diff --git a/service/lib/agama/software/product_builder.rb b/service/lib/agama/software/product_builder.rb
index a9ff1d655b..1d5234682b 100644
--- a/service/lib/agama/software/product_builder.rb
+++ b/service/lib/agama/software/product_builder.rb
@@ -65,6 +65,7 @@ def initialize_product(id, data, attrs)
product.name = data[:name]
product.version = data[:version]
product.icon = attrs["icon"] if attrs["icon"]
+ product.registration = attrs["registration"] if attrs["registration"]
end
end
From 68dad4ceba1ffc2f72665c53742cf7fa6a1909cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 11:41:48 +0000
Subject: [PATCH 04/47] feat(rust): include registration requirement in the
product
---
rust/agama-lib/src/product/client.rs | 17 +++++++------
rust/agama-lib/src/software/model.rs | 35 +++++++++------------------
rust/agama-server/src/software/web.rs | 26 --------------------
3 files changed, 22 insertions(+), 56 deletions(-)
diff --git a/rust/agama-lib/src/product/client.rs b/rust/agama-lib/src/product/client.rs
index 8517ddb9e9..4e6080b4f0 100644
--- a/rust/agama-lib/src/product/client.rs
+++ b/rust/agama-lib/src/product/client.rs
@@ -19,7 +19,9 @@
// find current contact information at www.suse.com.
use std::collections::HashMap;
+use std::str::FromStr;
+use crate::dbus::get_property;
use crate::error::ServiceError;
use crate::software::model::RegistrationRequirement;
use crate::software::proxies::SoftwareProductProxy;
@@ -39,6 +41,8 @@ pub struct Product {
pub description: String,
/// Product icon (e.g., "default.svg")
pub icon: String,
+ /// Registration requirement
+ pub registration: RegistrationRequirement,
}
/// D-Bus client for the software service
@@ -72,11 +76,17 @@ impl<'a> ProductClient<'a> {
Some(value) => value.try_into().unwrap(),
None => "default.svg",
};
+
+ let registration = get_property::(&data, "registration")
+ .map(|r| RegistrationRequirement::from_str(&r).unwrap_or_default())
+ .unwrap_or_default();
+
Product {
id,
name,
description: description.to_string(),
icon: icon.to_string(),
+ registration,
}
})
.collect();
@@ -114,13 +124,6 @@ impl<'a> ProductClient<'a> {
Ok(self.registration_proxy.email().await?)
}
- pub async fn registration_requirement(&self) -> Result {
- let requirement = self.registration_proxy.requirement().await?;
- // unknown number can happen only if we do programmer mistake
- let result: RegistrationRequirement = requirement.try_into().unwrap();
- Ok(result)
- }
-
/// register product
pub async fn register(&self, code: &str, email: &str) -> Result<(u32, String), ServiceError> {
let mut options: HashMap<&str, &zbus::zvariant::Value> = HashMap::new();
diff --git a/rust/agama-lib/src/software/model.rs b/rust/agama-lib/src/software/model.rs
index 31f81fbcaf..619b533a8f 100644
--- a/rust/agama-lib/src/software/model.rs
+++ b/rust/agama-lib/src/software/model.rs
@@ -47,14 +47,22 @@ pub struct RegistrationInfo {
pub key: String,
/// Registration email. Empty value mean email not used or not registered.
pub email: String,
- /// if registration is required, optional or not needed for current product.
- /// Change only if selected product is changed.
- pub requirement: RegistrationRequirement,
}
-#[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema)]
+#[derive(
+ Clone,
+ Default,
+ Debug,
+ Serialize,
+ Deserialize,
+ strum::Display,
+ strum::EnumString,
+ utoipa::ToSchema,
+)]
+#[strum(serialize_all = "camelCase")]
pub enum RegistrationRequirement {
/// Product does not require registration
+ #[default]
NotRequired = 0,
/// Product has optional registration
Optional = 1,
@@ -62,25 +70,6 @@ pub enum RegistrationRequirement {
Mandatory = 2,
}
-impl TryFrom for RegistrationRequirement {
- type Error = ();
-
- fn try_from(v: u32) -> Result {
- match v {
- x if x == RegistrationRequirement::NotRequired as u32 => {
- Ok(RegistrationRequirement::NotRequired)
- }
- x if x == RegistrationRequirement::Optional as u32 => {
- Ok(RegistrationRequirement::Optional)
- }
- x if x == RegistrationRequirement::Mandatory as u32 => {
- Ok(RegistrationRequirement::Mandatory)
- }
- _ => Err(()),
- }
- }
-}
-
/// Software resolvable type (package or pattern).
#[derive(Deserialize, Serialize, strum::Display, utoipa::ToSchema)]
#[strum(serialize_all = "camelCase")]
diff --git a/rust/agama-server/src/software/web.rs b/rust/agama-server/src/software/web.rs
index f4a0532349..f800c75cab 100644
--- a/rust/agama-server/src/software/web.rs
+++ b/rust/agama-server/src/software/web.rs
@@ -74,10 +74,6 @@ pub async fn software_streams(dbus: zbus::Connection) -> Result Result, Error> {
- // TODO: move registration requirement to product in dbus and so just one event will be needed.
- let proxy = RegistrationProxy::new(&dbus).await?;
- let stream = proxy
- .receive_requirement_changed()
- .await
- .then(|change| async move {
- if let Ok(id) = change.get().await {
- // unwrap is safe as possible numbers is send by our controlled dbus
- return Some(Event::RegistrationRequirementChanged {
- requirement: id.try_into().unwrap(),
- });
- }
- None
- })
- .filter_map(|e| e);
- Ok(stream)
-}
-
async fn registration_email_changed_stream(
dbus: zbus::Connection,
) -> Result, Error> {
@@ -269,7 +244,6 @@ async fn get_registration(
let result = RegistrationInfo {
key: state.product.registration_code().await?,
email: state.product.email().await?,
- requirement: state.product.registration_requirement().await?,
};
Ok(Json(result))
}
From fbbb7a74b522275d4612095de69206a1ab5db2e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 11:50:11 +0000
Subject: [PATCH 05/47] feat(products): add registration requirement to SLE 15
SP6
---
products.d/sles_156.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/products.d/sles_156.yaml b/products.d/sles_156.yaml
index cf242e68a0..ea22e9266e 100644
--- a/products.d/sles_156.yaml
+++ b/products.d/sles_156.yaml
@@ -1,5 +1,6 @@
id: SLES
name: SUSE Linux Enterprise Server 15.6
+registration: "mandatory"
# ------------------------------------------------------------------------------
# WARNING: When changing the product description delete the translations located
# at the at translations/description key below to avoid using obsolete
From d0beb0b007cbd6931b49db871e9df2fccd0b3666 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 12:12:16 +0000
Subject: [PATCH 06/47] feat(service): move the "version" field to the
top-level
---
products.d/sles_156.yaml | 2 +-
service/lib/agama/software/product_builder.rb | 2 +-
.../agama/software/product_builder_test.rb | 22 +++++++++++--------
3 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/products.d/sles_156.yaml b/products.d/sles_156.yaml
index ea22e9266e..48c70ca4af 100644
--- a/products.d/sles_156.yaml
+++ b/products.d/sles_156.yaml
@@ -1,6 +1,7 @@
id: SLES
name: SUSE Linux Enterprise Server 15.6
registration: "mandatory"
+version: "15-6"
# ------------------------------------------------------------------------------
# WARNING: When changing the product description delete the translations located
# at the at translations/description key below to avoid using obsolete
@@ -60,7 +61,6 @@ software:
- NetworkManager
optional_packages: null
base_product: SLES
- version: "15-6"
security:
lsm: selinux
diff --git a/service/lib/agama/software/product_builder.rb b/service/lib/agama/software/product_builder.rb
index 1d5234682b..a2507507bd 100644
--- a/service/lib/agama/software/product_builder.rb
+++ b/service/lib/agama/software/product_builder.rb
@@ -66,6 +66,7 @@ def initialize_product(id, data, attrs)
product.version = data[:version]
product.icon = attrs["icon"] if attrs["icon"]
product.registration = attrs["registration"] if attrs["registration"]
+ product.version = attrs["version"] if attrs["version"]
end
end
@@ -99,7 +100,6 @@ def set_translations(product, attrs)
def product_data_from_config(id)
{
name: config.products.dig(id, "software", "base_product"),
- version: config.products.dig(id, "software", "version"),
icon: config.products.dig(id, "software", "icon"),
labels: config.arch_elements_from(
id, "software", "installation_labels", property: :label
diff --git a/service/test/agama/software/product_builder_test.rb b/service/test/agama/software/product_builder_test.rb
index a48bc87ceb..cf67c02b0f 100644
--- a/service/test/agama/software/product_builder_test.rb
+++ b/service/test/agama/software/product_builder_test.rb
@@ -41,6 +41,8 @@
"id" => "Test1",
"name" => "Product Test 1",
"description" => "This is a test product named Test 1",
+ "version" => "1.0",
+ "registration" => "mandatory",
"translations" => {
"description" => {
"cs" => "Czech",
@@ -84,19 +86,19 @@
"archs" => "aarch64"
}
],
- "base_product" => "Test1",
- "version" => "1.0"
+ "base_product" => "Test1"
}
},
{
- "id" => "Test2",
- "name" => "Product Test 2",
- "description" => "This is a test product named Test 2",
- "archs" => "x86_64,aarch64",
- "software" => {
+ "id" => "Test2",
+ "name" => "Product Test 2",
+ "description" => "This is a test product named Test 2",
+ "archs" => "x86_64,aarch64",
+ "version" => "2.0",
+ "registration" => "optional",
+ "software" => {
"mandatory_patterns" => ["pattern2-1"],
- "base_product" => "Test2",
- "version" => "2.0"
+ "base_product" => "Test2"
}
},
{
@@ -143,6 +145,7 @@
description: "This is a test product named Test 1",
name: "Test1",
version: "1.0",
+ registration: "mandatory",
repositories: ["https://repos/test1/x86_64/product/"],
mandatory_patterns: ["pattern1-1", "pattern1-2"],
optional_patterns: ["pattern1-3"],
@@ -200,6 +203,7 @@
description: "This is a test product named Test 2",
name: "Test2",
version: "2.0",
+ registration: "optional",
repositories: [],
mandatory_patterns: ["pattern2-1"],
optional_patterns: [],
From bf7d346da98964c6f7d61d88a151b6b6fc11b2e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 12:23:23 +0000
Subject: [PATCH 07/47] refactor(rust): change registration "not_required" to
"no"
---
rust/agama-lib/src/software/model.rs | 2 +-
service/lib/agama/dbus/software/product.rb | 2 +-
service/lib/agama/registration.rb | 6 +++---
service/lib/agama/software/product.rb | 2 +-
service/test/agama/dbus/software/product_test.rb | 2 +-
service/test/agama/registration_test.rb | 4 ++--
6 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/rust/agama-lib/src/software/model.rs b/rust/agama-lib/src/software/model.rs
index 619b533a8f..1678334473 100644
--- a/rust/agama-lib/src/software/model.rs
+++ b/rust/agama-lib/src/software/model.rs
@@ -63,7 +63,7 @@ pub struct RegistrationInfo {
pub enum RegistrationRequirement {
/// Product does not require registration
#[default]
- NotRequired = 0,
+ No = 0,
/// Product has optional registration
Optional = 1,
/// It is mandatory to register the product
diff --git a/service/lib/agama/dbus/software/product.rb b/service/lib/agama/dbus/software/product.rb
index db2838a944..40b7d2b1fd 100644
--- a/service/lib/agama/dbus/software/product.rb
+++ b/service/lib/agama/dbus/software/product.rb
@@ -178,7 +178,7 @@ def register(reg_code, email: nil)
[1, "Product not selected yet"]
elsif backend.registration.reg_code
[2, "Product already registered"]
- elsif backend.registration.requirement == Agama::Registration::Requirement::NOT_REQUIRED
+ elsif backend.registration.requirement == Agama::Registration::Requirement::NO
[3, "Product does not require registration"]
else
connect_result(first_error_code: 4) do
diff --git a/service/lib/agama/registration.rb b/service/lib/agama/registration.rb
index 3466147215..bc180f1ccd 100644
--- a/service/lib/agama/registration.rb
+++ b/service/lib/agama/registration.rb
@@ -41,7 +41,7 @@ class Registration
attr_reader :email
module Requirement
- NOT_REQUIRED = :not_required
+ NO = :no
OPTIONAL = :optional
MANDATORY = :mandatory
end
@@ -142,10 +142,10 @@ def finish
#
# @return [Symbol] See {Requirement}.
def requirement
- return Requirement::NOT_REQUIRED unless product
+ return Requirement::NO unless product
return Requirement::MANDATORY if product.repositories.none?
- Requirement::NOT_REQUIRED
+ Requirement::NO
end
# Callbacks to be called when registration changes (e.g., a different product is selected).
diff --git a/service/lib/agama/software/product.rb b/service/lib/agama/software/product.rb
index ff18bdc9f0..01b219bf09 100644
--- a/service/lib/agama/software/product.rb
+++ b/service/lib/agama/software/product.rb
@@ -123,7 +123,7 @@ def initialize(id)
@optional_patterns = []
# nil = display all visible patterns, [] = display no patterns
@user_patterns = nil
- @registration = Agama::Registration::Requirement::NOT_REQUIRED
+ @registration = Agama::Registration::Requirement::NO
@translations = {}
end
diff --git a/service/test/agama/dbus/software/product_test.rb b/service/test/agama/dbus/software/product_test.rb
index 8ca3d45787..cadcdf8dae 100644
--- a/service/test/agama/dbus/software/product_test.rb
+++ b/service/test/agama/dbus/software/product_test.rb
@@ -158,7 +158,7 @@
end
context "if the registration is not required" do
- let(:requirement) { Agama::Registration::Requirement::NOT_REQUIRED }
+ let(:requirement) { Agama::Registration::Requirement::NO }
it "returns 0" do
expect(subject.requirement).to eq(0)
diff --git a/service/test/agama/registration_test.rb b/service/test/agama/registration_test.rb
index a2dd6731b8..05b7757fef 100644
--- a/service/test/agama/registration_test.rb
+++ b/service/test/agama/registration_test.rb
@@ -346,7 +346,7 @@
let(:product) { nil }
it "returns not required" do
- expect(subject.requirement).to eq(Agama::Registration::Requirement::NOT_REQUIRED)
+ expect(subject.requirement).to eq(Agama::Registration::Requirement::NO)
end
end
@@ -359,7 +359,7 @@
let(:repositories) { ["https://repo"] }
it "returns not required" do
- expect(subject.requirement).to eq(Agama::Registration::Requirement::NOT_REQUIRED)
+ expect(subject.requirement).to eq(Agama::Registration::Requirement::NO)
end
end
From 9aac42681f219db7e5ca7f4b1c9e01694c1a6086 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 13:35:02 +0000
Subject: [PATCH 08/47] chore(products): temporarily move SLE 15 definition to
openSUSE package
---
products.d/agama-products.spec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/products.d/agama-products.spec b/products.d/agama-products.spec
index 4db27c60a9..d252bd9b75 100644
--- a/products.d/agama-products.spec
+++ b/products.d/agama-products.spec
@@ -52,6 +52,7 @@ Definition of openSUSE products (Tumbleweed, Leap, MicroOS and Slowroll) for the
%{_datadir}/agama/products.d/tumbleweed.yaml
%{_datadir}/agama/products.d/leap_160.yaml
%{_datadir}/agama/products.d/slowroll.yaml
+%{_datadir}/agama/products.d/sles_160.yaml
%package sle
Summary: Definition of SLE products for the Agama installer.
@@ -66,7 +67,6 @@ Definition of SLE-based products (e.g., SUSE Linux Enterprise Server) for the Ag
%dir %{_datadir}/agama
%dir %{_datadir}/agama/products.d
%{_datadir}/agama/products.d/sles_156.yaml
-%{_datadir}/agama/products.d/sles_160.yaml
%{_datadir}/agama/products.d/sles_sap_160.yaml
%changelog
From 9906fc03ab61bc6e0dade90c0a9670bc835108c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 14:00:57 +0000
Subject: [PATCH 09/47] chore(products): swap SLE 15 and SLE 16 definitions
---
products.d/agama-products.spec | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/products.d/agama-products.spec b/products.d/agama-products.spec
index d252bd9b75..41aba34169 100644
--- a/products.d/agama-products.spec
+++ b/products.d/agama-products.spec
@@ -52,7 +52,7 @@ Definition of openSUSE products (Tumbleweed, Leap, MicroOS and Slowroll) for the
%{_datadir}/agama/products.d/tumbleweed.yaml
%{_datadir}/agama/products.d/leap_160.yaml
%{_datadir}/agama/products.d/slowroll.yaml
-%{_datadir}/agama/products.d/sles_160.yaml
+%{_datadir}/agama/products.d/sles_156.yaml
%package sle
Summary: Definition of SLE products for the Agama installer.
@@ -66,7 +66,7 @@ Definition of SLE-based products (e.g., SUSE Linux Enterprise Server) for the Ag
%license LICENSE
%dir %{_datadir}/agama
%dir %{_datadir}/agama/products.d
-%{_datadir}/agama/products.d/sles_156.yaml
+%{_datadir}/agama/products.d/sles_160.yaml
%{_datadir}/agama/products.d/sles_sap_160.yaml
%changelog
From 22b734dc66cfcb4c4ba590c38256bc7bb6252ce4 Mon Sep 17 00:00:00 2001
From: Josef Reidinger
Date: Wed, 4 Dec 2024 17:34:07 +0100
Subject: [PATCH 10/47] quick POC to fix registration:
---
service/lib/agama/registration.rb | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/service/lib/agama/registration.rb b/service/lib/agama/registration.rb
index bc180f1ccd..7d50e9c401 100644
--- a/service/lib/agama/registration.rb
+++ b/service/lib/agama/registration.rb
@@ -30,6 +30,15 @@
module Agama
# Handles everything related to registration of system to SCC, RMT or similar.
class Registration
+
+ # Note: identical and keep in sync with Software::Manager::TARGET_DIR
+ TARGET_DIR = "/run/agama/zypp"
+ private_constant :TARGET_DIR
+
+ GLOBAL_CREDENTIALS_PATH = File.join(TARGET_DIR,
+ SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE)
+ private_constant :GLOBAL_CREDENTIALS_PATH
+
# Code used for registering the product.
#
# @return [String, nil] nil if the product is not registered yet.
@@ -74,7 +83,7 @@ def register(code, email: "")
login, password = SUSE::Connect::YaST.announce_system(connect_params, target_distro)
# write the global credentials
# TODO: check if we can do it in memory for libzypp
- SUSE::Connect::YaST.create_credentials_file(login, password)
+ SUSE::Connect::YaST.create_credentials_file(login, password, GLOBAL_CREDENTIALS_PATH)
target_product = OpenStruct.new(
arch: Yast::Arch.rpm_arch,
@@ -86,7 +95,7 @@ def register(code, email: "")
# if service require specific credentials file, store it
@credentials_file = credentials_from_url(@service.url)
if @credentials_file
- SUSE::Connect::YaST.create_credentials_file(login, password, @credentials_file)
+ SUSE::Connect::YaST.create_credentials_file(login, password, File.join(TARGET_DIR, @credentials_file))
end
Y2Packager::NewRepositorySetup.instance.add_service(@service.name)
@software.add_service(@service)
@@ -116,9 +125,9 @@ def deregister
email: email
}
SUSE::Connect::YaST.deactivate_system(connect_params)
- FileUtils.rm(SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE) # connect does not remove it itself
+ FileUtils.rm(GLOBAL_CREDENTIALS_PATH) # connect does not remove it itself
if @credentials_file
- FileUtils.rm(credentials_path(@credentials_file))
+ FileUtils.rm(credentials_path(File.join(TARGET_DIR, @credentials_file)))
@credentials_file = nil
end
@@ -131,7 +140,7 @@ def deregister
def finish
return unless reg_code
- files = [credentials_path(@credentials_file), SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE]
+ files = [credentials_path(@credentials_file), SUSE::Connect::YaST::GLOBAL_CREDENTIALS_PATH]
files.each do |file|
dest = File.join(Yast::Installation.destdir, file)
FileUtils.cp(file, dest)
From b9c01551b418e06af6eea7a8d0a37c2c4d6565bb Mon Sep 17 00:00:00 2001
From: Josef Reidinger
Date: Wed, 4 Dec 2024 17:41:35 +0100
Subject: [PATCH 11/47] make rubocop happy and fix tests
---
service/lib/agama/registration.rb | 6 +++---
service/test/agama/registration_test.rb | 7 ++++---
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/service/lib/agama/registration.rb b/service/lib/agama/registration.rb
index 7d50e9c401..c3618c70c5 100644
--- a/service/lib/agama/registration.rb
+++ b/service/lib/agama/registration.rb
@@ -30,8 +30,7 @@
module Agama
# Handles everything related to registration of system to SCC, RMT or similar.
class Registration
-
- # Note: identical and keep in sync with Software::Manager::TARGET_DIR
+ # NOTE: identical and keep in sync with Software::Manager::TARGET_DIR
TARGET_DIR = "/run/agama/zypp"
private_constant :TARGET_DIR
@@ -95,7 +94,8 @@ def register(code, email: "")
# if service require specific credentials file, store it
@credentials_file = credentials_from_url(@service.url)
if @credentials_file
- SUSE::Connect::YaST.create_credentials_file(login, password, File.join(TARGET_DIR, @credentials_file))
+ SUSE::Connect::YaST.create_credentials_file(login, password,
+ File.join(TARGET_DIR, @credentials_file))
end
Y2Packager::NewRepositorySetup.instance.add_service(@service.name)
@software.add_service(@service)
diff --git a/service/test/agama/registration_test.rb b/service/test/agama/registration_test.rb
index 05b7757fef..72b79d2323 100644
--- a/service/test/agama/registration_test.rb
+++ b/service/test/agama/registration_test.rb
@@ -90,7 +90,7 @@
it "creates credentials file" do
expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
- .with("test-user", "12345")
+ .with("test-user", "12345", "/run/agama/zypp/etc/zypp/credentials.d/SCCcredentials")
subject.register("11112222", email: "test@test.com")
end
@@ -117,13 +117,14 @@
before do
allow(subject).to receive(:credentials_from_url)
- .with("https://credentials/file").and_return("credentials")
+ .with("https://credentials/file")
+ .and_return("/etc/zypp/credentials.d/product")
end
it "creates the credentials file" do
expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
- .with("test-user", "12345", "credentials")
+ .with("test-user", "12345", "/run/agama/zypp/etc/zypp/credentials.d/product")
subject.register("11112222", email: "test@test.com")
end
From 3e3708c8c8f7bdce297a37ccb747a4ce98e03848 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?=
Date: Wed, 4 Dec 2024 22:00:47 +0000
Subject: [PATCH 12/47] fix(web): add registration to Product type
---
web/src/App.test.tsx | 4 ++--
web/src/components/core/ChangeProductLink.test.tsx | 2 ++
web/src/components/layout/Header.test.tsx | 7 +++++--
web/src/components/product/ProductSelectionPage.test.tsx | 2 ++
web/src/types/software.ts | 2 ++
5 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx
index 1f806f0989..abe0834187 100644
--- a/web/src/App.test.tsx
+++ b/web/src/App.test.tsx
@@ -40,8 +40,8 @@ jest.mock("~/api/l10n", () => ({
updateConfig: jest.fn(),
}));
-const tumbleweed: Product = { id: "openSUSE", name: "openSUSE Tumbleweed" };
-const microos: Product = { id: "Leap Micro", name: "openSUSE Micro" };
+const tumbleweed: Product = { id: "openSUSE", name: "openSUSE Tumbleweed", registration: "No" };
+const microos: Product = { id: "Leap Micro", name: "openSUSE Micro", registration: "No" };
// list of available products
let mockProducts: Product[];
diff --git a/web/src/components/core/ChangeProductLink.test.tsx b/web/src/components/core/ChangeProductLink.test.tsx
index ddbdd369d1..bc71c1be9b 100644
--- a/web/src/components/core/ChangeProductLink.test.tsx
+++ b/web/src/components/core/ChangeProductLink.test.tsx
@@ -32,12 +32,14 @@ const tumbleweed: Product = {
name: "openSUSE Tumbleweed",
icon: "tumbleweed.svg",
description: "Tumbleweed description...",
+ registration: "No",
};
const microos: Product = {
id: "MicroOS",
name: "openSUSE MicroOS",
icon: "MicroOS.svg",
description: "MicroOS description",
+ registration: "No",
};
let mockUseProduct: { products: Product[]; selectedProduct?: Product };
diff --git a/web/src/components/layout/Header.test.tsx b/web/src/components/layout/Header.test.tsx
index 1ae19803fc..882a11e904 100644
--- a/web/src/components/layout/Header.test.tsx
+++ b/web/src/components/layout/Header.test.tsx
@@ -25,17 +25,20 @@ import { screen, within } from "@testing-library/react";
import { installerRender, mockRoutes } from "~/test-utils";
import Header from "./Header";
import { InstallationPhase } from "~/types/status";
+import { Product } from "~/types/software";
-const tumbleweed = {
+const tumbleweed: Product = {
id: "Tumbleweed",
name: "openSUSE Tumbleweed",
description: "Tumbleweed description...",
+ registration: "No",
};
-const microos = {
+const microos: Product = {
id: "MicroOS",
name: "openSUSE MicroOS",
description: "MicroOS description",
+ registration: "No",
};
let phase: InstallationPhase;
diff --git a/web/src/components/product/ProductSelectionPage.test.tsx b/web/src/components/product/ProductSelectionPage.test.tsx
index 4209e2da58..bb096ef626 100644
--- a/web/src/components/product/ProductSelectionPage.test.tsx
+++ b/web/src/components/product/ProductSelectionPage.test.tsx
@@ -33,6 +33,7 @@ const tumbleweed: Product = {
name: "openSUSE Tumbleweed",
icon: "tumbleweed.svg",
description: "Tumbleweed description...",
+ registration: "No",
};
const microOs: Product = {
@@ -40,6 +41,7 @@ const microOs: Product = {
name: "openSUSE MicroOS",
icon: "microos.svg",
description: "MicroOS description",
+ registration: "No",
};
jest.mock("~/queries/software", () => ({
diff --git a/web/src/types/software.ts b/web/src/types/software.ts
index 822d60855a..6a54c0b429 100644
--- a/web/src/types/software.ts
+++ b/web/src/types/software.ts
@@ -41,6 +41,8 @@ type Product = {
description?: string;
/** Product icon (e.g., "default.svg") */
icon?: string;
+ /** If product is registrable or not */
+ registration: string;
};
type PatternsSelection = { [key: string]: SelectedBy };
From 56fa368c6fab9dc8e61652d45290dcf12c4d6f70 Mon Sep 17 00:00:00 2001
From: Josef Reidinger
Date: Wed, 4 Dec 2024 23:02:59 +0100
Subject: [PATCH 13/47] fix creating credentials
---
service/lib/agama/registration.rb | 4 ++--
service/test/agama/registration_test.rb | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/service/lib/agama/registration.rb b/service/lib/agama/registration.rb
index c3618c70c5..208c8290ed 100644
--- a/service/lib/agama/registration.rb
+++ b/service/lib/agama/registration.rb
@@ -95,7 +95,7 @@ def register(code, email: "")
@credentials_file = credentials_from_url(@service.url)
if @credentials_file
SUSE::Connect::YaST.create_credentials_file(login, password,
- File.join(TARGET_DIR, @credentials_file))
+ File.join(TARGET_DIR, credentials_path(@credentials_file)))
end
Y2Packager::NewRepositorySetup.instance.add_service(@service.name)
@software.add_service(@service)
@@ -127,7 +127,7 @@ def deregister
SUSE::Connect::YaST.deactivate_system(connect_params)
FileUtils.rm(GLOBAL_CREDENTIALS_PATH) # connect does not remove it itself
if @credentials_file
- FileUtils.rm(credentials_path(File.join(TARGET_DIR, @credentials_file)))
+ FileUtils.rm(File.join(TARGET_DIR, credentials_path(@credentials_file)))
@credentials_file = nil
end
diff --git a/service/test/agama/registration_test.rb b/service/test/agama/registration_test.rb
index 72b79d2323..d34255c2ba 100644
--- a/service/test/agama/registration_test.rb
+++ b/service/test/agama/registration_test.rb
@@ -118,13 +118,13 @@
before do
allow(subject).to receive(:credentials_from_url)
.with("https://credentials/file")
- .and_return("/etc/zypp/credentials.d/product")
+ .and_return("productA")
end
it "creates the credentials file" do
expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
- .with("test-user", "12345", "/run/agama/zypp/etc/zypp/credentials.d/product")
+ .with("test-user", "12345", "/run/agama/zypp/etc/zypp/credentials.d/productA")
subject.register("11112222", email: "test@test.com")
end
From 916d41e99dbacd3b3393d4b0730e638d2960fbca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?=
Date: Wed, 4 Dec 2024 22:03:47 +0000
Subject: [PATCH 14/47] feat(web): allow setting a route only for registrable
products
Actually, at this moment the flag is only used for including a link to
the route in the main navigation or not depending on the
Product#registration value. But the idea is to limite the navigation too
in future iterations.
---
web/src/components/layout/Sidebar.test.tsx | 66 +++++++++++++++++++---
web/src/components/layout/Sidebar.tsx | 4 ++
web/src/types/routes.ts | 2 +
3 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/web/src/components/layout/Sidebar.test.tsx b/web/src/components/layout/Sidebar.test.tsx
index fa3d3c7495..346bf95317 100644
--- a/web/src/components/layout/Sidebar.test.tsx
+++ b/web/src/components/layout/Sidebar.test.tsx
@@ -24,25 +24,77 @@ import React from "react";
import { screen, within } from "@testing-library/react";
import { installerRender } from "~/test-utils";
import Sidebar from "./Sidebar";
+import { Product } from "~/types/software";
+import { useProduct } from "~/queries/software";
jest.mock("~/components/core/ChangeProductLink", () => () =>
ChangeProductLink Mock
);
+const tw: Product = {
+ id: "Tumbleweed",
+ name: "openSUSE Tumbleweed",
+ registration: "No",
+};
+
+const sle: Product = {
+ id: "sle",
+ name: "SLE",
+ registration: "Mandatory",
+};
+
+let selectedProduct: Product;
+
+jest.mock("~/queries/software", () => ({
+ ...jest.requireActual("~/queries/software"),
+ useProduct: (): ReturnType => {
+ return {
+ products: [tw, sle],
+ selectedProduct,
+ };
+ },
+}));
+
jest.mock("~/router", () => ({
rootRoutes: () => [
{ path: "/", handle: { name: "Main" } },
{ path: "/l10n", handle: { name: "L10n" } },
{ path: "/hidden" },
+ {
+ path: "/registration",
+ handle: { name: "Registration", needsRegistrableProduct: true },
+ },
],
}));
describe("Sidebar", () => {
- it("renders a navigation on top of root routes with handle object", () => {
- installerRender();
- const mainNavigation = screen.getByRole("navigation");
- const mainNavigationLinks = within(mainNavigation).getAllByRole("link");
- expect(mainNavigationLinks.length).toBe(2);
- screen.getByRole("link", { name: "Main" });
- screen.getByRole("link", { name: "L10n" });
+ describe("when product is registrable", () => {
+ beforeEach(() => {
+ selectedProduct = sle;
+ });
+
+ it("renders a navigation including all root routes with handle object", () => {
+ installerRender();
+ const mainNavigation = screen.getByRole("navigation");
+ const mainNavigationLinks = within(mainNavigation).getAllByRole("link");
+ expect(mainNavigationLinks.length).toBe(3);
+ screen.getByRole("link", { name: "Main" });
+ screen.getByRole("link", { name: "L10n" });
+ screen.getByRole("link", { name: "Registration" });
+ });
+ });
+
+ describe("when product is not registrable", () => {
+ beforeEach(() => {
+ selectedProduct = tw;
+ });
+
+ it("renders a navigation including all root routes with handle object, except ones set as needsRegistrableProduct", () => {
+ installerRender();
+ const mainNavigation = screen.getByRole("navigation");
+ const mainNavigationLinks = within(mainNavigation).getAllByRole("link");
+ expect(mainNavigationLinks.length).toBe(2);
+ screen.getByRole("link", { name: "Main" });
+ screen.getByRole("link", { name: "L10n" });
+ });
});
it("mounts core/ChangeProductLink component", () => {
diff --git a/web/src/components/layout/Sidebar.tsx b/web/src/components/layout/Sidebar.tsx
index aa768d0218..2b1789c21d 100644
--- a/web/src/components/layout/Sidebar.tsx
+++ b/web/src/components/layout/Sidebar.tsx
@@ -27,10 +27,14 @@ import { Icon } from "~/components/layout";
import { ChangeProductLink } from "~/components/core";
import { rootRoutes } from "~/router";
import { _ } from "~/i18n";
+import { useProduct } from "~/queries/software";
const MainNavigation = (): React.ReactNode => {
+ const { selectedProduct: product } = useProduct();
+
const links = rootRoutes().map((r) => {
if (!r.handle) return null;
+ if (r.handle.needsRegistrableProduct && product.registration === "No") return null;
// eslint-disable-next-line agama-i18n/string-literals
const name = _(r.handle.name);
diff --git a/web/src/types/routes.ts b/web/src/types/routes.ts
index 11f2135547..fe5b85ea71 100644
--- a/web/src/types/routes.ts
+++ b/web/src/types/routes.ts
@@ -29,6 +29,8 @@ type RouteHandle = {
title?: string;
/** Icon for representing the route in some places, like a menu entry */
icon?: string;
+ /** Whether the route link will be rendered for registrable products only */
+ needsRegistrableProduct?: boolean;
};
type Route = RouteObject & { handle?: RouteHandle };
From 34d311db505410ecf082e658c5d19aa1828ca653 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?=
Date: Wed, 4 Dec 2024 22:15:51 +0000
Subject: [PATCH 15/47] feat(web): add product registration interface
An initial version that basically consists on
- Main menu entry for registration
- Global alert visible on all pages except the registration page and
those mounted at "supportive paths" (temporary name)
- Section with form for entering registration code and email
- Section to display registration information when available
Only basic workflow has been manually tested; unit tests will be added in upcoming commits.
---
web/src/api/software.ts | 37 +++-
web/src/components/core/IssuesDrawer.tsx | 7 +-
web/src/components/core/Page.tsx | 10 +-
web/src/components/layout/Icon.tsx | 2 +
.../product/ProductRegistrationAlert.tsx | 58 ++++++
.../product/ProductRegistrationPage.tsx | 184 ++++++++++++++++++
web/src/components/product/index.ts | 2 +
web/src/queries/software.ts | 61 ++++++
web/src/router.js | 2 +
web/src/routes/paths.ts | 15 +-
web/src/routes/registration.tsx | 40 ++++
web/src/types/software.ts | 14 +-
web/src/utils.js | 6 +
13 files changed, 429 insertions(+), 9 deletions(-)
create mode 100644 web/src/components/product/ProductRegistrationAlert.tsx
create mode 100644 web/src/components/product/ProductRegistrationPage.tsx
create mode 100644 web/src/routes/registration.tsx
diff --git a/web/src/api/software.ts b/web/src/api/software.ts
index 3be92e58ff..82f8650dc5 100644
--- a/web/src/api/software.ts
+++ b/web/src/api/software.ts
@@ -20,8 +20,14 @@
* find current contact information at www.suse.com.
*/
-import { Pattern, Product, SoftwareConfig, SoftwareProposal } from "~/types/software";
-import { get, put } from "~/api/http";
+import {
+ Pattern,
+ Product,
+ SoftwareConfig,
+ RegistrationInfo,
+ SoftwareProposal,
+} from "~/types/software";
+import { del, get, post, put } from "~/api/http";
/**
* Returns the software configuration
@@ -38,6 +44,11 @@ const fetchProposal = (): Promise => get("/api/software/propos
*/
const fetchProducts = (): Promise => get("/api/software/products");
+/**
+ * Returns an object with the registration info
+ */
+const fetchRegistration = (): Promise => get("/api/software/registration");
+
/**
* Returns the list of patterns for the selected product
*/
@@ -50,4 +61,24 @@ const fetchPatterns = (): Promise => get("/api/software/patterns");
*/
const updateConfig = (config: SoftwareConfig) => put("/api/software/config", config);
-export { fetchConfig, fetchPatterns, fetchProposal, fetchProducts, updateConfig };
+/**
+ * Request registration of selected product with given key
+ */
+const register = ({ key, email }: { key: string; email?: string }) =>
+ post("/api/software/registration", { key, email });
+
+/**
+ * Request deregistering selected product
+ */
+const deregister = () => del("/api/software/registration");
+
+export {
+ fetchConfig,
+ fetchPatterns,
+ fetchProposal,
+ fetchProducts,
+ fetchRegistration,
+ updateConfig,
+ register,
+ deregister,
+};
diff --git a/web/src/components/core/IssuesDrawer.tsx b/web/src/components/core/IssuesDrawer.tsx
index 4a0ecfa7fe..67a8fdd94d 100644
--- a/web/src/components/core/IssuesDrawer.tsx
+++ b/web/src/components/core/IssuesDrawer.tsx
@@ -49,6 +49,8 @@ const IssuesDrawer = forwardRef(({ onClose }: { onClose: () => void }, ref) => {
users: _("Users"),
storage: _("Storage"),
software: _("Software"),
+ // FIXME: Registration or Product?
+ product: _("Registration"),
};
if (issues.isEmpty || phase === InstallationPhase.Install) return;
@@ -65,13 +67,16 @@ const IssuesDrawer = forwardRef(({ onClose }: { onClose: () => void }, ref) => {
{Object.entries(issuesByScope).map(([scope, issues], idx) => {
if (issues.length === 0) return null;
+ // FIXME: address this better or use the /product(s)? namespace instead of
+ // /registration.
+ const section = scope === "product" ? "registration" : scope;
const ariaLabelId = `${scope}-issues-section`;
return (
-
+
{scopeHeaders[scope]}
diff --git a/web/src/components/core/Page.tsx b/web/src/components/core/Page.tsx
index 799f2413eb..5ddf83e5fa 100644
--- a/web/src/components/core/Page.tsx
+++ b/web/src/components/core/Page.tsx
@@ -40,6 +40,7 @@ import {
TitleProps,
} from "@patternfly/react-core";
import { Flex } from "~/components/layout";
+import { ProductRegistrationAlert } from "~/components/product";
import { _ } from "~/i18n";
import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
import flexStyles from "@patternfly/react-styles/css/utilities/Flex/flex";
@@ -279,9 +280,12 @@ const Submit = ({ children, ...props }: SubmitActionProps) => {
* @see [Patternfly Page/PageSection](https://www.patternfly.org/components/page#pagesection)
*/
const Content = ({ children, ...pageSectionProps }: React.PropsWithChildren) => (
-
- {children}
-
+ <>
+
+
+ {children}
+
+ >
);
/**
diff --git a/web/src/components/layout/Icon.tsx b/web/src/components/layout/Icon.tsx
index cbdc3ce110..09ab2d872b 100644
--- a/web/src/components/layout/Icon.tsx
+++ b/web/src/components/layout/Icon.tsx
@@ -26,6 +26,7 @@ import React from "react";
// icons location. Check the tsconfig.json file to see its value.
import AddAPhoto from "@icons/add_a_photo.svg?component";
import Apps from "@icons/apps.svg?component";
+import AppRegistration from "@icons/app_registration.svg?component";
import Badge from "@icons/badge.svg?component";
import Backspace from "@icons/backspace.svg?component";
import CheckCircle from "@icons/check_circle.svg?component";
@@ -91,6 +92,7 @@ import { SiLinux } from "@icons-pack/react-simple-icons";
const icons = {
add_a_photo: AddAPhoto,
apps: Apps,
+ app_registration: AppRegistration,
badge: Badge,
backspace: Backspace,
check_circle: CheckCircle,
diff --git a/web/src/components/product/ProductRegistrationAlert.tsx b/web/src/components/product/ProductRegistrationAlert.tsx
new file mode 100644
index 0000000000..1681daaf92
--- /dev/null
+++ b/web/src/components/product/ProductRegistrationAlert.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) [2023-2024] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { Alert, Flex } from "@patternfly/react-core";
+import { useLocation } from "react-router-dom";
+import { Link } from "~/components/core";
+import { useProduct, useRegistration } from "~/queries/software";
+import { REGISTRATION, SUPPORTIVE_PATHS } from "~/routes/paths";
+import { isEmpty } from "~/utils";
+import { _ } from "~/i18n";
+import { sprintf } from "sprintf-js";
+
+export default function ProductRegistrationAlert() {
+ const location = useLocation();
+ const { selectedProduct: product } = useProduct();
+ const registration = useRegistration();
+
+ if ([...SUPPORTIVE_PATHS, REGISTRATION.root].includes(location.pathname)) return;
+ if (product.registration === "No" || !isEmpty(registration.key)) return;
+
+ return (
+
+
+
+ {sprintf(
+ _(
+ "%s has to be registered because ",
+ ),
+ product.name,
+ )}
+
+
+ {_("Register it now")}
+
+
+
+ );
+}
diff --git a/web/src/components/product/ProductRegistrationPage.tsx b/web/src/components/product/ProductRegistrationPage.tsx
new file mode 100644
index 0000000000..ca6ea4ed94
--- /dev/null
+++ b/web/src/components/product/ProductRegistrationPage.tsx
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) [2023-2024] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React, { useState } from "react";
+import {
+ ActionGroup,
+ Alert,
+ Button,
+ DescriptionList,
+ DescriptionListDescription,
+ DescriptionListGroup,
+ DescriptionListTerm,
+ Flex,
+ Form,
+ FormGroup,
+ Grid,
+ Stack,
+ TextInput,
+} from "@patternfly/react-core";
+import { Page, PasswordInput } from "~/components/core";
+import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
+import spacingStyles from "@patternfly/react-styles/css/utilities/Spacing/spacing";
+import {
+ useProduct,
+ useRegistration,
+ useRegisterMutation,
+ useDeregisterMutation,
+} from "~/queries/software";
+import { isEmpty, mask } from "~/utils";
+import { _ } from "~/i18n";
+import { sprintf } from "sprintf-js";
+
+const FORM_ID = "productRegistration";
+const KEY_LABEL = _("Registration code");
+const EMAIL_LABEL = "Email";
+
+const RegisteredProductSection = () => {
+ const { selectedProduct: product } = useProduct();
+ const { mutate: deregister } = useDeregisterMutation();
+ const registration = useRegistration();
+ const [showCode, setShowCode] = useState(false);
+ const toggleCodeVisibility = () => setShowCode(!showCode);
+
+ const footer = _("For using a different registration code, please %s the product first.");
+ const deregisterButtonLabel = _("deregister");
+ const [footerStart, footerEnd] = footer.split("%s");
+
+ return (
+
+ {footerStart}{" "}
+ {" "}
+ {footerEnd}
+
+ }
+ >
+
+
+ {KEY_LABEL}
+
+
+ {showCode ? registration.key : mask(registration.key)}
+
+
+
+ {!isEmpty(registration.email) && (
+ <>
+ {EMAIL_LABEL}
+ {registration.email}
+ >
+ )}
+
+
+
+ );
+};
+
+const RegistrationFormSection = () => {
+ const { mutate: register } = useRegisterMutation();
+ const [key, setKey] = useState("");
+ const [email, setEmail] = useState("");
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ // FIXME: use the right type for AxiosResponse
+ const onRegisterError = ({ response }) => {
+ const originalMessage = response.data.message;
+ const from = originalMessage.indexOf(":") + 1;
+ setError(originalMessage.slice(from).trim());
+ };
+
+ const submit = async (e: React.SyntheticEvent) => {
+ e.preventDefault();
+ setError(null);
+ setLoading(true);
+ register({ key, email }, { onError: onRegisterError, onSettled: () => setLoading(false) });
+ };
+
+ // TODO: adjust texts based of registration "type", mandatory or optional
+
+ return (
+
+
+
+ );
+};
+
+export default function ProductRegistrationPage() {
+ const { selectedProduct: product } = useProduct();
+ const registration = useRegistration();
+
+ // TODO: render something meaningful instead? "Product not registrable"?
+ if (product.registration === "No") return;
+
+ return (
+
+
+
{_("Registration")}
+
+
+
+
+
+ {isEmpty(registration.key) ? : }
+
+
+
+
+ );
+}
diff --git a/web/src/components/product/index.ts b/web/src/components/product/index.ts
index 1340569f37..fc63c951d0 100644
--- a/web/src/components/product/index.ts
+++ b/web/src/components/product/index.ts
@@ -22,3 +22,5 @@
export { default as ProductSelectionPage } from "./ProductSelectionPage";
export { default as ProductSelectionProgress } from "./ProductSelectionProgress";
+export { default as ProductRegistrationPage } from "./ProductRegistrationPage";
+export { default as ProductRegistrationAlert } from "./ProductRegistrationAlert";
diff --git a/web/src/queries/software.ts b/web/src/queries/software.ts
index 6030489bd7..287154283d 100644
--- a/web/src/queries/software.ts
+++ b/web/src/queries/software.ts
@@ -33,15 +33,19 @@ import {
Pattern,
PatternsSelection,
Product,
+ RegistrationInfo,
SelectedBy,
SoftwareConfig,
SoftwareProposal,
} from "~/types/software";
import {
+ deregister,
fetchConfig,
fetchPatterns,
fetchProducts,
fetchProposal,
+ fetchRegistration,
+ register,
updateConfig,
} from "~/api/software";
import { QueryHookOptions } from "~/types/queries";
@@ -80,6 +84,14 @@ const selectedProductQuery = () => ({
queryFn: () => fetchConfig().then(({ product }) => product),
});
+/**
+ * Query to retrieve registration info
+ */
+const registrationQuery = () => ({
+ queryKey: ["software/registration"],
+ queryFn: fetchRegistration,
+});
+
/**
* Query to retrieve available patterns
*/
@@ -111,6 +123,44 @@ const useConfigMutation = () => {
return useMutation(query);
};
+/**
+ * Hook that builds a mutation for registering a product
+ *
+ * @note it would trigger a general probing as a side-effect when mutation
+ * includes a product.
+ */
+const useRegisterMutation = () => {
+ const queryClient = useQueryClient();
+
+ const query = {
+ mutationFn: register,
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["software/registration"] });
+ startProbing();
+ },
+ };
+ return useMutation(query);
+};
+
+/**
+ * Hook that builds a mutation for deregistering a product
+ *
+ * @note it would trigger a general probing as a side-effect when mutation
+ * includes a product.
+ */
+const useDeregisterMutation = () => {
+ const queryClient = useQueryClient();
+
+ const query = {
+ mutationFn: deregister,
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["software/registration"] });
+ startProbing();
+ },
+ };
+ return useMutation(query);
+};
+
/**
* Returns available products and selected one, if any
*/
@@ -172,6 +222,14 @@ const useProposal = (): SoftwareProposal => {
return proposal;
};
+/**
+ * Returns registration info
+ */
+const useRegistration = (): RegistrationInfo => {
+ const { data: registration } = useSuspenseQuery(registrationQuery());
+ return registration;
+};
+
/**
* Hook that returns a useEffect to listen for software proposal events
*
@@ -226,4 +284,7 @@ export {
useProductChanges,
useProposal,
useProposalChanges,
+ useRegistration,
+ useRegisterMutation,
+ useDeregisterMutation,
};
diff --git a/web/src/router.js b/web/src/router.js
index f949a76652..314ef63116 100644
--- a/web/src/router.js
+++ b/web/src/router.js
@@ -30,6 +30,7 @@ import { OverviewPage } from "~/components/overview";
import l10nRoutes from "~/routes/l10n";
import networkRoutes from "~/routes/network";
import productsRoutes from "~/routes/products";
+import registrationRoutes from "~/routes/registration";
import storageRoutes from "~/routes/storage";
import softwareRoutes from "~/routes/software";
import usersRoutes from "~/routes/users";
@@ -43,6 +44,7 @@ const rootRoutes = () => [
element: ,
handle: { name: N_("Overview"), icon: "list_alt" },
},
+ registrationRoutes(),
l10nRoutes(),
networkRoutes(),
storageRoutes(),
diff --git a/web/src/routes/paths.ts b/web/src/routes/paths.ts
index 22cdba06e2..efe67522a4 100644
--- a/web/src/routes/paths.ts
+++ b/web/src/routes/paths.ts
@@ -39,6 +39,10 @@ const PRODUCT = {
progress: "/products/progress",
};
+const REGISTRATION = {
+ root: "/registration",
+};
+
const ROOT = {
root: "/",
login: "/login",
@@ -78,4 +82,13 @@ const STORAGE = {
},
};
-export { L10N, NETWORK, PRODUCT, ROOT, SOFTWARE, STORAGE, USER };
+const SUPPORTIVE_PATHS = [
+ ROOT.login,
+ PRODUCT.changeProduct,
+ PRODUCT.progress,
+ ROOT.installationProgress,
+ ROOT.installationFinished,
+ USER.rootUser.edit,
+];
+
+export { L10N, NETWORK, PRODUCT, REGISTRATION, ROOT, SOFTWARE, STORAGE, USER, SUPPORTIVE_PATHS };
diff --git a/web/src/routes/registration.tsx b/web/src/routes/registration.tsx
new file mode 100644
index 0000000000..db27ad5037
--- /dev/null
+++ b/web/src/routes/registration.tsx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) [2024] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { ProductRegistrationPage } from "~/components/product";
+import { Route } from "~/types/routes";
+import { REGISTRATION as PATHS } from "~/routes/paths";
+import { N_ } from "~/i18n";
+
+const routes = (): Route => ({
+ path: PATHS.root,
+ handle: { name: N_("Registration"), icon: "app_registration", needsRegistrableProduct: true },
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ ],
+});
+
+export default routes;
diff --git a/web/src/types/software.ts b/web/src/types/software.ts
index 6a54c0b429..59f5dcaeef 100644
--- a/web/src/types/software.ts
+++ b/web/src/types/software.ts
@@ -78,5 +78,17 @@ type Pattern = {
selectedBy?: SelectedBy;
};
+type RegistrationInfo = {
+ key: string;
+ email?: string;
+};
+
export { SelectedBy };
-export type { Pattern, PatternsSelection, Product, SoftwareConfig, SoftwareProposal };
+export type {
+ Pattern,
+ PatternsSelection,
+ Product,
+ SoftwareConfig,
+ RegistrationInfo,
+ SoftwareProposal,
+};
diff --git a/web/src/utils.js b/web/src/utils.js
index 50feed2b79..418de947a1 100644
--- a/web/src/utils.js
+++ b/web/src/utils.js
@@ -420,6 +420,11 @@ const timezoneTime = (timezone, { date = new Date() }) => {
}
};
+const mask = (value, visible = 4, character = "*") => {
+ const regex = new RegExp(`.(?=(.{${visible}}))`, "g");
+ return value.replace(regex, character);
+};
+
export {
noop,
identity,
@@ -441,4 +446,5 @@ export {
remoteConnection,
slugify,
timezoneTime,
+ mask,
};
From 0063a08dc465e603e3d9aa2f0fc6ce876af86f10 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?=
Date: Wed, 4 Dec 2024 22:30:49 +0000
Subject: [PATCH 16/47] fix(web): use "supportive paths" for InstallButton
Certain elements must not be rendered on paths that do not use the
"FullLayout". Other paths, considered "accessory" paths, have been
temporarily grouped under the "SUPPORTIVE_PATHS" constant to reduce code
duplication when excluding elements from them.
Although the approach needs improvement, starting with the naming,
let's use it where needed meanwhile.
---
web/src/components/core/InstallButton.tsx | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/web/src/components/core/InstallButton.tsx b/web/src/components/core/InstallButton.tsx
index 5a6d44333d..93826fe188 100644
--- a/web/src/components/core/InstallButton.tsx
+++ b/web/src/components/core/InstallButton.tsx
@@ -26,7 +26,7 @@ import { Popup } from "~/components/core";
import { startInstallation } from "~/api/manager";
import { useAllIssues } from "~/queries/issues";
import { useLocation } from "react-router-dom";
-import { PRODUCT, ROOT, USER } from "~/routes/paths";
+import { SUPPORTIVE_PATHS } from "~/routes/paths";
import { _ } from "~/i18n";
import { Icon } from "../layout";
@@ -38,14 +38,6 @@ import { Icon } from "../layout";
* defining the root authentication for the fisrt time, nor when the installer
* is setting the chosen product.
* */
-const EXCLUDED_FROM = [
- ROOT.login,
- PRODUCT.changeProduct,
- PRODUCT.progress,
- ROOT.installationProgress,
- ROOT.installationFinished,
- USER.rootUser.edit,
-];
const InstallConfirmationPopup = ({ onAccept, onClose }) => {
return (
@@ -90,7 +82,7 @@ const InstallButton = (
const location = useLocation();
const hasIssues = !issues.isEmpty;
- if (EXCLUDED_FROM.includes(location.pathname)) return;
+ if (SUPPORTIVE_PATHS.includes(location.pathname)) return;
const { onClickWithIssues, ...buttonProps } = props;
const open = async () => setIsOpen(true);
From 64d27a343b712fb8bf728b91cf36be212059718d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 22:52:00 +0000
Subject: [PATCH 17/47] fix(service): do not initialize the target in
add_service
---
service/lib/agama/software/manager.rb | 2 --
1 file changed, 2 deletions(-)
diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb
index f76046ea7a..3312d5138f 100644
--- a/service/lib/agama/software/manager.rb
+++ b/service/lib/agama/software/manager.rb
@@ -338,8 +338,6 @@ def registration
# code is based on https://github.com/yast/yast-registration/blob/master/src/lib/registration/sw_mgmt.rb#L365
# rubocop:disable Metrics/AbcSize
def add_service(service)
- # init repos, so we are sure we operate on "/" and have GPG imported
- initialize_target
# save repositories before refreshing added services (otherwise
# pkg-bindings will treat them as removed by the service refresh and
# unload them)
From 656091ee556e2b468211d75215c0156a5865e3f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Wed, 4 Dec 2024 23:06:06 +0000
Subject: [PATCH 18/47] fix(web): temporarily disable a TS problem
---
web/src/components/product/ProductRegistrationPage.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/web/src/components/product/ProductRegistrationPage.tsx b/web/src/components/product/ProductRegistrationPage.tsx
index ca6ea4ed94..463ba123e8 100644
--- a/web/src/components/product/ProductRegistrationPage.tsx
+++ b/web/src/components/product/ProductRegistrationPage.tsx
@@ -120,6 +120,7 @@ const RegistrationFormSection = () => {
e.preventDefault();
setError(null);
setLoading(true);
+ // @ts-ignore
register({ key, email }, { onError: onRegisterError, onSettled: () => setLoading(false) });
};
From 945b00ff3757bdfc6002fcc60cfcbfa4320054f7 Mon Sep 17 00:00:00 2001
From: Josef Reidinger
Date: Thu, 5 Dec 2024 10:17:36 +0100
Subject: [PATCH 19/47] fix reading global credentials file
---
service/lib/agama/registration.rb | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/service/lib/agama/registration.rb b/service/lib/agama/registration.rb
index 208c8290ed..655dc96c1d 100644
--- a/service/lib/agama/registration.rb
+++ b/service/lib/agama/registration.rb
@@ -34,7 +34,9 @@ class Registration
TARGET_DIR = "/run/agama/zypp"
private_constant :TARGET_DIR
- GLOBAL_CREDENTIALS_PATH = File.join(TARGET_DIR,
+ # FIXME: it should use TARGET_DIR instead of "/", but connect failed to read it even
+ # if fs_root passed as client params. Check with SCC guys why.
+ GLOBAL_CREDENTIALS_PATH = File.join("/",
SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE)
private_constant :GLOBAL_CREDENTIALS_PATH
From 35f303f7dc977165eaf7415bbed1996a508f48c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?=
Date: Thu, 5 Dec 2024 09:49:12 +0000
Subject: [PATCH 20/47] fix(web): mount registration alert only in valid paths
Otherwise, it will trigger the useProduct hook which might not be
available yet like in the case of the login path, in which the
QueryClient has not been initialized yet.
---
web/src/components/core/Page.tsx | 26 ++++++++++++-------
.../product/ProductRegistrationAlert.tsx | 2 ++
2 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/web/src/components/core/Page.tsx b/web/src/components/core/Page.tsx
index 5ddf83e5fa..0e9ad30de9 100644
--- a/web/src/components/core/Page.tsx
+++ b/web/src/components/core/Page.tsx
@@ -44,8 +44,9 @@ import { ProductRegistrationAlert } from "~/components/product";
import { _ } from "~/i18n";
import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
import flexStyles from "@patternfly/react-styles/css/utilities/Flex/flex";
-import { To, useNavigate } from "react-router-dom";
+import { To, useLocation, useNavigate } from "react-router-dom";
import { isEmpty, isObject } from "~/utils";
+import { REGISTRATION, SUPPORTIVE_PATHS } from "~/routes/paths";
/**
* Props accepted by Page.Section
@@ -279,14 +280,21 @@ const Submit = ({ children, ...props }: SubmitActionProps) => {
*
* @see [Patternfly Page/PageSection](https://www.patternfly.org/components/page#pagesection)
*/
-const Content = ({ children, ...pageSectionProps }: React.PropsWithChildren) => (
- <>
-
-
- {children}
-
- >
-);
+const Content = ({ children, ...pageSectionProps }: React.PropsWithChildren) => {
+ const location = useLocation();
+ const mountRegistrationAlert = ![...SUPPORTIVE_PATHS, REGISTRATION.root].includes(
+ location.pathname,
+ );
+
+ return (
+ <>
+ {mountRegistrationAlert && }
+
+ {children}
+
+ >
+ );
+};
/**
* Component for structuring an Agama page, built on top of PF/Page/PageGroup.
diff --git a/web/src/components/product/ProductRegistrationAlert.tsx b/web/src/components/product/ProductRegistrationAlert.tsx
index 1681daaf92..241ccaf6b6 100644
--- a/web/src/components/product/ProductRegistrationAlert.tsx
+++ b/web/src/components/product/ProductRegistrationAlert.tsx
@@ -35,6 +35,8 @@ export default function ProductRegistrationAlert() {
const { selectedProduct: product } = useProduct();
const registration = useRegistration();
+ // NOTE: it shouldn't be mounted in these paths, but let's prevent rendering
+ // if so just in case.
if ([...SUPPORTIVE_PATHS, REGISTRATION.root].includes(location.pathname)) return;
if (product.registration === "No" || !isEmpty(registration.key)) return;
From f385dda1e694836ee58ced5676c9b0bfcadc260b Mon Sep 17 00:00:00 2001
From: Josef Reidinger
Date: Thu, 5 Dec 2024 11:43:10 +0100
Subject: [PATCH 21/47] adapt tests for workaround
---
service/test/agama/registration_test.rb | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/service/test/agama/registration_test.rb b/service/test/agama/registration_test.rb
index d34255c2ba..c62e258a61 100644
--- a/service/test/agama/registration_test.rb
+++ b/service/test/agama/registration_test.rb
@@ -90,7 +90,9 @@
it "creates credentials file" do
expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
- .with("test-user", "12345", "/run/agama/zypp/etc/zypp/credentials.d/SCCcredentials")
+ .with("test-user", "12345", "/etc/zypp/credentials.d/SCCcredentials")
+ # TODO: when fixing suse-connect read of fsroot
+ # .with("test-user", "12345", "/run/agama/zypp/etc/zypp/credentials.d/SCCcredentials")
subject.register("11112222", email: "test@test.com")
end
From f532b37d1c2dbb59c15d3e7b62cb811629d638a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Thu, 5 Dec 2024 12:11:36 +0000
Subject: [PATCH 22/47] fix: use registration requirement capitalization
---
rust/agama-lib/src/software/model.rs | 1 +
web/src/components/layout/Sidebar.test.tsx | 2 +-
web/src/types/registration.ts | 2 +-
web/src/types/software.ts | 2 +-
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/rust/agama-lib/src/software/model.rs b/rust/agama-lib/src/software/model.rs
index 1678334473..32618542f1 100644
--- a/rust/agama-lib/src/software/model.rs
+++ b/rust/agama-lib/src/software/model.rs
@@ -60,6 +60,7 @@ pub struct RegistrationInfo {
utoipa::ToSchema,
)]
#[strum(serialize_all = "camelCase")]
+#[serde(rename_all = "camelCase")]
pub enum RegistrationRequirement {
/// Product does not require registration
#[default]
diff --git a/web/src/components/layout/Sidebar.test.tsx b/web/src/components/layout/Sidebar.test.tsx
index 346bf95317..fb47f7c809 100644
--- a/web/src/components/layout/Sidebar.test.tsx
+++ b/web/src/components/layout/Sidebar.test.tsx
@@ -38,7 +38,7 @@ const tw: Product = {
const sle: Product = {
id: "sle",
name: "SLE",
- registration: "Mandatory",
+ registration: "mandatory",
};
let selectedProduct: Product;
diff --git a/web/src/types/registration.ts b/web/src/types/registration.ts
index 4e70d22974..bb31e80435 100644
--- a/web/src/types/registration.ts
+++ b/web/src/types/registration.ts
@@ -22,7 +22,7 @@
type Registration = {
/** Registration requirement (i.e., "not-required", "optional", "mandatory") */
- requirement: string;
+ requirement: "no" | "optional" | "mandatory";
/** Registration code, if any */
code?: string;
/** Registration email, if any */
diff --git a/web/src/types/software.ts b/web/src/types/software.ts
index 59f5dcaeef..f35a9a609e 100644
--- a/web/src/types/software.ts
+++ b/web/src/types/software.ts
@@ -42,7 +42,7 @@ type Product = {
/** Product icon (e.g., "default.svg") */
icon?: string;
/** If product is registrable or not */
- registration: string;
+ registration: "no" | "optional" | "mandatory";
};
type PatternsSelection = { [key: string]: SelectedBy };
From 1d6c5a933ecd806790088f1f2d669e13110864e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?=
Date: Thu, 5 Dec 2024 12:32:25 +0000
Subject: [PATCH 23/47] fix(web): adapt the registration alert condition
---
web/src/components/product/ProductRegistrationAlert.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/components/product/ProductRegistrationAlert.tsx b/web/src/components/product/ProductRegistrationAlert.tsx
index 241ccaf6b6..7a119a6ebe 100644
--- a/web/src/components/product/ProductRegistrationAlert.tsx
+++ b/web/src/components/product/ProductRegistrationAlert.tsx
@@ -38,7 +38,7 @@ export default function ProductRegistrationAlert() {
// NOTE: it shouldn't be mounted in these paths, but let's prevent rendering
// if so just in case.
if ([...SUPPORTIVE_PATHS, REGISTRATION.root].includes(location.pathname)) return;
- if (product.registration === "No" || !isEmpty(registration.key)) return;
+ if (product.registration === "no" || !isEmpty(registration.key)) return;
return (
From 0bb69a75300624bca26cc0cb26ef13b4a2d981cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?=
Date: Thu, 5 Dec 2024 12:40:10 +0000
Subject: [PATCH 24/47] fix(web): show registration alert in all sections
But not rendering the link to the registration section when the user is
already there.
This commit also add the missing unit tests, covering all scenarios
implemented until now.
---
web/src/components/core/Page.tsx | 6 +-
.../product/ProductRegistrationAlert.test.tsx | 134 ++++++++++++++++++
.../product/ProductRegistrationAlert.tsx | 32 ++---
3 files changed, 152 insertions(+), 20 deletions(-)
create mode 100644 web/src/components/product/ProductRegistrationAlert.test.tsx
diff --git a/web/src/components/core/Page.tsx b/web/src/components/core/Page.tsx
index 0e9ad30de9..f72d6ba171 100644
--- a/web/src/components/core/Page.tsx
+++ b/web/src/components/core/Page.tsx
@@ -46,7 +46,7 @@ import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
import flexStyles from "@patternfly/react-styles/css/utilities/Flex/flex";
import { To, useLocation, useNavigate } from "react-router-dom";
import { isEmpty, isObject } from "~/utils";
-import { REGISTRATION, SUPPORTIVE_PATHS } from "~/routes/paths";
+import { SUPPORTIVE_PATHS } from "~/routes/paths";
/**
* Props accepted by Page.Section
@@ -282,9 +282,7 @@ const Submit = ({ children, ...props }: SubmitActionProps) => {
*/
const Content = ({ children, ...pageSectionProps }: React.PropsWithChildren) => {
const location = useLocation();
- const mountRegistrationAlert = ![...SUPPORTIVE_PATHS, REGISTRATION.root].includes(
- location.pathname,
- );
+ const mountRegistrationAlert = !SUPPORTIVE_PATHS.includes(location.pathname);
return (
<>
diff --git a/web/src/components/product/ProductRegistrationAlert.test.tsx b/web/src/components/product/ProductRegistrationAlert.test.tsx
new file mode 100644
index 0000000000..856e47737a
--- /dev/null
+++ b/web/src/components/product/ProductRegistrationAlert.test.tsx
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) [2024] SUSE LLC
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, contact SUSE LLC.
+ *
+ * To contact SUSE LLC about this file by physical or electronic mail, you may
+ * find current contact information at www.suse.com.
+ */
+
+import React from "react";
+import { screen } from "@testing-library/react";
+import { installerRender, mockRoutes } from "~/test-utils";
+import ProductRegistrationAlert from "./ProductRegistrationAlert";
+import { Product, RegistrationInfo } from "~/types/software";
+import { useProduct, useRegistration } from "~/queries/software";
+import { PRODUCT, REGISTRATION, ROOT, USER } from "~/routes/paths";
+
+jest.mock("~/components/core/ChangeProductLink", () => () =>