diff --git a/doc/dbus/bus/org.opensuse.Agama.Manager1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Manager1.bus.xml
deleted file mode 100644
index 945966dedd..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama.Manager1.bus.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/bus/org.opensuse.Agama.Security.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Security.bus.xml
deleted file mode 120000
index d0feb248d1..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama.Security.bus.xml
+++ /dev/null
@@ -1 +0,0 @@
-org.opensuse.Agama.Software1.bus.xml
\ No newline at end of file
diff --git a/doc/dbus/bus/org.opensuse.Agama.Software1.Product.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Software1.Product.bus.xml
deleted file mode 100644
index 360c277ef0..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama.Software1.Product.bus.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/bus/org.opensuse.Agama.Software1.Proposal.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Software1.Proposal.bus.xml
deleted file mode 100644
index b501f0d052..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama.Software1.Proposal.bus.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/bus/org.opensuse.Agama.Software1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Software1.bus.xml
deleted file mode 100644
index 959cc521b8..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama.Software1.bus.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/bus/org.opensuse.Agama.Users1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Users1.bus.xml
deleted file mode 100644
index fcb0439338..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama.Users1.bus.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/bus/org.opensuse.Agama1.Manager.bus.xml b/doc/dbus/bus/org.opensuse.Agama1.Manager.bus.xml
deleted file mode 100644
index 060c3de4d2..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama1.Manager.bus.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/bus/org.opensuse.Agama1.Progress.bus.xml b/doc/dbus/bus/org.opensuse.Agama1.Progress.bus.xml
deleted file mode 120000
index d0feb248d1..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama1.Progress.bus.xml
+++ /dev/null
@@ -1 +0,0 @@
-org.opensuse.Agama.Software1.bus.xml
\ No newline at end of file
diff --git a/doc/dbus/bus/org.opensuse.Agama1.Registration.bus.xml b/doc/dbus/bus/org.opensuse.Agama1.Registration.bus.xml
deleted file mode 120000
index 9cfd11ce93..0000000000
--- a/doc/dbus/bus/org.opensuse.Agama1.Registration.bus.xml
+++ /dev/null
@@ -1 +0,0 @@
-org.opensuse.Agama.Software1.Product.bus.xml
\ No newline at end of file
diff --git a/doc/dbus/bus/seed.sh b/doc/dbus/bus/seed.sh
index a2ef64f46c..d7a1fb44e7 100755
--- a/doc/dbus/bus/seed.sh
+++ b/doc/dbus/bus/seed.sh
@@ -27,13 +27,4 @@ look() {
>$DD.$1.bus.xml
}
-look Manager1
-look Software1
-look Software1.Proposal
look Storage1
-
-abusctl introspect --xml-interface \
- ${DD}.Manager1 \
- ${SS}/Users1 |
- cleanup \
- >${DD}.Users1.bus.xml
diff --git a/doc/dbus/org.opensuse.Agama.Security.doc.xml b/doc/dbus/org.opensuse.Agama.Security.doc.xml
deleted file mode 100644
index 6b183e682b..0000000000
--- a/doc/dbus/org.opensuse.Agama.Security.doc.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/org.opensuse.Agama.Software1.Product.doc.xml b/doc/dbus/org.opensuse.Agama.Software1.Product.doc.xml
deleted file mode 100644
index 165a67094b..0000000000
--- a/doc/dbus/org.opensuse.Agama.Software1.Product.doc.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/org.opensuse.Agama.Software1.doc.xml b/doc/dbus/org.opensuse.Agama.Software1.doc.xml
deleted file mode 100644
index 215acb0e81..0000000000
--- a/doc/dbus/org.opensuse.Agama.Software1.doc.xml
+++ /dev/null
@@ -1,137 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/org.opensuse.Agama.Users1.doc.xml b/doc/dbus/org.opensuse.Agama.Users1.doc.xml
deleted file mode 100644
index 50c3971583..0000000000
--- a/doc/dbus/org.opensuse.Agama.Users1.doc.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/org.opensuse.Agama1.Manager.doc.xml b/doc/dbus/org.opensuse.Agama1.Manager.doc.xml
deleted file mode 100644
index 13dd26b9a5..0000000000
--- a/doc/dbus/org.opensuse.Agama1.Manager.doc.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/org.opensuse.Agama1.Progress.doc.xml b/doc/dbus/org.opensuse.Agama1.Progress.doc.xml
deleted file mode 100644
index 1a1a3ed0e0..0000000000
--- a/doc/dbus/org.opensuse.Agama1.Progress.doc.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus/org.opensuse.Agama1.Registration.doc.xml b/doc/dbus/org.opensuse.Agama1.Registration.doc.xml
deleted file mode 100644
index 750c11ee7d..0000000000
--- a/doc/dbus/org.opensuse.Agama1.Registration.doc.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/dbus_api.md b/doc/dbus_api.md
index 7352bf2d12..7e5ae4c7a0 100644
--- a/doc/dbus_api.md
+++ b/doc/dbus_api.md
@@ -44,15 +44,6 @@ We use these resources to get more familiar with D-Bus API designing.
- network manager design https://people.freedesktop.org/~lkundrak/nm-docs/spec.html
- anakonda D-Bus API ( spread in `*_interface.py` files https://github.com/rhinstaller/anaconda/tree/master/pyanaconda/modules
-## Base Product
-
-Iface: o.o.Agama.Software1
-
-See the new-style [reference][lang-ref] ([source][lang-src]).
-
-[lang-ref]: https://opensuse.github.io/agama/dbus/ref-org.opensuse.Agama.Software1.html
-[lang-src]: dbus/org.opensuse.Agama.Software1.doc.xml
-
## `org.opensuse.Agama.Storage1` Service
Service for managing storage devices.
@@ -594,17 +585,3 @@ Summary readable a{s(uub)}
##### Signals
* `PropertiesChanged`, as standard from `org.freedesktop.DBus.Properties`.
-
-## Users
-
-See the new-style [reference][usr-ref] ([source][usr-src]).
-
-[usr-ref]: https://opensuse.github.io/agama/dbus/ref-org.opensuse.Agama.Users1.html
-[usr-src]: dbus/org.opensuse.Agama.Users1.doc.xml
-
-## Manager
-
-See the new-style [reference][mgr-ref] ([source][mgr-src]).
-
-[mgr-ref]: https://opensuse.github.io/agama/dbus/ref-org.opensuse.Agama1.Manager.html
-[mgr-src]: dbus/org.opensuse.Agama1.Manager.doc.xml
diff --git a/service/Gemfile.lock b/service/Gemfile.lock
index 7182a681bb..87e0536f53 100755
--- a/service/Gemfile.lock
+++ b/service/Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- agama-yast (17.devel564.e889a82fd)
+ agama-yast (17.devel904.666a28170)
cfa (~> 1.0.2)
cfa_grub2 (~> 2.0.0)
cheetah (~> 1.0.0)
diff --git a/service/agama-yast.gemspec b/service/agama-yast.gemspec
index 438f65ccda..86ccb6d74b 100644
--- a/service/agama-yast.gemspec
+++ b/service/agama-yast.gemspec
@@ -40,7 +40,7 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/agama-project/agama"
spec.license = "GPL-2.0-only"
spec.files = Dir["lib/**/*.rb", "bin/*", "share/*", "conf.d/*", "install.sh"]
- spec.executables = ["agamactl", "agama-proxy-setup", "agama-autoyast"]
+ spec.executables = ["agamactl", "agama-autoyast"]
spec.metadata = { "rubygems_mfa_required" => "true" }
spec.required_ruby_version = ">= 2.5.0"
diff --git a/service/agama-yast.spec.in b/service/agama-yast.spec.in
index 335e1e736f..177147e2d2 100644
--- a/service/agama-yast.spec.in
+++ b/service/agama-yast.spec.in
@@ -80,7 +80,6 @@ sh "%{SOURCE2}" "%{SOURCE1}"
%{_datadir}/dbus-1/agama-services/org.opensuse.Agama*.service
%{_unitdir}/agama.service
%{_unitdir}/agama-dbus-monitor.service
-%{_unitdir}/agama-proxy-setup.service
%dir %{_datadir}/agama
%dir %{_datadir}/agama/conf.d
%{_datadir}/agama/conf.d
diff --git a/service/bin/agama-proxy-setup b/service/bin/agama-proxy-setup
deleted file mode 100755
index 8d16341175..0000000000
--- a/service/bin/agama-proxy-setup
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-require "agama/proxy_setup"
-
-Agama::ProxySetup.instance.run
diff --git a/service/bin/agamactl b/service/bin/agamactl
index a497f9027a..348a392172 100755
--- a/service/bin/agamactl
+++ b/service/bin/agamactl
@@ -21,7 +21,6 @@
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.
-# TEMPORARY overwrite of Y2DIR to use DBus for communication with dependent yast modules
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
# Set the PATH to a known value
@@ -55,10 +54,6 @@ end
# @param name [Symbol] Service name
# @see ORDERED_SERVICES
def start_service(name)
- general_y2dir = File.expand_path("../lib/agama/dbus/y2dir", __dir__)
- module_y2dir = File.expand_path("../lib/agama/dbus/y2dir/#{name}", __dir__)
- ENV["Y2DIR"] = [ENV.fetch("Y2DIR", nil), module_y2dir, general_y2dir].compact.join(":")
-
logger = logger_for(name)
service_runner = Agama::DBus::ServiceRunner.new(name, logger: logger)
service_runner.run
diff --git a/service/lib/agama/dbus.rb b/service/lib/agama/dbus.rb
index 23bfc6ee71..6c5239d8af 100644
--- a/service/lib/agama/dbus.rb
+++ b/service/lib/agama/dbus.rb
@@ -25,7 +25,4 @@ module DBus
end
end
-require "agama/dbus/manager"
-require "agama/dbus/software"
require "agama/dbus/storage"
-require "agama/dbus/users"
diff --git a/service/lib/agama/dbus/clients/manager.rb b/service/lib/agama/dbus/clients/manager.rb
deleted file mode 100644
index 2ef82a0cd5..0000000000
--- a/service/lib/agama/dbus/clients/manager.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/dbus/clients/base"
-require "agama/dbus/clients/with_service_status"
-require "agama/dbus/clients/with_progress"
-require "agama/dbus/manager"
-require "agama/installation_phase"
-
-module Agama
- module DBus
- module Clients
- # D-Bus client for manager service
- class Manager < Base
- include WithServiceStatus
- include WithProgress
-
- def initialize
- super
-
- @dbus_object = service["/org/opensuse/Agama/Manager1"]
- @dbus_object.introspect
- end
-
- def service_name
- @service_name ||= "org.opensuse.Agama.Manager1"
- end
-
- def probe
- dbus_object.Probe
- end
-
- # Starts the installation
- def commit
- dbus_object.Commit
- end
-
- def current_installation_phase
- dbus_phase = dbus_object["org.opensuse.Agama.Manager1"]["CurrentInstallationPhase"]
-
- case dbus_phase
- when DBus::Manager::STARTUP_PHASE
- InstallationPhase::STARTUP
- when DBus::Manager::CONFIG_PHASE
- InstallationPhase::CONFIG
- when DBus::Manager::INSTALL_PHASE
- InstallationPhase::INSTALL
- when DBus::Manager::FINISH_PHASE
- InstallationPhase::FINISH
- end
- end
-
- private
-
- # @return [::DBus::Object]
- attr_reader :dbus_object
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/clients/network.rb b/service/lib/agama/dbus/clients/network.rb
deleted file mode 100644
index 0b8ff686ca..0000000000
--- a/service/lib/agama/dbus/clients/network.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/dbus/clients/base"
-
-module Agama
- module DBus
- module Clients
- # D-Bus client for the network service
- #
- # This client is intended to be used to watch for changes in
- # NetworkManager because Agama does not implement its own network
- # service. The configuration is done directly in the UI or through
- # `nmcli`.
- class Network < Base
- def initialize
- super
-
- @dbus_object = service["/org/freedesktop/NetworkManager"]
- @dbus_object.introspect
- @nm_iface = @dbus_object["org.freedesktop.NetworkManager"]
- end
-
- def service_name
- @service_name ||= "org.freedesktop.NetworkManager"
- end
-
- CONNECTED_NM_STATE = 70
- private_constant :CONNECTED_NM_STATE
-
- # Registers a callback to call when connectivity state changes
- #
- # The block receives a boolean argument which is true when the network
- # connection is working or false otherwise.
- #
- # @param [Proc] block
- def on_connection_changed(&block)
- @nm_iface.on_signal("StateChanged") do |nm_state|
- block.call(nm_state == CONNECTED_NM_STATE)
- end
- end
-
- private
-
- def bus
- @bus ||= ::DBus::SystemBus.instance
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/clients/software.rb b/service/lib/agama/dbus/clients/software.rb
deleted file mode 100644
index 4e89a1c92a..0000000000
--- a/service/lib/agama/dbus/clients/software.rb
+++ /dev/null
@@ -1,215 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2024] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/dbus/clients/base"
-require "agama/dbus/clients/with_issues"
-require "agama/dbus/clients/with_locale"
-require "agama/dbus/clients/with_progress"
-require "agama/dbus/clients/with_service_status"
-
-module Agama
- module DBus
- module Clients
- # D-Bus client for software configuration
- class Software < Base
- include WithLocale
- include WithIssues
- include WithProgress
- include WithServiceStatus
-
- TYPES = [:package, :pattern].freeze
- private_constant :TYPES
-
- # @note This client is singleton because ruby-dbus does not work properly with several
- # instances of the same client.
- def self.instance
- @instance ||= new
- end
-
- # @return [String]
- def service_name
- @service_name ||= "org.opensuse.Agama.Software1"
- end
-
- # Available products for the installation
- #
- # @return [Array>] name and display name of each product
- def available_products
- dbus_product["org.opensuse.Agama.Software1.Product"]["AvailableProducts"].map do |l|
- l[0..1]
- end
- end
-
- # Product selected to install
- #
- # @return [String, nil] name of the product
- def selected_product
- product = dbus_product["org.opensuse.Agama.Software1.Product"]["SelectedProduct"]
- return nil if product.empty?
-
- product
- end
-
- # Selects the product to install
- #
- # @param name [String]
- def select_product(name)
- dbus_product.SelectProduct(name)
- end
-
- # Starts the probing process
- #
- # If a block is given, the method returns immediately and the probing is performed in an
- # asynchronous way.
- #
- # @param done [Proc] Block to execute once the probing is done
- def probe(&done)
- dbus_object.Probe(&done)
- end
-
- # Performs the packages installation
- def install
- dbus_object.Install
- end
-
- # Makes the software proposal
- def propose
- dbus_object.Propose
- end
-
- # Finishes the software installation
- def finish
- dbus_object.Finish
- end
-
- # Determine whether the given tags are provided by the selected packages
- #
- # @param tags [Array] Tags to search for (package names, requires/provides, or file
- # names)
- # @return [Array] An array containing whether each tag is selected or not
- def provisions_selected?(tags)
- dbus_object.ProvisionsSelected(tags)
- end
-
- # Determines whether a package is installed.
- #
- # @param name [String] Package name.
- # @return [Boolean]
- def package_installed?(name)
- dbus_object.IsPackageInstalled(name)
- end
-
- # Determines whether a package is available.
- #
- # @param name [String] Package name.
- # @return [Boolean]
- def package_available?(name)
- dbus_object.IsPackageAvailable(name)
- end
-
- # Add the given list of resolvables to the packages proposal
- #
- # @param unique_id [String] Unique identifier for the resolvables list
- # @param type [Symbol] Resolvables type (:package or :pattern)
- # @param resolvables [Array] Resolvables to add
- # @param [Boolean] optional True for optional list, false (the default) for
- # the required list
- def add_resolvables(unique_id, type, resolvables, optional: false)
- dbus_proposal.AddResolvables(unique_id, TYPES.index(type), resolvables, optional)
- end
-
- # Returns a list of resolvables
- #
- # @param unique_id [String] Unique identifier for the resolvables list
- # @param type [Symbol] Resolvables type (:package or :pattern)
- # @param [Boolean] optional True for optional list, false (the default) for
- # the required list
- # @return [Array] Resolvables
- def get_resolvables(unique_id, type, optional: false)
- dbus_proposal.GetResolvables(unique_id, TYPES.index(type), optional).first
- end
-
- # Replace a list of resolvables in the packages proposal
- #
- # @param unique_id [String] Unique identifier for the resolvables list
- # @param type [Symbol] Resolvables type (:package or :pattern)
- # @param resolvables [Array] List of resolvables
- # @param [Boolean] optional True for optional list, false (the default) for
- # the required list
- def set_resolvables(unique_id, type, resolvables, optional: false)
- dbus_proposal.SetResolvables(unique_id, TYPES.index(type), resolvables, optional)
- end
-
- # Remove resolvables from a list
- #
- # @param unique_id [String] Unique identifier for the resolvables list
- # @param type [Symbol] Resolvables type (:package or :pattern)
- # @param resolvables [Array] Resolvables to remove
- # @param [Boolean] optional True for optional list, false (the default) for
- # the required list
- def remove_resolvables(unique_id, type, resolvables, optional: false)
- dbus_proposal.RemoveResolvables(unique_id, TYPES.index(type), resolvables, optional)
- end
-
- # Registers a callback to run when the product changes
- #
- # @param block [Proc] Callback to run when a product is selected
- def on_product_selected(&block)
- on_properties_change(dbus_product) do |_, changes, _|
- product = changes["SelectedProduct"]
- block.call(product) unless product.nil?
- end
- end
-
- # Registers a callback to run when the software is probed.
- #
- # @param block [Proc]
- def on_probe_finished(&block)
- subscribe(dbus_object, "org.opensuse.Agama.Software1", "ProbeFinished", &block)
- end
-
- private
-
- # @return [::DBus::Object]
- attr_reader :dbus_object
-
- # @return [::DBus::Object]
- attr_reader :dbus_product
-
- # @return [::DBus::Object]
- attr_reader :dbus_proposal
-
- def initialize
- super
-
- @dbus_object = service["/org/opensuse/Agama/Software1"]
- @dbus_object.introspect
-
- @dbus_product = service["/org/opensuse/Agama/Software1/Product"]
- @dbus_product.introspect
-
- @dbus_proposal = service["/org/opensuse/Agama/Software1/Proposal"]
- @dbus_proposal.introspect
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/manager.rb b/service/lib/agama/dbus/manager.rb
deleted file mode 100644
index b319e04f21..0000000000
--- a/service/lib/agama/dbus/manager.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2021-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "dbus"
-require "agama/autoyast/converter"
-require "agama/dbus/base_object"
-require "agama/dbus/interfaces/locale"
-require "agama/dbus/interfaces/progress"
-require "agama/dbus/interfaces/service_status"
-require "agama/dbus/with_progress"
-require "agama/dbus/with_service_status"
-require "agama/manager"
-
-module Agama
- module DBus
- # D-Bus object to manage the installation process
- class Manager < BaseObject
- include WithProgress
- include WithServiceStatus
- include Interfaces::Progress
- include Interfaces::ServiceStatus
- include Interfaces::Locale
-
- PATH = "/org/opensuse/Agama/Manager1"
- private_constant :PATH
-
- # Constructor
- #
- # @param backend [Agama::Manager]
- # @param logger [Logger]
- def initialize(backend, logger)
- super(PATH, logger: logger)
- @backend = backend
- register_callbacks
- register_progress_callbacks
- register_service_status_callbacks
- end
-
- MANAGER_INTERFACE = "org.opensuse.Agama.Manager1"
- private_constant :MANAGER_INTERFACE
-
- STARTUP_PHASE = 0
- CONFIG_PHASE = 1
- INSTALL_PHASE = 2
- FINISH_PHASE = 3
-
- dbus_interface MANAGER_INTERFACE do
- dbus_method(:Probe, "in data:a{sv}") { |_| config_phase }
- dbus_method(:Reprobe, "in data:a{sv}") { |_| config_phase(reprobe: true) }
- dbus_method(:Commit, "") { install_phase }
- dbus_method(:CanInstall, "out result:b") { can_install? }
- dbus_method(:CollectLogs, "out tarball_filesystem_path:s") { collect_logs }
- dbus_method(:Finish, "in method:s, out result:b") { |m| finish_phase(m) }
- dbus_reader :installation_phases, "aa{sv}"
- dbus_reader :current_installation_phase, "u"
- dbus_reader :iguana_backend, "b"
- dbus_reader :busy_services, "as"
- end
-
- # Runs the config phase
- #
- # @param reprobe [Boolean] Whether a reprobe should be done instead of a probe.
- def config_phase(reprobe: false)
- safe_run do
- busy_while { backend.config_phase(reprobe: reprobe) }
- end
- end
-
- # Runs the install phase
- def install_phase
- raise ::DBus::Error, "Installation settings are invalid" unless backend.valid?
-
- safe_run do
- busy_while { backend.install_phase }
- end
- end
-
- # Determines whether the installation can start
- #
- # @return [Boolean]
- def can_install?
- backend.valid?
- end
-
- # Collects the YaST logs
- def collect_logs
- backend.collect_logs
- end
-
- # Last action for the installer
- #
- # @param method [String]
- # @return [Boolean]
- def finish_phase(method)
- backend.finish_installation(method)
- end
-
- # Description of all possible installation phase values
- #
- # @return [Array]
- def installation_phases
- [
- { "id" => STARTUP_PHASE, "label" => "startup" },
- { "id" => CONFIG_PHASE, "label" => "config" },
- { "id" => INSTALL_PHASE, "label" => "install" },
- { "id" => FINISH_PHASE, "label" => "finish" }
- ]
- end
-
- # Current value of the installation phase
- #
- # @return [Integer]
- def current_installation_phase
- return STARTUP_PHASE if backend.installation_phase.startup?
- return CONFIG_PHASE if backend.installation_phase.config?
- return INSTALL_PHASE if backend.installation_phase.install?
- return FINISH_PHASE if backend.installation_phase.finish?
- end
-
- # States whether installation runs on iguana
- def iguana_backend
- backend.iguana?
- end
-
- # Name of the services that are currently busy
- #
- # @return [Array]
- def busy_services
- backend.busy_services
- end
-
- # Redefines #service_status to use the one from the backend
- #
- # @return [DBus::ServiceStatus]
- def service_status
- backend.service_status
- end
-
- def locale=(locale)
- safe_run do
- busy_while { backend.locale = locale }
- end
- end
-
- private
-
- # @return [Agama::Manager]
- attr_reader :backend
-
- # Executes the given block only if the service is idle
- #
- # @note The service still dispatches messages while waiting for a D-Bus answer.
- #
- # @param block [Proc]
- def safe_run(&block)
- raise busy_error if service_status.busy?
-
- block.call
- end
-
- def busy_error
- ::DBus.error("org.opensuse.Agama1.Error.Busy")
- end
-
- # Registers callback to be called
- def register_callbacks
- backend.installation_phase.on_change do
- dbus_properties_changed(MANAGER_INTERFACE,
- { "CurrentInstallationPhase" => current_installation_phase }, [])
- end
-
- backend.on_services_status_change do
- dbus_properties_changed(MANAGER_INTERFACE, { "BusyServices" => busy_services }, [])
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/manager_service.rb b/service/lib/agama/dbus/manager_service.rb
deleted file mode 100644
index 5804d9623b..0000000000
--- a/service/lib/agama/dbus/manager_service.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "dbus"
-require "agama/manager"
-require "agama/users"
-require "agama/dbus/bus"
-require "agama/dbus/manager"
-require "agama/dbus/users"
-
-module Agama
- module DBus
- # D-Bus service (org.opensuse.Agama.Manager1)
- #
- # It connects to the system D-Bus and answers requests on objects below
- # `/org/opensuse/Agama1`.
- class ManagerService
- # D-Bus service (org.opensuse.Agama.Manager1)
- SERVICE_NAME = "org.opensuse.Agama.Manager1"
- private_constant :SERVICE_NAME
-
- # Agama D-Bus
- #
- # @return [::DBus::BusConnection]
- attr_reader :bus
-
- # Installation manager
- #
- # @return [Agama::Manager]
- attr_reader :manager
-
- # Users manager
- #
- # @return [Agama::Users]
- attr_reader :users
-
- # @param config [Config] Configuration
- # @param logger [Logger]
- def initialize(config, logger = nil)
- @config = config
- @manager = Agama::Manager.new(config, logger)
- @users = Agama::Users.new(logger)
- @logger = logger || Logger.new($stdout)
- @bus = Bus.current
- end
-
- # Initializes and exports the D-Bus API
- #
- # @note The service runs its startup phase
- def start
- export
- manager.on_progress_change { dispatch } # make single thread more responsive
- manager.startup_phase
- end
-
- # Exports the installer object through the D-Bus service
- def export
- # manager service initialization
- dbus_objects.each { |o| service.export(o) }
-
- paths = dbus_objects.map(&:path).join(", ")
- logger.info "Exported #{paths} objects"
- end
-
- # Call this from some main loop to dispatch the D-Bus messages
- def dispatch
- bus.dispatch_message_queue
- end
-
- private
-
- # @return [Logger]
- attr_reader :logger
-
- # @return [Config]
- attr_reader :config
-
- # @return [::DBus::ObjectServer]
- def service
- @service ||= bus.request_service(SERVICE_NAME)
- end
-
- # @return [Array<::DBus::Object>]
- def dbus_objects
- @dbus_objects ||= [
- manager_dbus,
- users_dbus
- ]
- end
-
- def manager_dbus
- @manager_dbus ||= Agama::DBus::Manager.new(manager, logger)
- end
-
- def users_dbus
- @users_dbus ||= Agama::DBus::Users.new(manager.users, logger)
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/software.rb b/service/lib/agama/dbus/software.rb
deleted file mode 100644
index ace66f64fc..0000000000
--- a/service/lib/agama/dbus/software.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-module Agama
- module DBus
- # Name space for software D-Bus classes
- module Software
- end
- end
-end
-
-require "agama/dbus/software/manager"
-require "agama/dbus/software/product"
-require "agama/dbus/software/proposal"
diff --git a/service/lib/agama/dbus/software/manager.rb b/service/lib/agama/dbus/software/manager.rb
deleted file mode 100644
index fde826934d..0000000000
--- a/service/lib/agama/dbus/software/manager.rb
+++ /dev/null
@@ -1,280 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "dbus"
-require "agama/dbus/base_object"
-require "agama/dbus/clients/network"
-require "agama/dbus/interfaces/issues"
-require "agama/dbus/interfaces/locale"
-require "agama/dbus/interfaces/progress"
-require "agama/dbus/interfaces/service_status"
-require "agama/dbus/with_progress"
-require "agama/dbus/with_service_status"
-
-module Agama
- module DBus
- module Software
- # D-Bus object to manage software installation
- class Manager < BaseObject
- include WithProgress
- include WithServiceStatus
- include Interfaces::Progress
- include Interfaces::ServiceStatus
- include Interfaces::Issues
- include Interfaces::Locale
-
- PATH = "/org/opensuse/Agama/Software1"
- private_constant :PATH
-
- # Constructor
- #
- # @param backend [Agama::Software]
- # @param logger [Logger]
- def initialize(backend, logger)
- super(PATH, logger: logger)
- @backend = backend
- register_callbacks
- register_progress_callbacks
- register_service_status_callbacks
- @selected_patterns = {}
- @conflicts = []
- end
-
- # List of software related issues, see {DBus::Interfaces::Issues}
- #
- # @return [Array]
- def issues
- backend.issues
- end
-
- def only_required
- case backend.proposal.only_required
- when nil then 0
- when false then 1
- when true then 2
- else
- @logger.warn(
- "Unexpected value in only_required #{backend.proposal.only_required.inspect}"
- )
- 0
- end
- end
-
- def only_required=(flag)
- value = case flag
- when 0 then nil
- when 1 then false
- when 2 then true
- else
- @logger.warn "Unexpected value in only_required #{flag.inspect}"
- end
- backend.proposal.only_required = value
- # propose again after changing solver flag
- propose
- end
-
- SOFTWARE_INTERFACE = "org.opensuse.Agama.Software1"
- private_constant :SOFTWARE_INTERFACE
-
- dbus_interface SOFTWARE_INTERFACE do
- # Flag for proposing required only dependencies
- # Propose is called automatically whenever the value is assigned.
- # value mapping 0 for not set, 1 for false and 2 for true
- dbus_accessor :only_required, "u"
-
- # array of repository properties: pkg-bindings ID, alias, name, URL, product dir, enabled
- # and loaded flag
- dbus_method :ListRepositories, "out Result:a(issssbb)" do
- [
- 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
-
- # set user specified repositories properties
- dbus_method :SetUserRepositories, "in repos:aa{sv}" do |repos|
- @logger.info "Setting user repositories #{repos.inspect}"
- backend.repositories.user_repositories = repos
- end
-
- # set user specified repositories properties
- dbus_method :ListUserRepositories, "out repos:aa{sv}" do
- [backend.repositories.user_repositories]
- 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|
- [
- backend.patterns(filtered).each_with_object({}) do |pattern, result|
- # make sure all attributes are already preloaded, adjust the "patterns" method
- # in service/lib/agama/software/manager.rb when changing this list
- value = [
- pattern.category,
- pattern.description,
- pattern.icon,
- pattern.summary,
- pattern.order
- ]
- result[pattern.name] = value
- end
- ]
- end
-
- # selected patterns is hash with pattern name as id and 0 for user selected and
- # 1 for auto selected. Can be extended in future e.g. for mandatory patterns
- dbus_reader_attr_accessor :selected_patterns, "a{sy}"
-
- dbus_method(:AddPattern, "in id:s, out result:b") { |p| backend.add_pattern(p) }
- dbus_method(:RemovePattern, "in id:s, out result:b") { |p| backend.remove_pattern(p) }
- dbus_method(:SetUserPatterns, "in add:as, in remove:as, out wrong:as") do |add, remove|
- [backend.assign_patterns(add, remove)]
- end
-
- dbus_reader_attr_accessor :conflicts, "a(ussa(uss))"
-
- dbus_method :SolveConflicts, "in solutions:a(uu)" do |solutions|
- ret = backend.proposal.solve_conflicts(solutions)
- # update the user selected patterns, patterns might be unselected as
- # part of the conflict resolution
- backend.update_selected_patterns
- ret
- end
-
- dbus_method :ProvisionsSelected, "in Provisions:as, out Result:ab" do |provisions|
- [provisions.map { |p| backend.provision_selected?(p) }]
- end
-
- dbus_method :IsPackageInstalled, "in Name:s, out Result:b" do |name|
- backend.package_installed?(name)
- end
-
- dbus_method :IsPackageAvailable, "in name:s, out result:b" do |name|
- backend.package_available?(name)
- end
-
- dbus_method(:UsedDiskSpace, "out SpaceSize:s") { backend.used_disk_space }
-
- dbus_signal(:ProbeFinished)
-
- dbus_method(:Probe) { probe }
- dbus_method(:Propose) { propose }
- dbus_method(:Install) { install }
- dbus_method(:Finish) { finish }
- end
-
- def probe
- busy_while { backend.probe }
- self.ProbeFinished
- end
-
- def propose
- busy_while { backend.propose }
-
- nil # explicit nil as return value
- end
-
- def install
- busy_while { backend.install }
- end
-
- def finish
- busy_while { backend.finish }
- end
-
- def locale=(locale)
- busy_while do
- backend.locale = locale
- end
- end
-
- def ssl_fingerprints
- ssl_storage.fingerprints.map { |f| [f.sum, f.value] }
- end
-
- def ssl_fingerprints=(new_fps)
- fps = new_fps.map { |f| SSL::Fingerprint.new(f[0], f[1]) }
- ssl_storage.fingerprints.replace(fps)
- end
-
- SECURITY_INTERFACE = "org.opensuse.Agama.Security"
- private_constant :SECURITY_INTERFACE
-
- dbus_interface SECURITY_INTERFACE do
- # List of SSL fingerprints serialized into type and its value
- dbus_accessor :ssl_fingerprints, "a(ss)"
- end
-
- private
-
- def ssl_storage
- SSL::Storage.instance
- end
- # @return [Agama::Software]
- attr_reader :backend
-
- # Registers callback to be called
- def register_callbacks
- nm_client = Agama::DBus::Clients::Network.new
- nm_client.on_connection_changed do |connected|
- probe if connected
- end
-
- backend.on_selected_patterns_change do
- self.selected_patterns = compute_patterns
- end
-
- backend.proposal.on_conflicts_change do |conflicts|
- self.conflicts = conflicts.map do |conflict|
- [
- conflict["id"], conflict["description"], conflict["details"] || "",
- conflict["solutions"].map do |solution|
- [solution["id"], solution["description"], solution["details"] || ""]
- end
- ]
- end
- end
-
- backend.on_issues_change { issues_properties_changed }
- end
-
- USER_SELECTED_PATTERN = 0
- AUTO_SELECTED_PATTERN = 1
- def compute_patterns
- patterns = {}
- user_selected, auto_selected = backend.selected_patterns
- user_selected.each { |p| patterns[p] = USER_SELECTED_PATTERN }
- auto_selected.each { |p| patterns[p] = AUTO_SELECTED_PATTERN }
-
- patterns
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/software/product.rb b/service/lib/agama/dbus/software/product.rb
deleted file mode 100644
index 9e373cab48..0000000000
--- a/service/lib/agama/dbus/software/product.rb
+++ /dev/null
@@ -1,411 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "dbus"
-require "suse/connect"
-require "agama/dbus/base_object"
-require "agama/dbus/interfaces/issues"
-require "agama/errors"
-require "agama/registration"
-
-module Agama
- module DBus
- module Software
- # D-Bus object to manage product configuration.
- class Product < BaseObject
- include Interfaces::Issues
-
- PATH = "/org/opensuse/Agama/Software1/Product"
- private_constant :PATH
-
- # @param backend [Agama::Software::Manager]
- # @param logger [Logger]
- def initialize(backend, logger)
- super(PATH, logger: logger)
- @backend = backend
- @logger = logger
- register_callbacks
- end
-
- # List of issues, see {DBus::Interfaces::Issues}.
- #
- # @return [Array]
- def issues
- backend.product_issues
- end
-
- def available_products
- backend.products.map do |product|
- data = {
- "description" => product.localized_description,
- "icon" => product.icon,
- "registration" => product.registration
- }
- data["license"] = product.license if product.license
- [
- product.id,
- product.display_name,
- data
- ]
- end
- end
-
- # Returns the selected base product.
- #
- # @return [String] Product ID or an empty string if no product is selected
- def selected_product
- backend.product&.id || ""
- end
-
- # Selects a product.
- #
- # @param id [String] Product id.
- # @return [Array(Integer, String)] Result code and a description.
- # Possible result codes:
- # 0: success
- # 1: already selected
- # 2: deregister first
- # 3: unknown product
- def select_product(id)
- if backend.product&.id == id
- [1, "Product is already selected"]
- elsif backend.registration.registered
- [2, "Current product must be deregistered first"]
- else
- backend.select_product(id)
- [0, ""]
- end
- rescue ArgumentError
- [3, "Unknown product"]
- end
-
- PRODUCT_INTERFACE = "org.opensuse.Agama.Software1.Product"
- private_constant :PRODUCT_INTERFACE
-
- dbus_interface PRODUCT_INTERFACE do
- dbus_method :AvailableProducts, "out result:a(ssa{sv})" do
- [available_products]
- end
-
- dbus_reader :selected_product, "s"
-
- dbus_method :SelectProduct, "in id:s, out result:(us)" do |id|
- logger.info "Selecting product #{id}"
-
- code, description = select_product(id)
-
- if code == 0
- dbus_properties_changed(PRODUCT_INTERFACE, { "SelectedProduct" => id }, [])
- # FIXME: Product issues might change after selecting a product. Nevertheless,
- # #on_issues_change callbacks should be used for emitting issues signals, ensuring
- # they are emitted every time the backend changes its issues. Currently,
- # #on_issues_change cannot be used for product issues. Note that Software::Manager
- # backend takes care of both software and product issues. And it already uses
- # #on_issues_change callbacks for software related issues.
- issues_properties_changed
- end
-
- [[code, description]]
- end
- end
-
- def registered
- !!backend.registration.registered
- end
-
- def reg_code
- backend.registration.reg_code || ""
- end
-
- def email
- backend.registration.email || ""
- end
-
- def url
- backend.registration.registration_url || ""
- end
-
- def url=(url)
- # dbus has problem with nils, so empty string is only for dbus nil
- backend.registration.registration_url = url.empty? ? nil : url
- end
-
- # list of already registered addons
- #
- # @return [Array>] each list contains three items: addon id, version and
- # registration code
- def registered_addons
- backend.registration.registered_addons.map do |addon|
- [
- addon.name,
- # return empty string if the version was not explicitly specified (was autodetected)
- addon.required_version ? addon.version : "",
- addon.reg_code
- ]
- end
- end
-
- # list of available addons
- #
- # @return [Array>] List of addons
- def available_addons
- addons = backend.registration.available_addons || []
-
- addons.map do |a|
- {
- "id" => a.identifier,
- "version" => a.version,
- "label" => a.friendly_name,
- "available" => a.available, # boolean
- "free" => a.free, # boolean
- "recommended" => a.recommended, # boolean
- "description" => a.description,
- "type" => a.product_type, # "extension"
- "release" => a.release_stage # "beta"
- }
- end
- end
-
- # Tries to register with the given registration code.
- #
- # @note Software is not automatically probed after registering the product. The reason is
- # to avoid dealing with possible probing issues in the registration D-Bus API. Clients
- # have to explicitly call to #Probe after registering a product.
- #
- # @param reg_code [String]
- # @param email [String, nil]
- #
- # @return [Array(Integer, String)] Result code and a description.
- # Possible result codes:
- # 0: success
- # 1: missing product
- # 2: already registered
- # 3: registration not required
- # 4: network error
- # 5: timeout error
- # 6: api error
- # 7: missing credentials
- # 8: incorrect credentials
- # 9: invalid certificate
- # 10: internal error (e.g., parsing json data)
- # 13: Failed to add service from registration
- def register(reg_code, email: nil)
- if !backend.product
- [1, "Product not selected yet"]
- # report success and do nothing when already registered with the same code
- elsif backend.registration.registered && backend.registration.reg_code == reg_code
- [0, ""]
- elsif backend.registration.registered
- [2, "Product already registered"]
- elsif !backend.product.registration
- [3, "Product does not require registration"]
- else
- connect_result(first_error_code: 4) do
- backend.registration.register(reg_code, email: email)
- end
- end
- end
-
- # Tries to register the given addon. The base product must be already registered and if the
- # addon requires some other addon it must be already registered as well. (The code does not
- # check any dependencies.)
- #
- # @note Software is not automatically probed after registering the product. The reason is
- # to avoid dealing with possible probing issues in the registration D-Bus API. Clients
- # have to explicitly call to #Probe after registering a product.
- #
- # @param name [String] name (id) of the addon, e.g. "sle-ha"
- # @param version [String] version of the addon, e.g. "16.0", if empty the version is found
- # automatically in the list of available addons
- # @param reg_code [String] registration code, if the code is not required for the addon use
- # an empty string ("")
- #
- # @return [Array(Integer, String)] Result code and a description.
- # Possible result codes:
- # 0: success
- # 1: a base product was not selected yet
- # 2: the base product does not require registration
- # 3: the base product was not registered yet
- # 4: network error
- # 5: timeout error
- # 6: api error
- # 7: missing credentials
- # 8: incorrect credentials
- # 9: invalid certificate
- # 10: internal error (e.g., parsing json data)
- # 11: addon not found
- # 12: addon found in multiple versions
- # 13: Failed to add service from registration
- def register_addon(name, version, reg_code)
- if !backend.product
- [1, "Product not selected yet"]
- elsif !backend.product.registration
- [2, "Base product does not require registration"]
- elsif !backend.registration.registered
- [3, "Base product not registered yet"]
- else
- connect_result(first_error_code: 4) do
- backend.registration.register_addon(name, version, reg_code)
- end
- end
- end
-
- # Tries to deregister.
- #
- # @note Software is not automatically probed after deregistering the product. The reason is
- # to avoid dealing with possible probing issues in the deregistration D-Bus API. Clients
- # have to explicitly call to #Probe after deregistering a product.
- #
- # @return [Array(Integer, String)] Result code and a description.
- # Possible result codes:
- # 0: success
- # 1: missing product
- # 2: not registered yet
- # 3: network error
- # 4: timeout error
- # 5: api error
- # 6: missing credentials
- # 7: incorrect credentials
- # 8: invalid certificate
- # 9: internal error (e.g., parsing json data)
- # 13: Failed to remove service from registration
- def deregister
- if !backend.product
- [1, "Product not selected yet"]
- elsif !backend.registration.registered
- [2, "Product not registered yet"]
- else
- connect_result(first_error_code: 3) do
- backend.registration.deregister
- end
- end
- end
-
- REGISTRATION_INTERFACE = "org.opensuse.Agama1.Registration"
- private_constant :REGISTRATION_INTERFACE
-
- dbus_interface REGISTRATION_INTERFACE do
- dbus_reader(:registered, "b")
-
- dbus_reader(:reg_code, "s")
-
- dbus_reader(:email, "s")
-
- dbus_accessor(:url, "s")
-
- dbus_reader(:registered_addons, "a(sss)")
-
- dbus_reader(:available_addons, "aa{sv}")
-
- dbus_method(:Register, "in reg_code:s, in options:a{sv}, out result:(us)") do |*args|
- [register(args[0], email: args[1]["Email"])]
- end
-
- dbus_method(:RegisterAddon, "in name:s, in version:s, in reg_code:s, out result:(us)") do |*args|
- [register_addon(*args)]
- end
-
- dbus_method(:Deregister, "out result:(us)") { [deregister] }
- end
-
- private
-
- # @return [Agama::Software]
- attr_reader :backend
-
- # @return [Logger]
- attr_reader :logger
-
- # Registers callback to be called
- def register_callbacks
- # FIXME: Product issues might change after changing the registration. Nevertheless,
- # #on_issues_change callbacks should be used for emitting issues signals, ensuring they
- # are emitted every time the backend changes its issues. Currently, #on_issues_change
- # cannot be used for product issues. Note that Software::Manager backend takes care of
- # both software and product issues. And it already uses #on_issues_change callbacks for
- # software related issues.
- backend.registration.on_change { issues_properties_changed }
- backend.registration.on_change { registration_properties_changed }
- backend.on_issues_change { issues_properties_changed }
- end
-
- def registration_properties_changed
- dbus_properties_changed(REGISTRATION_INTERFACE,
- interfaces_and_properties[REGISTRATION_INTERFACE], [])
- end
-
- def product_properties_changed
- dbus_properties_changed(PRODUCT_INTERFACE,
- interfaces_and_properties[PRODUCT_INTERFACE], [])
- end
-
- # Result from calling to SUSE connect.
- #
- # @raise [Exception] if an unexpected error is found.
- #
- # @return [Array(Integer, String)] List including a result code and a description
- # (e.g., [1, "Connection to registration server failed (network error)"]).
- def connect_result(first_error_code: 1, &block)
- block.call
- [0, ""]
- rescue SocketError => e
- connect_result_from_error(e, first_error_code, "network error")
- rescue Timeout::Error => e
- connect_result_from_error(e, first_error_code + 1, "timeout")
- rescue SUSE::Connect::ApiError => e
- connect_result_from_error(e, first_error_code + 2)
- rescue SUSE::Connect::MissingSccCredentialsFile => e
- connect_result_from_error(e, first_error_code + 3, "missing credentials")
- rescue SUSE::Connect::MalformedSccCredentialsFile => e
- connect_result_from_error(e, first_error_code + 4, "incorrect credentials")
- rescue OpenSSL::SSL::SSLError => e
- connect_result_from_error(e, first_error_code + 5, "invalid certificate")
- rescue JSON::ParserError => e
- connect_result_from_error(e, first_error_code + 6)
- rescue Errors::Registration::ExtensionNotFound => e
- connect_result_from_error(e, first_error_code + 7)
- rescue Errors::Registration::MultipleExtensionsFound => e
- connect_result_from_error(e, first_error_code + 8)
- rescue Agama::Software::ServiceError => e
- connect_result_from_error(e, first_error_code + 9)
- rescue StandardError => e
- connect_result_from_error(e, first_error_code + 10)
- end
-
- # Generates a result from a given error.
- #
- # @param error [Exception]
- # @param error_code [Integer]
- # @param details [String, nil]
- #
- # @return [Array(Integer, String)] List including an error code and a description.
- def connect_result_from_error(error, error_code, details = nil)
- logger.error("Error connecting to registration server: #{error}")
-
- description = "Connection to registration server failed: #{error}"
- description += " (#{details})" if details
-
- [error_code, description]
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/software/proposal.rb b/service/lib/agama/dbus/software/proposal.rb
deleted file mode 100644
index cd0e3587d0..0000000000
--- a/service/lib/agama/dbus/software/proposal.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "dbus"
-Yast.import "PackagesProposal"
-
-module Agama
- module DBus
- module Software
- # Software proposal D-Bus representation
- #
- # This class allows to change software proposal settings through D-Bus.
- #
- # @see Yast::PackagesProposal
- class Proposal < ::DBus::Object
- PATH = "/org/opensuse/Agama/Software1/Proposal"
- private_constant :PATH
-
- INTERFACE = "org.opensuse.Agama.Software1.Proposal"
- private_constant :INTERFACE
-
- TYPES = [:package, :pattern].freeze
- private_constant :TYPES
-
- # Constructor
- #
- # @param logger [Logger]
- def initialize(logger)
- @logger = logger
- super(PATH)
- end
-
- dbus_interface INTERFACE do
- dbus_method :AddResolvables,
- "in Id:s, in Type:y, in Resolvables:as, in Optional:b" do |id, type, resolvables, opt|
- Yast::PackagesProposal.AddResolvables(id, TYPES[type], resolvables, optional: opt)
- end
-
- dbus_method :GetResolvables,
- "in Id:s, in Type:y, in Optional:b, out Resolvables:as" do |id, type, opt|
- [Yast::PackagesProposal.GetResolvables(id, TYPES[type], optional: opt)]
- end
-
- dbus_method :SetResolvables,
- "in Id:s, in Type:y, in Resolvables:as, in Optional:b" do |id, type, resolvables, opt|
- Yast::PackagesProposal.SetResolvables(id, TYPES[type], resolvables, optional: opt)
- end
-
- dbus_method :RemoveResolvables,
- "in Id:s, in Type:y, in Resolvables:as, in Optional:b" do |id, type, resolvables, opt|
- Yast::PackagesProposal.RemoveResolvables(id, TYPES[type], resolvables, optional: opt)
- end
- end
-
- private
-
- # @return [Logger]
- attr_reader :logger
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/software_service.rb b/service/lib/agama/dbus/software_service.rb
deleted file mode 100644
index 650de0a894..0000000000
--- a/service/lib/agama/dbus/software_service.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "dbus"
-require "agama/dbus/bus"
-require "agama/dbus/software"
-require "agama/software"
-
-require "yast"
-Yast.import "Pkg"
-Yast.import "Language"
-
-module Agama
- module DBus
- # D-Bus service (org.opensuse.Agama.Software1)
- #
- # It connects to the system D-Bus and answers requests on objects below
- # `/org/opensuse/Agama/Software1`.
- class SoftwareService
- SERVICE_NAME = "org.opensuse.Agama.Software1"
- private_constant :SERVICE_NAME
-
- # D-Bus connection
- #
- # @return [::DBus::BusConnection]
- attr_reader :bus
-
- # @param config [Config] Configuration object
- # @param logger [Logger]
- def initialize(config, logger = nil)
- @logger = logger || Logger.new($stdout)
- @bus = Bus.current
- @backend = Agama::Software::Manager.new(config, logger)
- end
-
- # Starts software service. It does more then just #export method.
- def start
- # for some reason the the "export" method must be called before
- # registering the language change callback to work properly
- export
- end
-
- # Exports the software object through the D-Bus service
- def export
- dbus_objects.each { |o| service.export(o) }
- paths = dbus_objects.map(&:path).join(", ")
- logger.info "Exported #{paths} objects"
- end
-
- # Call this from some main loop to dispatch the D-Bus messages
- def dispatch
- bus.dispatch_message_queue
- end
-
- private
-
- # @return [Logger]
- attr_reader :logger, :backend
-
- # @return [::DBus::ObjectServer]
- def service
- @service ||= bus.request_service(SERVICE_NAME)
- end
-
- # @return [Array<::DBus::Object>]
- def dbus_objects
- @dbus_objects ||= [
- Agama::DBus::Software::Manager.new(@backend, logger),
- Agama::DBus::Software::Product.new(@backend, logger),
- Agama::DBus::Software::Proposal.new(logger)
- ]
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/users.rb b/service/lib/agama/dbus/users.rb
deleted file mode 100644
index 27fafa3993..0000000000
--- a/service/lib/agama/dbus/users.rb
+++ /dev/null
@@ -1,163 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "dbus"
-require "agama/users"
-require "agama/dbus/base_object"
-require "agama/dbus/with_service_status"
-require "agama/dbus/interfaces/issues"
-require "agama/dbus/interfaces/service_status"
-
-module Agama
- module DBus
- # YaST D-Bus object (/org/opensuse/Agama/Users1)
- class Users < BaseObject
- include WithServiceStatus
- include Interfaces::Issues
- include Interfaces::ServiceStatus
-
- PATH = "/org/opensuse/Agama/Users1"
- private_constant :PATH
-
- # Constructor
- #
- # @param backend [Agama::Users]
- # @param logger [Logger]
- def initialize(backend, logger)
- super(PATH, logger: logger)
- @backend = backend
- register_service_status_callbacks
- register_users_callbacks
- end
-
- # List of issues, see {DBus::Interfaces::Issues}
- #
- # @return [Array]
- def issues
- backend.issues
- end
-
- USERS_INTERFACE = "org.opensuse.Agama.Users1"
- private_constant :USERS_INTERFACE
-
- FUSER_SIG = "in FullName:s, in UserName:s, in Password:s, in HashedPassword:b, " \
- "in data:a{sv}"
- private_constant :FUSER_SIG
-
- dbus_interface USERS_INTERFACE do
- dbus_reader :root_user, "(sbs)"
-
- dbus_reader :first_user, "(sssba{sv})"
-
- dbus_method :SetRootPassword,
- "in Value:s, in Hashed:b, out result:u" do |value, hashed|
- logger.info "Setting Root Password"
- backend.assign_root_password(value, hashed)
-
- dbus_properties_changed(USERS_INTERFACE, { "RootUser" => root_user }, [])
- 0
- end
-
- dbus_method :RemoveRootPassword, "out result:u" do
- logger.info "Clearing the root password"
- backend.remove_root_password
-
- dbus_properties_changed(USERS_INTERFACE, { "RootUser" => root_user },
- [])
- 0
- end
-
- dbus_method :SetRootSSHKey, "in Value:s, out result:u" do |value|
- logger.info "Setting Root ssh key"
- backend.root_ssh_key = (value)
-
- dbus_properties_changed(USERS_INTERFACE, { "RootUser" => root_user }, [])
- 0
- end
-
- # It returns an Struct with the first field with the result of the operation as a boolean
- # and the second parameter as an array of issues found in case of failure
- dbus_method :SetFirstUser, FUSER_SIG + ", out result:(bas)" do |full_name, user_name,
- password, hashed_password, data|
- logger.info "Setting first user #{full_name}"
- user_issues = backend.assign_first_user(full_name, user_name, password,
- hashed_password, data)
-
- if user_issues.empty?
- dbus_properties_changed(USERS_INTERFACE, { "FirstUser" => first_user }, [])
- else
- logger.info "First user fatal issues detected: #{issues}"
- end
-
- [[user_issues.empty?, user_issues]]
- end
-
- dbus_method :RemoveFirstUser, "out result:u" do
- logger.info "Removing the first user"
- backend.remove_first_user
-
- dbus_properties_changed(USERS_INTERFACE, { "FirstUser" => first_user }, [])
- 0
- end
-
- dbus_method :Write, "out result:u" do
- logger.info "Writting users"
-
- backend.write
- 0
- end
- end
-
- def root_user
- root = backend.root_user
-
- [
- root.password_content || "",
- root.password&.value&.encrypted?,
- root.authorized_keys.first || ""
- ]
- end
-
- def first_user
- user = backend.first_user
-
- return ["", "", "", false, {}] unless user
-
- [
- user.full_name,
- user.name,
- user.password_content || "",
- user.password&.value&.encrypted? || false,
- {}
- ]
- end
-
- private
-
- # @return [Agama::Users]
- attr_reader :backend
-
- def register_users_callbacks
- backend.on_issues_change { issues_properties_changed }
- end
- end
- end
-end
diff --git a/service/lib/agama/dbus/y2dir/README.md b/service/lib/agama/dbus/y2dir/README.md
deleted file mode 100644
index 7febb8ce64..0000000000
--- a/service/lib/agama/dbus/y2dir/README.md
+++ /dev/null
@@ -1,28 +0,0 @@
-This directory contains some redefinitions of YaST modules in order to call to D-Bus methods instead
-of executing the actual code of the module.
-
-# Why is this needed?
-
-Agama relies on YaST code and YaST usually works as a single process. By contrast, Agama works as a
-set of different processes (software service, storage service, etc), and each service runs its own
-YaST instance.
-
-Having different YaST instances implies that the information is scattered in different processes.
-For example, only the YaST instance in the software service has the information about the software
-configuration. This means that other YaST instances need to ask to the software YaST instance for
-the information.
-
-# How to communicate among YaST instances
-
-A YaST instance can get information from other instance by doing a D-Bus call to the service running
-such an instance. For example, the YaST instance in the storage service has to call to the D-Bus API
-of the software service instead of directly calling to the software module code. To achieve that,
-the storage service replaces the implementation of the YaST software module by its own
-implementation which uses D-Bus calls.
-
-# How to replace a YaST module
-
-The code replacement of the YaST modules is done by means of the *Y2DIR* mechanism of YaST. When a
-service is started (check *agamactl* script), the YaST modules redefined by the service (under
-*lib/agama/dbus/y2dir/*) are added to the *Y2DIR* environment variable. YaST takes precedence of the
-paths at *Y2DIR*, so these files will be loaded instead of the files originally delivered by YaST.
diff --git a/service/lib/agama/dbus/y2dir/manager/modules/Package.rb b/service/lib/agama/dbus/y2dir/manager/modules/Package.rb
deleted file mode 100644
index 4ec7600b70..0000000000
--- a/service/lib/agama/dbus/y2dir/manager/modules/Package.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright (c) [2022-2024] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "agama/http/clients/software"
-
-# :nodoc:
-module Yast
- # Replacement for the Yast::Package module.
- class PackageClass < Module
- def main
- puts "Loading mocked module #{__FILE__}"
- @client = Agama::HTTP::Clients::Software.new(::Logger.new($stdout))
- end
-
- # Determines whether a package is available.
- #
- # @param name [String] Package name.
- # @return [Boolean]
- def Available(name)
- client.package_available?(name)
- end
-
- # Determines whether a set of packages is available.
- #
- # @param names [Array] Names of the packages.
- # @return [Boolean]
- def AvailableAll(names)
- names.all? { |name| client.package_available?(name) }
- end
-
- # Determines whether a package is installed in the target system.
- #
- # @param name [String] Package name.
- # @return [Boolean]
- def Installed(name, target: nil)
- client.package_installed?(name)
- end
-
- private
-
- attr_reader :client
- end
-
- Package = PackageClass.new
- Package.main
-end
diff --git a/service/lib/agama/dbus/y2dir/manager/modules/PackagesProposal.rb b/service/lib/agama/dbus/y2dir/manager/modules/PackagesProposal.rb
deleted file mode 100644
index 7d0ceeabce..0000000000
--- a/service/lib/agama/dbus/y2dir/manager/modules/PackagesProposal.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "agama/http/clients/main"
-
-# :nodoc:
-module Yast
- # Replacement for the Yast::PackagesProposal module
- class PackagesProposalClass < Module
- def main
- puts "Loading mocked module #{__FILE__}"
- @client = Agama::HTTP::Clients::Main.new(::Logger.new($stdout))
- end
-
- # @see https://github.com/yast/yast-yast2/blob/b8cd178b7f341f6e3438782cb703f4a3ab0529ed/library/general/src/modules/PackagesProposal.rb#L118
- def AddResolvables(unique_id, type, resolvables, optional: false)
- orig_resolvables = client.get_resolvables(unique_id, type, optional: optional)
- orig_resolvables += resolvables
- orig_resolvables.uniq!
- SetResolvables(unique_id, type, orig_resolvables, optional: optional)
- true
- end
-
- # @see https://github.com/yast/yast-yast2/blob/b8cd178b7f341f6e3438782cb703f4a3ab0529ed/library/general/src/modules/PackagesProposal.rb#L145
- def SetResolvables(unique_id, type, resolvables, optional: false)
- client.set_resolvables(unique_id, type, resolvables || [])
- true
- end
-
- # @see https://github.com/yast/yast-yast2/blob/b8cd178b7f341f6e3438782cb703f4a3ab0529ed/library/general/src/modules/PackagesProposal.rb#L285
- def GetResolvables(unique_id, type, optional: false)
- client.get_resolvables(unique_id, type, optional)
- end
-
- # @see https://github.com/yast/yast-yast2/blob/b8cd178b7f341f6e3438782cb703f4a3ab0529ed/library/general/src/modules/PackagesProposal.rb#L177
- def RemoveResolvables(unique_id, type, resolvables, optional: false)
- orig_resolvables = client.get_resolvables(unique_id, type, optional: optional)
- orig_resolvables -= resolvables
- orig_resolvables.uniq!
- SetResolvables(unique_id, type, orig_resolvables, optional: optional)
- true
- end
-
- private
-
- attr_reader :client
- end
-
- PackagesProposal = PackagesProposalClass.new
- PackagesProposal.main
-end
diff --git a/service/lib/agama/dbus/y2dir/modules/Autologin.rb b/service/lib/agama/dbus/y2dir/modules/Autologin.rb
deleted file mode 100644
index ed97f71f14..0000000000
--- a/service/lib/agama/dbus/y2dir/modules/Autologin.rb
+++ /dev/null
@@ -1,214 +0,0 @@
-# ------------------------------------------------------------------------------
-# Copyright (c) 2006-2012 Novell, Inc. All Rights Reserved.
-#
-#
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of version 2 of the GNU General Public License as published by the
-# Free Software Foundation.
-#
-# 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 Novell, Inc.
-#
-# To contact Novell about this file by physical or electronic mail, you may find
-# current contact information at www.novell.com.
-# ------------------------------------------------------------------------------
-
-# File: modules/Autologin.ycp
-# Package: yast2
-# Summary: Autologin read/write routines
-# Author: Jiri Suchomel
-# Flags: Stable
-#
-# $Id$
-require "yast"
-require "agama/http/clients/software"
-
-module Yast
- class AutologinClass < Module
- include Yast::Logger
-
- # Display managers that support autologin.
- # Notice that xdm does NOT support it!
- #
- # "autologin-support" is a pseudo-"provides" that maintainers of display
- # manager packages can add to indicate that the package has that
- # capability.
- DISPLAY_MANAGERS = ["autologin-support", "kdm", "gdm", "sddm", "lightdm"].freeze
-
- def main
- textdomain "pam"
-
- Yast.import "Popup"
-
- # User to log in automaticaly
- @user = ""
-
- # Login without passwords?
- @pw_less = false
-
- # Is autologin used? Usualy true when user is not empty, but for the first
- # time (during installation), this can be true by default although user is ""
- # (depends on the control file)
- @used = false
-
- # Autologin settings modified?
- @modified = false
-
- # Pkg stuff initialized?
- @pkg_initialized = false
-
- # Software service client
- @software_client = nil
- end
-
- def available
- @available = supported? if @available.nil?
- @available
- end
-
- # Read autologin settings
- # @return used?
- def Read
- if SCR.Read(path(".target.size"), "/etc/sysconfig/displaymanager") == -1
- @available = false
- @user = ""
- @used = false
- return false
- end
-
- @available = supported?
- @user = Convert.to_string(
- SCR.Read(path(".sysconfig.displaymanager.DISPLAYMANAGER_AUTOLOGIN"))
- )
- @pw_less = Convert.to_string(
- SCR.Read(
- path(".sysconfig.displaymanager.DISPLAYMANAGER_PASSWORD_LESS_LOGIN")
- )
- ) == "yes"
-
- @user = "" if @user.nil? || @user == ""
-
- @used = @user != ""
- @used
- end
-
- # Write autologin settings
- # @param _write_only [Boolean] when true, suseconfig script will not be run
- # @return [Boolean]
- def Write(_write_only)
- return false if !available || !@modified
-
- Builtins.y2milestone(
- "writing user %1 for autologin; pw_less is %2",
- @user,
- @pw_less
- )
-
- SCR.Write(
- path(".sysconfig.displaymanager.DISPLAYMANAGER_AUTOLOGIN"),
- @user
- )
- SCR.Write(
- path(".sysconfig.displaymanager.DISPLAYMANAGER_PASSWORD_LESS_LOGIN"),
- @pw_less ? "yes" : "no"
- )
- SCR.Write(path(".sysconfig.displaymanager"), nil)
-
- @modified = false
- true
- end
-
- # Disable autologin
- def Disable
- @user = ""
- @pw_less = false
- @used = false
- @modified = true
-
- nil
- end
-
- # Wrapper for setting the 'used' variable
- def Use(use)
- if @used != use
- @used = use
- @modified = true
- end
-
- nil
- end
-
- # Disable autologin and write it (used probably for automatic
- # disabling without asking)
- # @param [Boolean] write_only when true, suseconfig script will not be run
- # @return written anything?
- def DisableAndWrite(write_only)
- Disable()
- Write(write_only)
- end
-
- # Ask if autologin should be disabled (and disable it in such case)
- # @param [String] new The reason for disabling autologin (e.g. new set of users)
- # @return Is autologin used?
- def AskForDisabling(new)
- # popup text (%1 is user name, %2 is additional info,
- # like "Now LDAP was enabled")
- question = Builtins.sformat(
- _(
- "The automatic login feature is enabled for user %1.\n" +
- "%2\n" +
- "Disable automatic login?"
- ),
- @user,
- new
- )
-
- Disable() if @used && Popup.YesNo(question)
- @used
- end
-
- # Check if autologin is supported with the currently selected or installed
- # packages.
- #
- # @return Boolean
- def supported?
- supported = software_client.provisions_selected?(DISPLAY_MANAGERS).any?
-
- if supported
- log.info("Autologin is supported")
- else
- log.info("Autologin is not supported: No package provides any of #{DISPLAY_MANAGERS}")
- end
-
- supported
- end
-
- publish variable: :user, type: "string"
- publish variable: :pw_less, type: "boolean"
- publish variable: :used, type: "boolean"
- publish variable: :modified, type: "boolean"
- publish function: :Read, type: "boolean ()"
- publish function: :Write, type: "boolean (boolean)"
- publish function: :Disable, type: "void ()"
- publish function: :Use, type: "void (boolean)"
- publish function: :supported?, type: "boolean ()"
- publish function: :DisableAndWrite, type: "boolean (boolean)"
- publish function: :AskForDisabling, type: "boolean (string)"
-
- private
-
- # Software service client
- #
- # @return [Agama::DBus::Clients::Software] Software service client
- def software_client
- @software_client ||= Agama::HTTP::Clients::Software.new(::Logger.new($stdout))
- end
- end
-
- Autologin = AutologinClass.new
- Autologin.main
-end
diff --git a/service/lib/agama/dbus/y2dir/modules/InstFunctions.rb b/service/lib/agama/dbus/y2dir/modules/InstFunctions.rb
deleted file mode 100644
index a1d5a21cc4..0000000000
--- a/service/lib/agama/dbus/y2dir/modules/InstFunctions.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-
-# :nodoc:
-module Yast
- # Replacement for the Yast::Package module
- #
- # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb
- class InstFunctionsClass < Module
- def main
- puts "Loading mocked module #{__FILE__}"
- end
-
- # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L56
- def ignored_features
- []
- end
-
- # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L83
- def reset_ignored_features; end
-
- # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L91
- def feature_ignored?(_feature_name)
- false
- end
-
- # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L107
- def second_stage_required?
- false
- end
-
- # @see https://github.com/yast/yast-installation/blob/279b7d108eab24082237cf5e3f02a31f58fef8da/src/modules/InstFunctions.rb#L137
- def self_update_explicitly_enabled?
- false
- end
- end
-
- InstFunctions = InstFunctionsClass.new
- InstFunctions.main
-end
diff --git a/service/lib/agama/dbus/y2dir/software/modules/PackageCallbacks.rb b/service/lib/agama/dbus/y2dir/software/modules/PackageCallbacks.rb
deleted file mode 100644
index db2f77543b..0000000000
--- a/service/lib/agama/dbus/y2dir/software/modules/PackageCallbacks.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "logger"
-require "agama/software/callbacks"
-require "agama/http/clients"
-
-# :nodoc:
-module Yast
- # Replacement for the Yast::PackageCallbacks module.
- class PackageCallbacksClass < Module
- def main
- puts "Loading mocked module #{__FILE__}"
- end
-
- # @see https://github.com/yast/yast-yast2/blob/19180445ab935a25edd4ae0243aa7a3bcd09c9de/library/packages/src/modules/PackageCallbacks.rb#L183
- def InitPackageCallbacks(logger = nil)
- @logger = logger || ::Logger.new($stdout)
-
- Agama::Software::Callbacks::Digest.new(
- questions_client, logger
- ).setup
-
- Agama::Software::Callbacks::Media.new(
- questions_client, logger
- ).setup
-
- Agama::Software::Callbacks::Provide.new(
- questions_client, logger
- ).setup
-
- Agama::Software::Callbacks::Signature.new(
- questions_client, logger
- ).setup
-
- Agama::Software::Callbacks::Script.new(
- questions_client, logger
- ).setup
-
- Agama::Software::Callbacks::PkgGpgCheck.new(
- questions_client, logger
- ).setup
- end
-
- # Returns the client to ask questions
- #
- # @return [Agama::DBus::Clients::Questions]
- def questions_client
- @questions_client ||= Agama::HTTP::Clients::Questions.new(logger)
- end
-
- private
-
- # @return [Logger]
- attr_reader :logger
- end
-
- PackageCallbacks = PackageCallbacksClass.new
- PackageCallbacks.main
-end
diff --git a/service/lib/agama/dbus/y2dir/software/modules/SpaceCalculation.rb b/service/lib/agama/dbus/y2dir/software/modules/SpaceCalculation.rb
deleted file mode 100644
index 91055fed57..0000000000
--- a/service/lib/agama/dbus/y2dir/software/modules/SpaceCalculation.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-
-# :nodoc:
-module Yast
- # Replacement for the Yast::SpaceCalculation module.
- class SpaceCalculationClass < Module
- def main
- puts "Loading mocked module #{__FILE__}"
- end
-
- # @see https://github.com/yast/yast-packager/blob/master/src/modules/SpaceCalculation.rb#L711
- def GetPartitionInfo; end
-
- # @see https://github.com/yast/yast-packager/blob/master/src/modules/SpaceCalculation.rb#L860
- def CheckDiskSize; true; end
-
- # @see https://github.com/yast/yast-packager/blob/master/src/modules/SpaceCalculation.rb#L894
- def CheckDiskFreeSpace(*_args); []; end
-
- # @see https://github.com/yast/yast-packager/blob/master/src/modules/SpaceCalculation.rb#L60
- def GetFailedMounts
- []
- end
- end
-
- SpaceCalculation = SpaceCalculationClass.new
- SpaceCalculation.main
-end
diff --git a/service/lib/agama/dbus/y2dir/storage/modules/InstFunctions.rb b/service/lib/agama/dbus/y2dir/storage/modules/InstFunctions.rb
deleted file mode 120000
index 42c5d80092..0000000000
--- a/service/lib/agama/dbus/y2dir/storage/modules/InstFunctions.rb
+++ /dev/null
@@ -1 +0,0 @@
-../../modules/InstFunctions.rb
\ No newline at end of file
diff --git a/service/lib/agama/dbus/y2dir/storage/modules/Package.rb b/service/lib/agama/dbus/y2dir/storage/modules/Package.rb
deleted file mode 120000
index c56312372d..0000000000
--- a/service/lib/agama/dbus/y2dir/storage/modules/Package.rb
+++ /dev/null
@@ -1 +0,0 @@
-../../manager/modules/Package.rb
\ No newline at end of file
diff --git a/service/lib/agama/dbus/y2dir/storage/modules/PackagesProposal.rb b/service/lib/agama/dbus/y2dir/storage/modules/PackagesProposal.rb
deleted file mode 120000
index e496394bb7..0000000000
--- a/service/lib/agama/dbus/y2dir/storage/modules/PackagesProposal.rb
+++ /dev/null
@@ -1 +0,0 @@
-../../manager/modules/PackagesProposal.rb
\ No newline at end of file
diff --git a/service/lib/agama/errors.rb b/service/lib/agama/errors.rb
deleted file mode 100644
index 8fb2d21959..0000000000
--- a/service/lib/agama/errors.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-module Agama
- # Module containing common errors
- module Errors
- # Invalid value given by the user
- class InvalidValue < StandardError; end
-
- # Registration specific errors
- module Registration
- # The requested extension was not found
- class ExtensionNotFound < StandardError
- def initialize(name)
- super("#{name.inspect} is not available")
- end
- end
-
- # The requested extension exists in multiple versions
- class MultipleExtensionsFound < StandardError
- def initialize(name, versions)
- super("#{name.inspect} is available in multiple versions: #{versions.join(", ")}")
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/http/clients.rb b/service/lib/agama/http/clients.rb
index 5c7b765755..b3396525d8 100644
--- a/service/lib/agama/http/clients.rb
+++ b/service/lib/agama/http/clients.rb
@@ -28,9 +28,5 @@ module Clients
end
require "agama/http/clients/base"
-require "agama/http/clients/files"
require "agama/http/clients/main"
-require "agama/http/clients/network"
require "agama/http/clients/questions"
-require "agama/http/clients/scripts"
-require "agama/http/clients/software"
diff --git a/service/lib/agama/http/clients/files.rb b/service/lib/agama/http/clients/files.rb
deleted file mode 100644
index 55f2c08b82..0000000000
--- a/service/lib/agama/http/clients/files.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/http/clients/base"
-
-module Agama
- module HTTP
- module Clients
- # HTTP client to interact with the files API.
- class Files < Base
- # writes additional files
- def write
- post("files/write", nil)
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/http/clients/main.rb b/service/lib/agama/http/clients/main.rb
index 600bef871c..daa04cc0b1 100644
--- a/service/lib/agama/http/clients/main.rb
+++ b/service/lib/agama/http/clients/main.rb
@@ -37,7 +37,7 @@ def install
# @param resolvables [Array] Resolvables names.
def set_resolvables(unique_id, type, resolvables)
data = resolvables.map do |name|
- { "name" => name, "type" => type }
+ { "name" => name, "type" => type.to_s }
end
put("v2/private/resolvables/#{unique_id}", data)
end
diff --git a/service/lib/agama/http/clients/network.rb b/service/lib/agama/http/clients/network.rb
deleted file mode 100644
index ad98ab1d0d..0000000000
--- a/service/lib/agama/http/clients/network.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/http/clients/base"
-
-module Agama
- module HTTP
- module Clients
- # HTTP client to interact with the network API.
- class Network < Base
- def connections
- JSON.parse(get("network/connections"))
- end
-
- def devices
- JSON.parse(get("network/devices"))
- end
-
- def persist_connections
- post("network/connections/persist", { value: true })
- end
-
- def state
- JSON.parse(get("network/state"))
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/http/clients/scripts.rb b/service/lib/agama/http/clients/scripts.rb
deleted file mode 100644
index 773b714db4..0000000000
--- a/service/lib/agama/http/clients/scripts.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-# 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 version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/http/clients/base"
-
-module Agama
- module HTTP
- module Clients
- # HTTP client to interact with the scripts API.
- class Scripts < Base
- # Runs the scripts
- def run(group)
- post("scripts/run", group)
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/http/clients/software.rb b/service/lib/agama/http/clients/software.rb
deleted file mode 100644
index caa1ba7b18..0000000000
--- a/service/lib/agama/http/clients/software.rb
+++ /dev/null
@@ -1,131 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/http/clients/base"
-
-module Agama
- module HTTP
- module Clients
- # HTTP client to interact with the software API.
- class Software < Base
- def products
- JSON.parse(get("software/products"))
- end
-
- def proposal
- JSON.parse(get("software/proposal"))
- end
-
- def probe
- post("software/probe", nil)
- end
-
- def propose
- # it is noop, probe already do proposal
- # post("software/propose", nil)
- end
-
- def install
- http = Net::HTTP.new("localhost")
- # FIXME: we need to improve it as it can e.g. wait for user interaction.
- http.read_timeout = 3 * 60 * 60 # set timeout to three hours for rpm installation
- response = http.post("/api/software/install", "", headers)
-
- return unless response.is_a?(Net::HTTPClientError)
-
- @logger.warn "server returned #{response.code} with body: #{response.body}"
- end
-
- def finish
- post("software/finish", nil)
- end
-
- def locale=(value)
- # TODO: implement it
- post("software/locale", value)
- end
-
- def config
- JSON.parse(get("v2/config"))
- end
-
- def selected_product
- config.dig("product", "id")
- end
-
- def errors?
- # TODO: severity as integer is nasty for http API
- JSON.parse(get("software/issues/software"))&.select { |i| i["severity"] == 1 }&.any?
- end
-
- def get_resolvables(unique_id, type, optional)
- JSON.parse(get("software/resolvables/#{unique_id}?type=#{type}&optional=#{optional}"))
- end
-
- # (Yes, with a question mark. Bad naming.)
- # @return [Array] Those names that are selected for installation
- def provisions_selected?(provisions)
- provisions.select do |prov|
- package_installed?(prov)
- end
- end
-
- def package_available?(_name)
- JSON.parse(get("software/available?tag=#{name}"))
- end
-
- def package_installed?(name)
- JSON.parse(get("software/selected?tag=#{name}"))
- end
-
- def set_resolvables(unique_id, type, resolvables, optional)
- data = {
- "names" => resolvables,
- "type" => type,
- "optional" => optional
- }
- put("software/resolvables/#{unique_id}", data)
- end
-
- def add_patterns(patterns)
- config_patterns = config["patterns"] || {}
- selected = config_patterns.select { |_k, v| v }.keys
- modified = false
-
- patterns.each do |pattern|
- unless selected.include?(pattern)
- config_patterns[pattern] = true
- modified = true
- end
- end
- return unless modified
-
- put("software/config", { "patterns" => config_patterns })
- end
-
- def on_probe_finished(&block)
- # TODO: it was agreed to change this storage observation to have the code
- # in rust part and call via dbus ruby part
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/manager.rb b/service/lib/agama/manager.rb
deleted file mode 100644
index 9f69447aa0..0000000000
--- a/service/lib/agama/manager.rb
+++ /dev/null
@@ -1,334 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "shellwords"
-
-require "yast"
-require "agama/config"
-require "agama/network"
-require "agama/proxy_setup"
-require "agama/with_locale"
-require "agama/with_progress_manager"
-require "agama/installation_phase"
-require "agama/service_status_recorder"
-require "agama/dbus/service_status"
-require "agama/http/clients/software"
-require "agama/dbus/clients/storage"
-require "agama/helpers"
-require "agama/http"
-require "agama/ipmi"
-
-Yast.import "Stage"
-
-module Agama
- # This class represents the top level installer manager.
- #
- # It is responsible for orchestrating the installation process. For module
- # specific stuff it delegates it to the corresponding module class (e.g.,
- # {Agama::Network}, {Agama::Storage::Proposal}, etc.) or asks
- # other services via HTTP (e.g., `/software`).
- class Manager
- include WithProgressManager
- include WithLocale
- include Helpers
- include Yast::I18n
-
- # @return [Logger]
- attr_reader :logger
-
- # @return [InstallationPhase]
- attr_reader :installation_phase
-
- # @return [DBus::ServiceStatus]
- attr_reader :service_status
-
- # Constructor
- #
- # @param config [Agama::Config]
- # @param logger [Logger]
- def initialize(config, logger)
- textdomain "agama"
-
- @config = config
- @logger = logger
- @installation_phase = InstallationPhase.new
- @service_status_recorder = ServiceStatusRecorder.new
- @service_status = DBus::ServiceStatus.new.busy
- @ipmi = Ipmi.new(logger)
-
- on_progress_change { logger.info progress.to_s }
- end
-
- # Runs the startup phase
- def startup_phase
- service_status.busy
- installation_phase.startup
- # FIXME: hot-fix for decision taken at bsc#1224868 (RC1)
- network.startup
- config_phase if software.config.dig("product", "id")
-
- logger.info("Startup phase done")
- service_status.idle
- end
-
- # Runs the config phase
- #
- # @param reprobe [Boolean] Whether a reprobe should be done instead of a probe.
- def config_phase(reprobe: false)
- installation_phase.config
- start_progress_with_descriptions(_("Analyze disks"), _("Configure software"))
- progress.step { configure_storage(reprobe) }
- progress.step { software.probe }
-
- logger.info("Config phase done")
- rescue StandardError => e
- logger.error "Startup error: #{e.inspect}. Backtrace: #{e.backtrace}"
- # TODO: report errors
- ensure
- finish_progress
- end
-
- # Runs the install phase
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
- def install_phase
- @ipmi.started
-
- installation_phase.install
- start_progress_with_descriptions(
- _("Prepare disks"),
- _("Install software"),
- _("Configure the system")
- )
-
- Yast::Installation.destdir = "/mnt"
-
- progress.step do
- storage.install
- run_post_partitioning_scripts
- proxy.propose
- # propose software after /mnt is already separated, so it uses proper
- # target
- software.propose
- end
-
- progress.step { software.install }
- progress.step do
- on_target do
- users.write
- network.install
- http_client.install
- software.finish
- storage.finish
- end
- end
-
- @ipmi.finished
-
- logger.info("Install phase done")
- rescue StandardError => e
- @ipmi.failed
- logger.error "Installation error: #{e.inspect}. Backtrace: #{e.backtrace}"
- ensure
- installation_phase.finish
- finish_progress
- end
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
-
- def locale=(locale)
- change_process_locale(locale)
- users.update_issues
- start_progress_with_descriptions(
- _("Load software translations"),
- _("Load storage translations")
- )
- progress.step { software.locale = locale }
- progress.step { storage.locale = locale }
- ensure
- finish_progress
- end
-
- # Software client
- #
- # @return [DBus::Clients::Software]
- def software
- @software ||= HTTP::Clients::Software.new(logger)
- # TODO: watch for http websocket events regarding software status
- # software.tap do |client|
- # client.on_service_status_change do |status|
- # service_status_recorder.save(client.service.name, status)
- # end
- # end
- end
-
- # ProxySetup instance
- #
- # @return [ProxySetup]
- def proxy
- ProxySetup.instance
- end
-
- # HTTP client.
- #
- # @return [HTTP::Clients::Base]
- def http_client
- @http_client ||= Agama::HTTP::Clients::Main.new(logger)
- end
-
- # Users client
- #
- # @return [Agama::Users]
- def users
- @users ||= Users.new(logger)
- end
-
- # Network manager
- #
- # @return [Network]
- def network
- @network ||= Network.new(logger)
- end
-
- # Storage manager
- #
- # @return [DBus::Clients::Storage]
- def storage
- @storage ||= DBus::Clients::Storage.new
- end
-
- # Name of busy services
- #
- # @see ServiceStatusRecorder
- #
- # @return [Array]
- def busy_services
- service_status_recorder.busy_services
- end
-
- # Registers a callback to be called when the status of a service changes
- #
- # @see ServiceStatusRecorder
- def on_services_status_change(&block)
- service_status_recorder.on_service_status_change(&block)
- end
-
- # Determines whether the configuration is valid and the system is ready for installation
- #
- # @return [Boolean]
- def valid?
- users.issues.empty? && !software.errors?
- end
-
- # Collects the logs and stores them into an archive
- #
- # @param path [String] directory where to store logs
- #
- # @return [String] path to created archive
- def collect_logs(path: nil)
- opt = "-d #{path.shellescape}" unless path.nil? || path.empty?
-
- `agama logs store #{opt}`.strip
- end
-
- # Whatever has to be done at the end of installation
- #
- # If a finish method is given it will call the related shutdown
- # command.
- #
- # @param method [HALT, POWEROFF, STOP, REBOOT]
- # @return [Boolean]
- def finish_installation(method)
- unless installation_phase.finish?
- logger.error "The installer has not finished correctly. Please check logs"
- return false
- end
-
- if method == STOP
- logger.info("Finished the installation (stop).")
- return true
- end
-
- cmd = finish_cmd(method)
- logger.info("Finishing installation with '#{cmd}' (#{method})")
-
- !!system(cmd)
- end
-
- # Says whether running on iguana or not
- #
- # @return [Boolean] true when running on iguana
- def iguana?
- Dir.exist?("/iguana")
- end
-
- private
-
- # Possible finish methods
- STOP = "stop"
- REBOOT = "reboot"
- HALT = "halt"
- POWEROFF = "poweroff"
-
- # Default finish method to be called if not given or not find
- DEFAULT_METHOD = "reboot"
- # Finish shutdown option for each finish method
- SHUTDOWN_OPT = { REBOOT => "-r", HALT => "-H", POWEROFF => "-P" }.freeze
-
- # Configures storage.
- #
- # Storage is configured as part of the config phase. The config phase is executed after
- # selecting or registering a product.
- #
- # @param reprobe [Boolean] is used to keep the current storage config after registering a
- # product, see https://github.com/agama-project/agama/pull/2532.
- def configure_storage(reprobe)
- # Note that probing storage is not needed after the product registration, but let's keep the
- # current behavior.
- return storage.probe if reprobe
-
- # Select the product
- storage.product = software.selected_product
- end
-
- # @param method [String, nil]
- # @return [String] the cmd to be run for finishing the installation
- def finish_cmd(method)
- return "/usr/bin/agamactl -k" if iguana?
-
- opt = SHUTDOWN_OPT[method]
- unless opt
- log.info "Not recognized method, using the default one (reboot)."
- opt = SHUTDOWN_OPT[DEFAULT_METHOD]
- end
- "/usr/sbin/shutdown #{opt} now"
- end
-
- attr_reader :config
-
- # @return [ServiceStatusRecorder]
- attr_reader :service_status_recorder
-
- # Runs post partitioning scripts
- def run_post_partitioning_scripts
- client = Agama::HTTP::Clients::Scripts.new(logger)
- client.run("postPartitioning")
- end
- end
-end
diff --git a/service/lib/agama/network.rb b/service/lib/agama/network.rb
deleted file mode 100644
index bd91caff30..0000000000
--- a/service/lib/agama/network.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "singleton"
-require "yast"
-require "yast2/systemd/service"
-require "y2network/proposal_settings"
-require "agama/proxy_setup"
-require "agama/http"
-
-Yast.import "Installation"
-
-module Agama
- # Backend class to handle network configuration
- class Network
- def initialize(logger)
- @logger = logger
- end
-
- def startup
- persist_connections if do_proposal?
- end
-
- # Writes the network configuration to the installed system
- #
- # * Copies the connections configuration for NetworkManager, as Agama is not
- # performing further configuration of the network.
- # * Enables the NetworkManager service.
- def install
- copy_files
- enable_service
-
- ProxySetup.instance.install
- end
-
- def link_resolv
- return unless File.exist?(RESOLV)
-
- link = File.join(Yast::Installation.destdir, RESOLV)
- target = File.join(RUN_NM_DIR, File.basename(RESOLV))
-
- return if File.exist?(link)
-
- FileUtils.touch RESOLV_FLAG
- FileUtils.ln_s target, link
- end
-
- def unlink_resolv
- return unless File.exist?(RESOLV_FLAG)
-
- link = File.join(Yast::Installation.destdir, RESOLV)
- FileUtils.rm_f link
- FileUtils.rm_f RESOLV_FLAG
- end
-
- private
-
- # @return [Logger]
- attr_reader :logger
-
- HOSTNAME = "/etc/hostname"
- RESOLV = "/etc/resolv.conf"
- NOT_COPY_NETWORK = "/run/agama/not_copy_network"
- AGAMA_SYSTEMD_LINK = "/run/agama/systemd/network"
- SYSTEMD_LINK = "/etc/systemd/network"
- RESOLV_FLAG = "/run/agama/manage_resolv"
- ETC_NM_DIR = "/etc/NetworkManager"
- RUN_NM_DIR = "/run/NetworkManager"
- private_constant :ETC_NM_DIR
-
- def enable_service
- service = Yast2::Systemd::Service.find("NetworkManager")
- if service.nil?
- logger.error "NetworkManager service was not found"
- return
- end
-
- service.enable
- end
-
- # Copies NetworkManager configuration files
- def copy_files
- copy(HOSTNAME)
-
- copy_directory(
- AGAMA_SYSTEMD_LINK,
- File.join(Yast::Installation.destdir, SYSTEMD_LINK)
- )
-
- return unless Dir.exist?(ETC_NM_DIR)
- return if File.exist?(NOT_COPY_NETWORK)
-
- copy_directory(
- File.join(ETC_NM_DIR, "system-connections"),
- File.join(Yast::Installation.destdir, ETC_NM_DIR, "system-connections")
- )
- end
-
- # Copies a directory
- #
- # This method checks whether the source directory exists. If preserves the target directory if
- # it exists (otherwise, it creates the directory).
- #
- # @param source [String] source directory
- # @param target [String] target directory
- def copy_directory(source, target)
- return unless Dir.exist?(source)
-
- FileUtils.mkdir_p(target)
- FileUtils.cp(Dir.glob(File.join(source, "*")), target)
- end
-
- # Copies a file
- #
- # This method checks whether the source file exists. It copies the file to the target system if
- # it exists
- #
- # @param source [String] source file
- # @param target [String,nil] target directory, only needed in case it is different to the
- # original source path in the target system.
- def copy(source, target = nil)
- return unless File.exist?(source)
-
- path = target || File.join(Yast::Installation.destdir, source)
- FileUtils.mkdir_p(File.dirname(path))
- FileUtils.copy_entry(source, path)
- end
-
- def http_client
- @http_client ||= Agama::HTTP::Clients::Network.new(logger)
- end
-
- def persist_connections
- http_client.persist_connections
- end
-
- def copy_connections?
- http_client.state["copyNetwork"]
- end
-
- def do_proposal?
- return false unless copy_connections?
- return false if http_client.connections.any? { |c| c["persistent"] }
-
- !http_client.connections.empty?
- end
- end
-end
diff --git a/service/lib/agama/proxy_setup.rb b/service/lib/agama/proxy_setup.rb
deleted file mode 100644
index 00ae9e3f88..0000000000
--- a/service/lib/agama/proxy_setup.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "uri"
-require "fileutils"
-require "agama/helpers"
-
-module Agama
- # This class is responsible of parsing the proxy url from the kernel cmdline or configured
- # through the dracut ask prompt configuration file (/etc/cmdline-menu.conf) during the boot
- # proccess of the system writing the configuration to /etc/sysconfig/proxy
- class ProxySetup
- include Singleton
- include Yast
- include Logger
- include Helpers
-
- CMDLINE_PATH = "/proc/cmdline"
- CMDLINE_MENU_CONF = "/etc/cmdline-menu.conf"
- PACKAGES = ["microos-tools"].freeze
- CONFIG_PATH = "/etc/sysconfig/proxy"
- PROPOSAL_ID = "network_proposal"
-
- # @return [URI::Generic]
- attr_accessor :proxy
-
- alias_method :logger, :log
-
- # Constructor
- def initialize
- Yast.import "Proxy"
- Yast.import "Installation"
- Yast.import "PackagesProposal"
-
- Proxy.Read
- end
-
- def run
- read
- write
- end
-
- def propose
- add_packages if Proxy.enabled
- end
-
- def install
- return unless Proxy.enabled
-
- on_local { copy_files }
- enable_services
- end
-
- private
-
- def read
- self.proxy = proxy_from_cmdline || proxy_from_dracut
- end
-
- def proxy_from_dracut
- return unless File.exist?(CMDLINE_MENU_CONF)
-
- options = File.read(CMDLINE_MENU_CONF)
- proxy_url_from(options)
- end
-
- def proxy_url_from(options)
- proxy_url = options.split.find { |o| o.start_with?(/proxy/i) }
- return unless proxy_url
-
- URI(proxy_url.downcase.gsub("proxy=", ""))
- end
-
- def proxy_from_cmdline
- return unless File.exist?(CMDLINE_PATH)
-
- options = File.read(CMDLINE_PATH)
- proxy_url_from(options)
- end
-
- def proxy_import_settings
- proto = proxy.scheme
- # save user name and password separately
- settings = {
- "proxy_user" => proxy.user,
- "proxy_password" => proxy.password,
- "enabled" => true
- }
- proxy.user = nil
- proxy.password = nil
-
- settings["#{proto}_proxy"] = proxy.to_s
- # Use the proxy also for https and ftp
- if proto == "http"
- settings["https_proxy"] = proxy.to_s
- settings["ftp_proxy"] = proxy.to_s
- end
- settings
- end
-
- def write
- return unless proxy
-
- settings = proxy_import_settings
- Proxy.Import(settings)
-
- log.info "Writing proxy settings: #{proxy.scheme}_proxy = '#{proxy}'"
- log.debug "Writing proxy settings: #{settings}"
-
- Proxy.Write
- end
-
- def add_packages
- log.info "Selecting these packages for installation: #{PACKAGES}"
- Yast::PackagesProposal.SetResolvables(PROPOSAL_ID, :package, PACKAGES)
- end
-
- def copy_files
- log.info "Copying proxy configuration to the target system"
- ::FileUtils.cp(CONFIG_PATH, File.join(Yast::Installation.destdir, CONFIG_PATH))
- end
-
- def enable_services
- service = Yast2::Systemd::Service.find("setup-systemd-proxy-env")
- if service.nil?
- log.error "setup-systemd-proxy-env service was not found"
- return
- end
-
- Yast::Execute.on_target!("systemctl", "enable", "setup-systemd-proxy-env.service")
- Yast::Execute.on_target!("systemctl", "enable", "setup-systemd-proxy-env.path")
- end
- end
-end
diff --git a/service/lib/agama/registration.rb b/service/lib/agama/registration.rb
deleted file mode 100644
index 4dd3e483de..0000000000
--- a/service/lib/agama/registration.rb
+++ /dev/null
@@ -1,541 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "fileutils"
-require "yast"
-require "ostruct"
-require "suse/connect"
-require "y2packager/new_repository_setup"
-require "y2packager/resolvable"
-require "yast2/execute"
-
-require "agama/cmdline_args"
-require "agama/errors"
-require "agama/registered_addon"
-require "agama/ssl/certificate"
-require "agama/ssl/errors"
-require "agama/ssl/fingerprint"
-require "agama/ssl/storage"
-
-Yast.import "Arch"
-Yast.import "Pkg"
-
-module Agama
- # Handles everything related to registration of system to SCC, RMT or similar.
- class Registration
- include Yast::I18n
-
- # NOTE: identical and keep in sync with Software::Manager::TARGET_DIR
- TARGET_DIR = "/run/agama/zypp"
- private_constant :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
-
- DEFAULT_REGISTRATION_URL = "https://scc.suse.com"
- private_constant :DEFAULT_REGISTRATION_URL
-
- # Code used for registering the product.
- #
- # @return [boolean] true if base product is already registered
- attr_reader :registered
-
- # Code used for registering the product.
- #
- # @return [String, nil] nil if the product is not registered yet.
- attr_reader :reg_code
-
- # Email used for registering the product.
- #
- # @return [String, nil]
- attr_reader :email
-
- # List of already registered addons
- #
- # @return [Array]
- attr_reader :registered_addons
-
- # Overwritten default registration URL
- #
- # @return [String, nil]
- attr_accessor :registration_url
-
- # @param software_manager [Agama::Software::Manager]
- # @param logger [Logger]
- def initialize(software_manager, logger)
- @software = software_manager
- @logger = logger
- @services = []
- @credentials_files = []
- @registered_addons = []
- @registration_url = registration_url_from_cmdline
- @registered = false
- end
-
- # Registers the selected product.
- #
- # @raise [
- # SocketError|Timeout::Error|SUSE::Connect::ApiError|
- # SUSE::Connect::MissingSccCredentialsFile|SUSE::Connect::MissingSccCredentialsFile|
- # OpenSSL::SSL::SSLError|JSON::ParserError|Agama::Software::AddServiceError
- # ]
- #
- # @param code [String] Registration code.
- # @param email [String] Email for registering the product.
- def register(code, email: "")
- return if product.nil? || registered
-
- catch_registration_errors do
- reg_params = connect_params(token: code, email: email)
-
- # hide the registration code in the logs
- log_params = reg_params.dup
- log_params[:token] = "" if log_params[:token] != ""
- @logger.info "Registering system #{target_distro}: #{log_params.inspect}"
-
- @login, @password = SUSE::Connect::YaST.announce_system(reg_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, GLOBAL_CREDENTIALS_PATH)
-
- activate_params = {}
- @logger.info "Activating product #{base_target_product.inspect}"
- service = SUSE::Connect::YaST.activate_product(base_target_product, activate_params, email)
- process_service(service)
- @registered = true
- end
- ensure
- @reg_code = code
- @email = email
- run_on_change_callbacks
- end
-
- def register_addon(name, version, code)
- catch_registration_errors do
- register_version = if version.empty?
- # version is not specified, find it automatically
- find_addon_version(name)
- else
- # use the explicitly required version
- version
- end
-
- if @registered_addons.any? { |a| a.name == name && a.version == register_version }
- @logger.info "Addon #{name}-#{register_version} already registered, skipping registration"
- return
- end
-
- @logger.info "Registering addon #{name}-#{register_version}"
- # do not log the code, but at least log if it is empty
- @logger.info "Using empty registration code" if code.empty?
-
- target_product = OpenStruct.new(
- arch: Yast::Arch.rpm_arch,
- identifier: name,
- version: register_version
- )
- activate_params = { token: code }
- service = SUSE::Connect::YaST.activate_product(target_product, activate_params, @email)
- process_service(service)
-
- @registered_addons << RegisteredAddon.new(name, register_version, !version.empty?, code)
- # select the products to install
- @software.addon_products(find_addon_products)
-
- run_on_change_callbacks
- end
- end
-
- # Deregisters the selected product.
- #
- # It uses the registration code and email passed to {#register}.
- #
- # @raise [
- # SocketError|Timeout::Error|SUSE::Connect::ApiError|
- # SUSE::Connect::MissingSccCredentialsFile|SUSE::Connect::MissingSccCredentialsFile|
- # OpenSSL::SSL::SSLError|JSON::ParserError
- # ]
- def deregister
- return unless registered
-
- @services.each do |service|
- Y2Packager::NewRepositorySetup.instance.services.delete(service.name)
- @software.remove_service(service)
- end
-
- # reset
- @software.addon_products([])
- @services = []
- @available_addons = nil
-
- reg_params = connect_params(token: reg_code, email: email)
- SUSE::Connect::YaST.deactivate_system(reg_params)
- FileUtils.rm(GLOBAL_CREDENTIALS_PATH) # connect does not remove it itself
- @credentials_files.each do |credentials_file|
- FileUtils.rm(File.join(TARGET_DIR, credentials_path(credentials_file)))
- end
- @credentials_files = []
-
- @registered = false
- @reg_code = nil
- @email = nil
- @registered_addons = []
-
- run_on_change_callbacks
- end
-
- # Copies configuration and credentials files to the target system.
- #
- # The configuration file is copied only if a registration URL was given.
- def finish
- return unless registered
-
- files = [[
- GLOBAL_CREDENTIALS_PATH, File.join(Yast::Installation.destdir, GLOBAL_CREDENTIALS_PATH)
- ]]
- @credentials_files.each do |credentials_file|
- files << [
- File.join(TARGET_DIR, credentials_path(credentials_file)),
- File.join(Yast::Installation.destdir, credentials_path(credentials_file))
- ]
- end
-
- if registration_url
- SUSE::Connect::YaST.write_config("url" => registration_url)
- files << [
- SUSE::Connect::Config::DEFAULT_CONFIG_FILE,
- File.join(Yast::Installation.destdir, SUSE::Connect::Config::DEFAULT_CONFIG_FILE)
- ]
- end
-
- files.each do |src_dest|
- FileUtils.cp(*src_dest)
- end
-
- copy_certificates_to_target
- end
-
- # Get the available addons for the specified base product.
- #
- # @note The result is bound to the registration code used for the base product, the result
- # might be different for different codes. E.g. the Alpha/Beta extensions might or might not
- # be included in the list.
- def available_addons
- return @available_addons if @available_addons
-
- @available_addons = SUSE::Connect::YaST.show_product(base_target_product,
- connect_params).extensions
- @logger.info "Available addons: #{available_addons.inspect}"
- @available_addons
- end
-
- # Callbacks to be called when registration changes (e.g., a different product is selected).
- def on_change(&block)
- @on_change_callbacks ||= []
- @on_change_callbacks << block
- end
-
- private
-
- # @return [Agama::Software::Manager]
- attr_reader :software
-
- # Currently selected product.
- #
- # @return [Agama::Software::Product, nil]
- def product
- software.product
- end
-
- def copy_certificates_to_target
- cert_file = SSL::Certificate.default_certificate_path
- return unless File.exist?(cert_file) # no certificate imported?
-
- # copy the imported certificate
- @logger.info "Copying SSL certificate (#{cert_file}) to the target system..."
- cert_target_file = File.join("/mnt",
- SUSE::Connect::YaST::SERVER_CERT_FILE)
- ::FileUtils.mkdir_p(File.dirname(cert_target_file))
- ::FileUtils.cp(cert_file, cert_target_file)
-
- # update the certificate links
- cmd = SUSE::Connect::YaST::UPDATE_CERTIFICATES
- @logger.info "Updating certificate links (#{cmd})..."
- # beware that Yast::Execute.on_target! does not work here due
- # to not changed SCR root when registration finish is executed.
- # So do chroot explicitelly.
- Yast::Execute.locally!(cmd, chroot: "/mnt")
- end
-
- # Product name expected by SCC.
- #
- # @return [String] E.g., "ALP-Dolomite-1-x86_64".
- def target_distro
- v = product.version.to_s.split(".").first || "1"
- "#{product.id}-#{v}-#{Yast::Arch.rpm_arch}"
- end
-
- def run_on_change_callbacks
- @on_change_callbacks&.map(&:call)
- end
-
- # taken from https://github.com/yast/yast-registration/blob/master/src/lib/registration/url_helpers.rb#L109
- def credentials_from_url(url)
- parsed_url = URI(url)
- params = URI.decode_www_form(parsed_url.query).to_h
-
- params["credentials"]
- rescue StandardError
- # if something goes wrong try to continue like if there is no credentials param
- nil
- end
-
- def credentials_path(file)
- File.join(SUSE::Connect::YaST::DEFAULT_CREDENTIALS_DIR, file)
- end
-
- # Returns the arguments to connect to the registration server
- #
- # @param params [Hash] additional parameters (e.g., email and token)
- # @return [Hash]
- def connect_params(params = {})
- default_params = {}
- default_params[:language] = http_language if http_language
- default_params[:url] = registration_url || DEFAULT_REGISTRATION_URL
- default_params[:verify_callback] = verify_callback
- default_params.merge(params)
- end
-
- def http_language
- lang = Yast::WFM.GetLanguage
- return nil if ["POSIX", "C"].include?(lang)
-
- # remove the encoding suffix (e.g. ".UTF-8")
- lang = lang.sub(/\..*$/, "")
-
- # replace Linux locale separator "_" by the HTTP separator "-", downcase the country name
- # see https://www.rfc-editor.org/rfc/rfc9110.html#name-accept-language
- lang.tr!("_", "-")
- lang.downcase!
-
- lang
- end
-
- # returns SSL verify callback
- def verify_callback
- lambda do |verify_ok, context|
-
- # we cannot raise an exception with details here (all exceptions in
- # verify_callback are caught and ignored), we need to store the error
- # details in a global instance
- store_ssl_error(context) unless verify_ok
-
- verify_ok
- rescue StandardError => e
- @logger.error "Exception in SSL verify callback: #{e.class}: #{e.message} : #{e.backtrace}"
- # the exception will be ignored, but reraise anyway...
- raise e
-
- end
- end
-
- def store_ssl_error(context)
- @logger.error "SSL verification failed: #{context.error}: #{context.error_string}"
- SSL::Errors.instance.ssl_error_code = context.error
- SSL::Errors.instance.ssl_error_msg = context.error_string
- SSL::Errors.instance.ssl_failed_cert =
- context.current_cert ? SSL::Certificate.load(context.current_cert) : nil
- end
-
- def catch_registration_errors(&block)
- # import the SSL certificate just once to avoid an infinite loop
- certificate_imported = false
- begin
- # reset the previous SSL errors
- Agama::SSL::Errors.instance.reset
-
- block.call
-
- true
- rescue OpenSSL::SSL::SSLError => e
- @logger.error "OpenSSL error: #{e}"
- should_retry = handle_ssl_error(e, certificate_imported)
- puts "handle ssl error #{should_retry}"
- if should_retry
- certificate_imported = true
- SSL::Errors.instance.ssl_failed_cert.import
- retry
- end
- raise e
- end
- end
-
- # @return [Boolean]
- def handle_ssl_error(_error, certificate_imported)
- return false if certificate_imported
-
- cert = SSL::Errors.instance.ssl_failed_cert
- return false unless cert
-
- # Import certificate if it matches predefined fingerprint.
- return true if SSL::Storage.instance.fingerprints.any? { |f| cert.match_fingerprint?(f) }
-
- error_code = SSL::Errors.instance.ssl_error_code
- return false unless SSL::ErrorCodes::IMPORT_ERROR_CODES.include?(error_code)
-
- question = certificate_question(cert)
- questions_client = Agama::HTTP::Clients::Questions.new(@logger)
- questions_client.ask(question) { |a| a.action == :trust }
- end
-
- # @param certificate [Agama::SSL::Certificate]
- # @return [Agama::Question]
- def certificate_question(certificate)
- question_data = {
- "url" => registration_url || DEFAULT_REGISTRATION_URL,
- "issuer_name" => certificate.issuer_name,
- "issue_date" => certificate.issued_on,
- "expiration_date" => certificate.expires_on,
- "sha1_fingerprint" => certificate.fingerprint(SSL::Fingerprint::SHA1).value,
- "sha256_fingerprint" => certificate.fingerprint(SSL::Fingerprint::SHA256).value
- }.filter { |_, v| !v.nil? }
-
- question_text = _(
- "Trying to import a self signed certificate. Do you want to trust it and register the " \
- "product?"
- )
-
- Agama::Question.new(
- qclass: "registration.certificate",
- text: question_text,
- options: [:trust, :reject],
- default_option: :reject,
- data: question_data
- )
- end
-
- # Returns the URL of the registration server
- #
- # At this point, it just checks the kernel's command-line.
- #
- # @return [String, nil]
- def registration_url_from_cmdline
- cmdline_args = CmdlineArgs.read
- cmdline_args.data["register_url"]
- end
-
- # process a newly added service, create the credentials file and add the service to libzypp
- def process_service(service)
- @services << service
- credentials_file = credentials_from_url(service.url)
- if credentials_file
- @credentials_files << credentials_file
- # addons use the same SCC credentials as the base product
- SUSE::Connect::YaST.create_credentials_file(@login, @password,
- File.join(TARGET_DIR, credentials_path(credentials_file)))
- end
- Y2Packager::NewRepositorySetup.instance.add_service(service.name)
- @software.add_service(service)
- end
-
- # Find all addon products
- #
- # @return [Array] names of the products
- def find_addon_products
- # find all repositories for registered addons (their services)
- addon_repos = @services.reduce([]) do |acc, service|
- # skip the first service, it belongs to the base product
- next acc if service == @services.first
-
- acc.concat(service_repos(service))
- end
-
- # find all products from those repositories
- products = Y2Packager::Resolvable.find(kind: :product)
- products.select! do |product|
- addon_repos.any? { |addon_repo| product.source == addon_repo["SrcId"] }
- end
-
- products.map!(&:name)
- @logger.info "Addon products to install: #{products}"
-
- products
- end
-
- # Find all repositories belonging to a service.
- #
- # @param product_service [OpenStruct] repository service from suseconnect
- #
- # @return [Array] repository data as returned by the Pkg.SourceGeneralData
- # call, additionally with the "SrcId" key
- def service_repos(product_service)
- @logger.info "product_service: #{product_service.inspect}"
- repo_data = Yast::Pkg.SourceGetCurrent(false).map { |repo| repository_data(repo) }
-
- service_name = product_service.name
- # select only repositories belonging to the product services
- repos = repo_data.select { |repo| service_name == repo["service"] }
- @logger.info "Service #{service_name.inspect} repositories: #{repos}"
-
- repos
- end
-
- # Get repository data
- # @param [Fixnum] repo repository ID
- # @return [Hash] repository properties, including the repository ID ("SrcId" key)
- def repository_data(repo)
- data = Yast::Pkg.SourceGeneralData(repo)
- data["SrcId"] = repo
- data
- end
-
- # Find the version for the specified addon, if none if multiple addons with the same name
- # are found an exception is thrown.
- #
- # @return [String] the addon version, e.g. "16.0"
- def find_addon_version(name)
- raise Errors::Registration::ExtensionNotFound, name unless available_addons
-
- requested_addons = available_addons.select { |a| a.identifier == name }
- case requested_addons.size
- when 0
- raise Errors::Registration::ExtensionNotFound, name
- when 1
- requested_addons.first.version
- else
- raise Errors::Registration::MultipleExtensionsFound.new(name,
- requested_addons.map(&:version))
- end
- end
-
- # Construct the base product data for sending to the server
- def base_target_product
- OpenStruct.new(
- arch: Yast::Arch.rpm_arch,
- identifier: product.id,
- version: product.version || "1.0"
- )
- end
- end
-end
diff --git a/service/lib/agama/security.rb b/service/lib/agama/security.rb
deleted file mode 100644
index 0f6d7af9e6..0000000000
--- a/service/lib/agama/security.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "y2security/lsm"
-require "yast2/execute"
-require "agama/config"
-require "agama/http"
-
-Yast.import "Bootloader"
-
-# FIXME: monkey patching of security config to not read control.xml and
-# instead use Agama::Config
-# TODO: add ability to set product features in LSM::Base
-module Y2Security
- module LSM
- # modified LSM Base class to use Agama config
- class Base
- def product_feature_settings
- return @product_feature_settings unless @product_feature_settings.nil?
-
- value = ::Agama::Config.current.data["security"]["available_lsms"][id.to_s]
- res = if value
- {
- selectable: true,
- configurable: true,
- patterns: (value["patterns"] || []).join(" "),
- mode: value["policy"]
- }
- else
- {
- selectable: false,
- configurable: false,
- patterns: "",
- mode: nil
- }
- end
- @product_feature_settings = res
- end
- end
- end
-end
-
-module Agama
- # Backend class between dbus service and yast code
- class Security
- # @return [Logger]
- attr_reader :logger
-
- # Constructor
- #
- # @param logger [Logger]
- # @param config [Agama::Config]
- def initialize(logger, config)
- @config = config
- @logger = logger
- end
-
- def write
- # at first clear previous kernel params
- selected = lsm_selected
- selected&.reset_kernel_params
-
- candidate = select_software_lsm
- return unless candidate
-
- lsm_config.select(candidate)
- kernel_params = lsm_selected.kernel_params
- # write manually here to bootloader as lsm_config.save do more than agama wants (bsc#1247046)
- @logger.info("Modifying Bootlooader kernel params using #{kernel_params}")
- Yast::Bootloader.modify_kernel_params(kernel_params)
- end
-
- private
-
- attr_reader :config
-
- def select_software_lsm
- candidates = [lsm_selected&.id&.to_s].compact | available_lsms.keys
-
- candidates.find { |c| proposal_patterns_include?(c) }
- end
-
- def available_lsms
- config.data.dig("security", "available_lsms") || {}
- end
-
- def proposal_patterns_include?(lsm_id)
- patterns = available_lsms.dig(lsm_id.to_s, "patterns") || []
-
- (patterns - proposal_patterns).empty?
- end
-
- def lsm_config
- Y2Security::LSM::Config.instance
- end
-
- def lsm_selected
- lsm_config.selected
- end
-
- def lsm_patterns(lsm_id)
- config.data.dig("security", "available_lsms", lsm_id.to_s, "patterns") || []
- end
-
- def proposal_patterns
- return @proposal_patterns if @proposal_patterns
-
- proposal = software_client.proposal || {}
-
- @proposal_patterns =
- (proposal["patterns"] || {}).select { |_p, v| [0, 1].include? v }.keys
- end
-
- # Returns the client to ask the software service
- #
- # @return [Agama::HTTP::Clients::Software]
- def software_client
- @software_client ||= Agama::HTTP::Clients::Software.new(logger)
- end
- end
-end
diff --git a/service/lib/agama/software.rb b/service/lib/agama/software.rb
deleted file mode 100644
index cc38ef8194..0000000000
--- a/service/lib/agama/software.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-module Agama
- # Namespace for software backend
- module Software
- end
-end
-
-require "agama/software/manager"
diff --git a/service/lib/agama/software/callbacks.rb b/service/lib/agama/software/callbacks.rb
deleted file mode 100644
index 659a021f47..0000000000
--- a/service/lib/agama/software/callbacks.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-module Agama
- module Software
- # Namespace for software callbacks
- module Callbacks
- end
- end
-end
-
-require "agama/software/callbacks/digest"
-require "agama/software/callbacks/media"
-require "agama/software/callbacks/pkg_gpg_check"
-require "agama/software/callbacks/progress"
-require "agama/software/callbacks/provide"
-require "agama/software/callbacks/script"
-require "agama/software/callbacks/signature"
diff --git a/service/lib/agama/software/callbacks/base.rb b/service/lib/agama/software/callbacks/base.rb
deleted file mode 100644
index fa6a26199d..0000000000
--- a/service/lib/agama/software/callbacks/base.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-
-module Agama
- module Software
- module Callbacks
- # Base class for libzypp callbacks for sharing some common texts.
- class Base
- include Yast::I18n
-
- # Constructor
- #
- # @param questions_client [Agama::HTTP::Clients::Questions]
- # @param logger [Logger]
- def initialize(questions_client, logger)
- textdomain "agama"
- @questions_client = questions_client
- @logger = logger || ::Logger.new($stdout)
- end
-
- def setup
- raise NotImplementedError
- end
-
- # label for the "retry" action
- def retry_label
- # TRANSLATORS: button label, try downloading the failed package again
- _("Try again")
- end
-
- # label for the "continue" action
- def continue_label
- # TRANSLATORS: button label, ignore the failed download, skip package installation
- _("Continue anyway")
- end
-
- # label for the "abort" action
- def abort_label
- # TRANSLATORS: button label, abort the installation completely after an error
- _("Abort installation")
- end
-
- # label for the "skip" action
- def skip_label
- # TRANSLATORS: button label, skip the error
- _("Skip")
- end
-
- # label for the "yes" action
- def yes_label
- # TRANSLATORS: button label
- _("Yes")
- end
-
- # label for the "no" action
- def no_label
- # TRANSLATORS: button label
- _("No")
- end
-
- private
-
- # @return [Agama::HTTP::Clients::Questions]
- attr_reader :questions_client
-
- # @return [Logger]
- attr_reader :logger
- end
- end
- end
-end
diff --git a/service/lib/agama/software/callbacks/digest.rb b/service/lib/agama/software/callbacks/digest.rb
deleted file mode 100644
index d36f9157b0..0000000000
--- a/service/lib/agama/software/callbacks/digest.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "agama/question"
-require "agama/software/callbacks/base"
-
-Yast.import "Pkg"
-
-module Agama
- module Software
- module Callbacks
- # Callbacks related to digest handling
- class Digest < Base
- def setup
- Yast::Pkg.CallbackAcceptFileWithoutChecksum(
- Yast::FunRef.new(
- method(:accept_file_without_checksum), "boolean (string)"
- )
- )
- Yast::Pkg.CallbackAcceptUnknownDigest(
- Yast::FunRef.new(
- method(:accept_unknown_digest), "boolean (string, string)"
- )
- )
- Yast::Pkg.CallbackAcceptWrongDigest(
- Yast::FunRef.new(
- method(:accept_wrong_digest), "boolean (string, string, string)"
- )
- )
- end
-
- # Callback to accept a file without a checksum
- #
- # @param filename [String] File name
- # @return [Boolean]
- def accept_file_without_checksum(filename)
- name = strip_download_prefix(filename)
- message = format(
- _(
- "No checksum for the file %{file} was found in the repository. This means that " \
- "although the file is part of the signed repository, the list of checksums " \
- "does not mention this file. Use it anyway?"
- ), file: name
- )
-
- question = Agama::Question.new(
- qclass: "software.digest.no_digest",
- text: message,
- options: [yes_label.to_sym, no_label.to_sym],
- default_option: yes_label.to_sym
- )
- questions_client.ask(question) do |answer|
- answer.action == yes_label.to_sym
- end
- end
-
- # Callback to accept an unknown digest
- #
- # @param filename [String] File name
- # @param digest [String] expected checksum
- # @return [Boolean]
- def accept_unknown_digest(filename, digest)
- name = strip_download_prefix(filename)
- message = format(
- _(
- "The checksum of the file %{file} is \"%{digest}\" but the expected checksum is " \
- "unknown. This means that the origin and integrity of the file cannot be verified. " \
- "Use it anyway?"
- ), file: name, digest: digest
- )
-
- question = Agama::Question.new(
- qclass: "software.digest.unknown_digest",
- text: message,
- options: [yes_label.to_sym, no_label.to_sym],
- default_option: yes_label.to_sym
- )
- questions_client.ask(question) do |answer|
- answer.action == yes_label.to_sym
- end
- end
-
- # Callback to accept wrong digest
- #
- # @param filename [String] File name
- # @param expected_digest [String] expected checksum
- # @param found_digest [String] found checksum
- # @return [Boolean]
- def accept_wrong_digest(filename, expected_digest, found_digest)
- name = strip_download_prefix(filename)
- message = format(
- _(
- "The expected checksum of file %{file} is \"%{found}\" but it was expected to be " \
- "\"%{expected}\". The file has changed by accident or by an attacker since the " \
- "creater signed it. Use it anyway?"
- ), file: name, found: found_digest, expected: expected_digest
- )
-
- question = Agama::Question.new(
- qclass: "software.digest.unknown_digest",
- text: message,
- options: [yes_label.to_sym, no_label.to_sym],
- default_option: yes_label.to_sym
- )
- questions_client.ask(question) do |answer|
- answer.action == yes_label.to_sym
- end
- end
-
- private
-
- # helper to strip download path. It uses internal knowledge that download
- # prefix ends in TmpDir.* zypp location
- #
- # From https://github.com/yast/yast-yast2/blob/master/library/packages/src/modules/SignatureCheckDialogs.rb#L836
- def strip_download_prefix(path)
- path.sub(/\A\/.*\/TmpDir\.[^\/]+\//, "")
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/software/callbacks/media.rb b/service/lib/agama/software/callbacks/media.rb
deleted file mode 100644
index f0cf972641..0000000000
--- a/service/lib/agama/software/callbacks/media.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2021-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "logger"
-require "yast"
-require "agama/question"
-require "agama/software/callbacks/base"
-require "agama/software/repository"
-
-Yast.import "Pkg"
-Yast.import "URL"
-
-module Agama
- module Software
- module Callbacks
- # Callbacks related to media handling
- class Media < Base
- def initialize(questions_client, logger)
- super
- # retry counter
- self.attempt = 0
- end
-
- # Register the callbacks
- def setup
- Yast::Pkg.CallbackMediaChange(
- Yast::FunRef.new(
- method(:media_change),
- "string (string, string, string, string, integer, string, integer, string, " \
- "boolean, list , integer)"
- )
- )
- Yast::Pkg.CallbackStartProvide(
- Yast::FunRef.new(method(:start_provide), "void (string, integer, boolean)")
- )
- end
-
- # @param name [String] name of the package to download
- # @param size [Integer] download size
- # @param _remote [Boolean] true if the package is downloaded from a remote repository,
- # false for local packages
- def start_provide(name, size, _remote)
- self.attempt = 1
- logger.debug("Downloading #{name}, size: #{size}")
- end
-
- # Media change callback
- #
- # @return [String]
- # @see https://github.com/yast/yast-yast2/blob/19180445ab935a25edd4ae0243aa7a3bcd09c9de/library/packages/src/modules/PackageCallbacks.rb#L620
- # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
- def media_change(error_code, error, url, product, current, current_label, wanted,
- wanted_label, double_sided, devices, current_device)
- logger.debug(
- format("MediaChange callback: error_code: %s, error: %s, url: %s, product: %s, " \
- "current: %s, current_label: %s, wanted: %s, wanted_label: %s, " \
- "double_sided: %s, devices: %s, current_device: %s",
- error_code,
- error,
- Yast::URL.HidePassword(url),
- product,
- current,
- current_label,
- wanted,
- wanted_label,
- double_sided,
- devices,
- current_device)
- )
-
- # "IO" = IO error (scratched DVD or HW failure)
- # "IO_SOFT" = network timeout
- # in other cases automatic retry usually does not make much sense
- if ["IO", "IO_SOFT"].include?(error_code) && attempt <= Repository::RETRY_COUNT
- self.attempt += 1
- logger.debug("Retry in #{Repository::RETRY_DELAY} seconds, attempt #{attempt}...")
- sleep(Repository::RETRY_DELAY)
-
- # retry
- return ""
- end
-
- question = Agama::Question.new(
- qclass: "software.package_error.medium_error",
- text: error,
- options: [retry_label.to_sym, continue_label.to_sym],
- data: { "url" => url }
- )
- questions_client.ask(question) do |answer|
- if answer.action == retry_label.to_sym
- self.attempt += 1
- ""
- else
- "S"
- end
- end
- end
- # rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
-
- private
-
- attr_accessor :attempt
- end
- end
- end
-end
diff --git a/service/lib/agama/software/callbacks/pkg_gpg_check.rb b/service/lib/agama/software/callbacks/pkg_gpg_check.rb
deleted file mode 100644
index 8c0537fb92..0000000000
--- a/service/lib/agama/software/callbacks/pkg_gpg_check.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "logger"
-require "yast"
-require "agama/cmdline_args"
-require "agama/question"
-require "agama/software/manager"
-require "agama/software/callbacks/base"
-
-Yast.import "Pkg"
-
-module Agama
- module Software
- module Callbacks
- # Provide callbacks
- class PkgGpgCheck < Base
- # https://github.com/openSUSE/libzypp/blob/6b385649d18269fcba8d80ed356adb8100be920d/zypp/target/rpm/RpmDb.h#L378-L384
- CHK_OK = 0 # Signature is OK
- CHK_NOTFOUND = 1 # Signature is unknown type
- CHK_FAIL = 2 # Signature does not verify
- CHK_NOTTRUSTED = 3 # Signature is OK, but key is not trusted
- CHK_NOKEY = 4 # Public key is unavailable
- CHK_ERROR = 5 # File does not exist or can't be opened
- CHK_NOSIG = 6 # File has no gpg signature
-
- # Register the callbacks
- def setup
- Yast::Pkg.CallbackPkgGpgCheck(
- Yast::FunRef.new(method(:pkg_gpg_check), "string(map)")
- )
- end
-
- # Package GPG check callback
- #
- # @param data [Hash] callback data, see
- # https://github.com/yast/yast-pkg-bindings/blob/853496f527543e6d51730fd7e3126ad94b13c303/src/Callbacks.cc#L739
- # @return [String] "I" for ignore, "R" for retry and "A" for abort,
- # empty string ("") means no decision has been made
- # @see https://github.com/yast/yast-yast2/blob/19180445ab935a25edd4ae0243aa7a3bcd09c9de/library/packages/src/modules/PackageCallbacks.rb#L620
- def pkg_gpg_check(data)
- error_code = data["CheckPackageResult"]
- package = data["Package"]
-
- if error_code == CHK_OK
- logger.debug "GPG check succeeded for package #{package}"
- return ""
- end
-
- logger.warn "GPG check failed for package #{package}, error code: #{error_code}"
-
- # ignore the error when the package comes from the DUD repository and
- # the DUD package GPG checks are disabled via a boot option
- if data["RepoMediaUrl"] == Agama::Software::Manager.dud_repository_url &&
- ignore_dud_packages_gpg_errors?
-
- logger.info "Ignoring the GPG check failure for a DUD package"
- return "I"
- end
-
- # no decision made, the error will be reported by the DoneProvide callback again
- ""
- end
-
- private
-
- # Should be the DUD packages GPG signatures verified? The GPG errors can
- # be ignored by using the "inst.dud_packages.gpg=0" boot option
- #
- # @return [Boolean] `true` if the GPG errors should be ignored, `false` otherwise
- def ignore_dud_packages_gpg_errors?
- return @ignore_dud_packages_gpg_errors unless @ignore_dud_packages_gpg_errors.nil?
-
- cmdline_args = CmdlineArgs.read
- dud = cmdline_args.data["dud_packages"]
- gpg = dud && dud["gpg"]
-
- @ignore_dud_packages_gpg_errors = [false, "0"].include?(gpg)
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/software/callbacks/progress.rb b/service/lib/agama/software/callbacks/progress.rb
deleted file mode 100644
index ce23409ca1..0000000000
--- a/service/lib/agama/software/callbacks/progress.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2021-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "logger"
-require "yast"
-require "agama/question"
-require "agama/http/clients"
-require "agama/software/callbacks/base"
-
-Yast.import "Pkg"
-
-module Agama
- module Software
- module Callbacks
- # This class represents the installer status
- class Progress < Base
- class << self
- def setup(pkg_count, progress, logger)
- new(pkg_count, progress, logger).setup
- end
- end
-
- def initialize(pkg_count, progress, logger)
- super(questions_client, logger)
-
- textdomain "agama"
-
- @total = pkg_count
- @installed = 0
- @progress = progress
- @logger = logger || ::Logger.new($stdout)
- end
-
- def setup
- Yast::Pkg.CallbackStartPackage(
- Yast::FunRef.new(
- method(:start_package), "void (string, string, string, integer, boolean)"
- )
- )
-
- Yast::Pkg.CallbackDonePackage(
- Yast::FunRef.new(
- method(:done_package), "string (integer, string)"
- )
- )
- end
-
- private
-
- # @return [Agama::Progress]
- attr_reader :progress
-
- # @return [String,nil]
- attr_accessor :current_package
-
- # @return [Logger]
- attr_reader :logger
-
- # @return [Agama::HTTP::Clients::Questions]
- def questions_client
- @questions_client ||= Agama::HTTP::Clients::Questions.new(logger)
- end
-
- def start_package(package, _file, _summary, _size, _other)
- progress.step("Installing #{package}")
- self.current_package = package
- end
-
- def done_package(error_code, description)
- return "" if error_code == 0
-
- logger.error("Package #{current_package} failed: #{description}")
-
- question = Agama::Question.new(
- qclass: "software.package_error.install_error",
- text: description,
- # FIXME: temporarily removed the "Abort" option until the final failed
- # state is handled properly
- options: [retry_label.to_sym, continue_label.to_sym],
- data: { "package" => current_package }
- )
-
- questions_client.ask(question) do |answer|
- case answer
- when retry_label.to_sym
- "R"
- # FIXME: temporarily disabled
- # when abort_label.to_sym
- # "C"
- when continue_label.to_sym
- "I"
- else
- logger.error("Unexpected response #{question_client.answer.inspect}, " \
- "ignoring the package error")
- "I"
- end
- end
- end
-
- def msg
- "Installing packages (#{@total - @installed} remains)"
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/software/callbacks/provide.rb b/service/lib/agama/software/callbacks/provide.rb
deleted file mode 100644
index 708c2d9745..0000000000
--- a/service/lib/agama/software/callbacks/provide.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "logger"
-require "yast"
-require "agama/question"
-require "agama/software/callbacks/base"
-
-Yast.import "Pkg"
-
-module Agama
- module Software
- module Callbacks
- # Provide callbacks
- class Provide < Base
- # From https://github.com/openSUSE/libzypp/blob/d90a93fc2a248e6592bd98114f82a0b88abadb72/zypp/ZYppCallbacks.h#L111
- NO_ERROR = 0
- NOT_FOUND = 1
- IO_ERROR = 2
- INVALID = 3
-
- # Register the callbacks
- def setup
- Yast::Pkg.CallbackDoneProvide(
- Yast::FunRef.new(method(:done_provide), "string (integer, string, string)")
- )
- end
-
- # DoneProvide callback
- #
- # @return [String, nil] "I" for ignore, "R" for retry and "C" for abort (not implemented)
- # @see https://github.com/yast/yast-yast2/blob/19180445ab935a25edd4ae0243aa7a3bcd09c9de/library/packages/src/modules/PackageCallbacks.rb#L620
- def done_provide(error, reason, name)
- args = [error, reason, name]
- logger.debug "DoneProvide callback: #{args.inspect}"
-
- error_code = case error
- when NO_ERROR, NOT_FOUND
- # "Not found" (error 1) is handled by the MediaChange callback.
- nil
- when IO_ERROR
- "IO_ERROR"
- when INVALID
- "INVALID"
- else
- logger.warn "DoneProvide: unknown error: '#{error}'"
- nil
- end
-
- return nil if error_code.nil?
-
- question = Agama::Question.new(
- qclass: "software.package_error.provide_error",
- text: reason,
- options: [retry_label.to_sym, continue_label.to_sym],
- data: { "package" => name, "error_code" => error_code }
- )
-
- questions_client.ask(question) do |answer|
- (answer.action == retry_label.to_sym) ? "R" : "I"
- end
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/software/callbacks/script.rb b/service/lib/agama/software/callbacks/script.rb
deleted file mode 100644
index 74b66c8d39..0000000000
--- a/service/lib/agama/software/callbacks/script.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "agama/question"
-require "agama/software/callbacks/base"
-
-Yast.import "Pkg"
-
-module Agama
- module Software
- module Callbacks
- # Script callbacks
- class Script < Base
- include Yast::I18n
-
- # Register the callbacks
- def setup
- Yast::Pkg.CallbackScriptProblem(
- Yast::FunRef.new(method(:script_problem), "string (string)")
- )
- end
-
- # DoneProvide callback
- #
- # @param description [String] Problem description
- # @return [String] "I" for ignore, "R" for retry and "C" for abort (not implemented)
- # @see https://github.com/yast/yast-yast2/blob/19180445ab935a25edd4ae0243aa7a3bcd09c9de/library/packages/src/modules/PackageCallbacks.rb#L620
- def script_problem(description)
- logger.debug "ScriptProblem callback: description: #{description}"
-
- message = _("There was a problem running a package script.")
- question = Agama::Question.new(
- qclass: "software.script_problem",
- text: message,
- options: [retry_label, continue_label],
- data: { "details" => description }
- )
- questions_client.ask(question) do |answer|
- (answer.action == retry_label.to_sym) ? "R" : "I"
- end
- end
-
- private
-
- # @return [Agama::HTTP::Clients::Questions]
- attr_reader :questions_client
-
- # @return [Logger]
- attr_reader :logger
- end
- end
- end
-end
diff --git a/service/lib/agama/software/callbacks/signature.rb b/service/lib/agama/software/callbacks/signature.rb
deleted file mode 100644
index 5aaabef771..0000000000
--- a/service/lib/agama/software/callbacks/signature.rb
+++ /dev/null
@@ -1,208 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "agama/question"
-require "agama/software/callbacks/base"
-require "agama/software/repositories_manager"
-
-Yast.import "Pkg"
-
-module Agama
- module Software
- module Callbacks
- # Callbacks related to signatures handling
- class Signature < Base
- # Register the callbacks
- def setup
- Yast::Pkg.CallbackAcceptUnsignedFile(
- Yast::FunRef.new(method(:accept_unsigned_file), "boolean (string, integer)")
- )
- Yast::Pkg.CallbackImportGpgKey(
- Yast::FunRef.new(method(:import_gpg_key), "boolean (map , integer)")
- )
- Yast::Pkg.CallbackAcceptUnknownGpgKey(
- Yast::FunRef.new(
- method(:accept_unknown_gpg_key), "boolean (string, string, integer)"
- )
- )
- Yast::Pkg.CallbackAcceptVerificationFailed(
- Yast::FunRef.new(
- method(:accept_verification_failed), "boolean (string, map , integer)"
- )
- )
- end
-
- # Callback to handle unsigned files
- #
- # @param filename [String] File name
- # @param repo_id [Integer] Repository ID. It might be -1 if there is not an associated repo.
- def accept_unsigned_file(filename, repo_id)
- repo = Yast::Pkg.SourceGeneralData(repo_id)
- if repo && Agama::Software::RepositoriesManager.instance.unsigned_allowed?(repo["alias"])
- return true
- end
-
- message = if repo
- format(
- _("The file %{filename} from %{repo_url} is not digitally signed. The origin " \
- "and integrity of the file cannot be verified. Use it anyway?"),
- filename: filename, repo_url: repo["url"]
- )
- else
- format(
- _("The file %{filename} is not digitally signed. The origin " \
- "and integrity of the file cannot be verified. Use it anyway?"),
- filename: filename
- )
- end
-
- question = Agama::Question.new(
- qclass: "software.unsigned_file",
- text: message,
- options: [yes_label.to_sym, no_label.to_sym],
- default_option: no_label.to_sym,
- data: { "filename" => filename }
- )
- questions_client.ask(question) do |answer|
- answer.action == yes_label.to_sym
- end
- end
-
- # Callback to handle signature verification failures
- #
- # @param key [Hash] GPG key data (id, name, fingerprint, etc.)
- # @param repo_id [Integer] Repository ID
- def import_gpg_key(key, repo_id)
- fingerprint = key["fingerprint"].scan(/.{4}/).join(" ")
- repo = Yast::Pkg.SourceGeneralData(repo_id)
- return true if repo && repo_manager.trust_gpg?(repo["alias"], fingerprint)
-
- message = format(
- _("The key %{id} (%{name}) with fingerprint %{fingerprint} is unknown. " \
- "Do you want to trust this key?"),
- id: key["id"], name: key["name"], fingerprint: fingerprint
- )
-
- question = Agama::Question.new(
- qclass: "software.import_gpg",
- text: message,
- options: [trust_label.to_sym, skip_label.to_sym],
- default_option: skip_label.to_sym,
- data: {
- "id" => key["id"],
- "name" => key["name"],
- "fingerprint" => fingerprint
- }
- )
-
- questions_client.ask(question) do |answer|
- answer.action == trust_label.to_sym
- end
- end
-
- # Callback to handle unknown GPG keys
- #
- # @param filename [String] Name of the file.
- # @param key_id [String] Key ID.
- # @param repo_id [String] Repository ID.
- def accept_unknown_gpg_key(filename, key_id, repo_id)
- repo = Yast::Pkg.SourceGeneralData(repo_id)
- message = if repo
- format(
- _("The file %{filename} from %{repo_url} is digitally signed with " \
- "the following unknown GnuPG key: %{key_id}. Use it anyway?"),
- filename: filename, repo_url: repo["url"], key_id: key_id
- )
- else
- format(
- _("The file %{filename} is digitally signed with " \
- "the following unknown GnuPG key: %{key_id}. Use it anyway?"),
- filename: filename, key_id: key_id
- )
- end
-
- question = Agama::Question.new(
- qclass: "software.unknown_gpg",
- text: message,
- options: [yes_label.to_sym, no_label.to_sym],
- default_option: no_label.to_sym,
- data: {
- "id" => key_id,
- "filename" => filename
- }
- )
-
- questions_client.ask(question) do |answer|
- answer.action == yes_label.to_sym
- end
- end
-
- # Callback to handle file verification failures
- #
- # @param filename [String] File name
- # @param key [Hash] GPG key data (id, name, fingerprint, etc.)
- # @param repo_id [Integer] Repository ID
- def accept_verification_failed(filename, key, repo_id)
- repo = Yast::Pkg.SourceGeneralData(repo_id)
- message = if repo
- format(
- _("The file %{filename} from %{repo_url} is digitally signed with the " \
- "following GnuPG key, but the integrity check failed: %{key_id} (%{key_name}). " \
- "Use it anyway?"),
- filename: filename, repo_url: repo["url"], key_id: key["id"], key_name: key["name"]
- )
- else
- format(
- _("The file %{filename} is digitally signed with the " \
- "following GnuPG key, but the integrity check failed: %{key_id} (%{key_name}). " \
- "Use it anyway?"),
- filename: filename, key_id: key["id"], key_name: key["name"]
- )
- end
-
- question = Agama::Question.new(
- qclass: "software.unsigned_file",
- text: message,
- options: [yes_label.to_sym, no_label.to_sym],
- default_option: no_label.to_sym,
- data: { "filename" => filename }
- )
- questions_client.ask(question) do |answer|
- answer.action == yes_label.to_sym
- end
- end
-
- private
-
- # label for the "trust" action
- def trust_label
- # TRANSLATORS: button label, trust the GPG key or the signature
- _("Trust")
- end
-
- def repo_manager
- Agama::Software::RepositoriesManager.instance
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb
deleted file mode 100644
index f75903dfe6..0000000000
--- a/service/lib/agama/software/manager.rb
+++ /dev/null
@@ -1,916 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2021-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "fileutils"
-require "json"
-require "shellwords"
-require "yast"
-require "packager/cfa/zypp_conf"
-require "cfa/augeas_parser"
-require "y2packager/product"
-require "y2packager/resolvable"
-require "agama/config"
-require "agama/helpers"
-require "agama/issue"
-require "agama/registration"
-require "agama/software/callbacks"
-require "agama/software/product"
-require "agama/software/product_builder"
-require "agama/software/proposal"
-require "agama/software/repositories_manager"
-require "agama/with_locale"
-require "agama/with_progress_manager"
-require "agama/with_issues"
-
-Yast.import "Installation"
-Yast.import "Language"
-Yast.import "Package"
-Yast.import "Packages"
-Yast.import "PackageCallbacks"
-Yast.import "Pkg"
-
-module Agama
- module Software
- class ServiceError < StandardError; end
-
- # This class is responsible for software handling.
- #
- # FIXME: This class has too many responsibilities:
- # * Address the software service workflow (probe, propose, install).
- # * Manages repositories, packages, patterns, services.
- # * Manages product selection.
- # * Manages software and product related issues.
- #
- # It should be splitted in separate and smaller classes.
- class Manager # rubocop:disable Metrics/ClassLength
- include Helpers
- include WithLocale
- include WithIssues
- include WithProgressManager
- include Yast::I18n
-
- GPG_KEYS_GLOB = "/usr/lib/rpm/gnupg/keys/gpg-*"
- private_constant :GPG_KEYS_GLOB
-
- # location of the custom DUD package repository,
- # see the /usr/lib/dracut/modules.d/99agama-dud/agama-dud-apply.sh script
- DUD_REPOSITORY_DIR = "/var/lib/agama/dud/repo"
- private_constant :DUD_REPOSITORY_DIR
-
- # name for the custom DUD package repository, use some special name to
- # minimize possible conflicts with user defined repositories, ugly name
- # does not matter, it is deleted in the end anyway
- DUD_REPOSITORY_NAME = "AgamaDriverUpdate"
- private_constant :DUD_REPOSITORY_NAME
-
- # use a higher priority for the custom DUD package repository,
- # the default priority is 99, the lower number the higher priority!
- # the linuxrc default is 50, let's use the same value here as well
- DUD_REPOSITORY_PRIORITY = 50
- private_constant :DUD_REPOSITORY_PRIORITY
-
- # Selected product.
- #
- # @return [Agama::Product, nil]
- attr_reader :product
-
- DEFAULT_LANGUAGES = ["en_US"].freeze
- private_constant :DEFAULT_LANGUAGES
-
- PROPOSAL_ID = "agama-user-software-selection"
- private_constant :PROPOSAL_ID
-
- # create the libzypp lock and the zypp caches in a special directory to
- # not be affected by the Live system package management
- TARGET_DIR = "/run/agama/zypp"
- private_constant :TARGET_DIR
-
- attr_accessor :languages
-
- # Available products for installation.
- #
- # @return [Array]
- attr_reader :products
-
- # @return [Agama::RepositoriesManager]
- attr_reader :repositories
-
- # @param config [Agama::Config]
- # @param logger [Logger]
- def initialize(config, logger)
- textdomain "agama"
-
- @config = config
- @logger = logger
- @languages = DEFAULT_LANGUAGES
- @products = build_products
- @product = find_initial_product
- @repositories = RepositoriesManager.instance
- # patterns selected by user
- @user_patterns = []
- @selected_patterns_change_callbacks = []
- on_progress_change { logger.info(progress.to_s) }
- Yast::PackageCallbacks.InitPackageCallbacks(logger)
- initialize_target
- end
-
- def self.dud_repository_url
- "dir:#{DUD_REPOSITORY_DIR}"
- end
-
- # Selects a product with the given id.
- #
- # @raise {ArgumentError} If id is unknown.
- #
- # @param id [String]
- # @return [Boolean] true on success.
- def select_product(id)
- return false if id == product&.id
-
- new_product = @products.find { |p| p.id == id }
-
- raise ArgumentError unless new_product
-
- proposal.set_resolvables(
- PROPOSAL_ID, :pattern, new_product.preselected_patterns
- )
- update_repositories(new_product)
-
- @product = new_product
-
- update_issues
- true
- end
-
- # select additional products to install
- # @param addon_products [Array] list of product names
- def addon_products(addon_products)
- # The PackagesProposal module can handle only packages and patterns,
- # so products need to be handled differently.
- proposal.addon_products = addon_products
- end
-
- def probe
- # Should an error be raised?
- return unless product
-
- logger.info "Probing software"
-
- common_steps = [
- _("Refreshing repositories metadata"),
- _("Calculating the software proposal")
- ]
- if repositories.empty?
- start_progress_with_descriptions(
- _("Initializing sources"), *common_steps
- )
- progress.step { add_base_repos }
- else
- start_progress_with_descriptions(*common_steps)
- end
-
- progress.step { repositories.load }
- progress.step { propose }
-
- update_issues
- end
-
- def initialize_target
- # create the zypp lock also in the target directory
- ENV["ZYPP_LOCKFILE_ROOT"] = TARGET_DIR
- # cleanup the previous content (after service restart or crash)
- FileUtils.rm_rf(TARGET_DIR)
- FileUtils.mkdir_p(TARGET_DIR)
- Yast::Pkg.TargetInitialize(TARGET_DIR)
- import_gpg_keys
- end
-
- # Updates the software proposal
- def propose
- # Should an error be raised?
- return unless product
-
- proposal.base_product = product.name
- proposal.languages = languages
- select_resolvables
- result = proposal.calculate
- update_issues
- logger.info "Proposal result: #{result.inspect}"
- selected_patterns_changed
- result
- end
-
- # Installs the packages to the target system
- def install
- # move the target from the Live ISO to the installed system (/mnt)
- Yast::Pkg.TargetFinish
- Yast::Pkg.TargetInitialize(Yast::Installation.destdir)
- Yast::Pkg.TargetLoad
-
- steps = proposal.packages_count
- start_progress_with_size(steps)
- Callbacks::Progress.setup(steps, progress, logger)
-
- # TODO: error handling
- commit_result = Yast::Pkg.Commit({})
-
- if commit_result.nil? || commit_result.empty?
- logger.error("Commit failed")
- raise Yast::Pkg.LastError
- end
-
- logger.info "Commit result #{commit_result}"
- rescue Agama::NotFinishedProgress => e
- logger.error "There is an unfinished progress: #{e.inspect}"
- finish_progress
- end
-
- # Writes the repositories information to the installed system
- def finish
- # disable local repositories (DVD, USB flash...)
- disable_local_repos
- remove_dud_repo
- Yast::Pkg.SourceSaveAll
- Yast::Pkg.TargetFinish
- # copy the libzypp caches to the target
- if Agama::Software::Repository.all.empty?
- logger.info("No repository defined, not copying the libzypp caches")
- else
- copy_zypp_to_target
- end
- registration.finish
- modify_zypp_conf
- end
-
- # Determine whether the given tag is provided by the selected packages
- #
- # @param tag [String] Tag to search for (package names, requires/provides, or file
- # names)
- # @return [Boolean] true if it is provided; false otherwise
- def provision_selected?(tag)
- Yast::Pkg.IsSelected(tag) || Yast::Pkg.IsProvided(tag)
- end
-
- # Enlist available patterns
- #
- # @param filtered [Boolean] If list of patterns should be filtered.
- # Filtering criteria can change.
- # @return [Array]
- # rubocop:disable Metrics/CyclomaticComplexity
- # rubocop:disable Metrics/PerceivedComplexity
- def patterns(filtered)
- # huge speed up, preload the used attributes to avoid querying libzypp again,
- # see "ListPatterns" method in service/lib/agama/dbus/software/manager.rb
- preload = [:category, :description, :icon, :summary, :order, :source, :user_visible]
- patterns = Y2Packager::Resolvable.find({ kind: :pattern }, preload)
- patterns = patterns.select(&:user_visible) if filtered
-
- # only display the configured patterns from the base product, from addons display everything
- if product.user_patterns && filtered
- base_repos = base_repositories
-
- user_patterns_names = (product.user_patterns || []).map(&:name)
- patterns.select! do |p|
- # the pattern is not from a base repository or is included in the display list
- !base_repos.include?(p.source) || user_patterns_names.include?(p.name)
- end
- end
-
- patterns
- end
- # rubocop:enable Metrics/CyclomaticComplexity
- # rubocop:enable Metrics/PerceivedComplexity
-
- def add_pattern(id)
- return false unless pattern_exist?(id)
-
- res = Yast::Pkg.ResolvableInstall(id, :pattern)
- logger.info "Adding pattern #{res.inspect}"
- Yast::PackagesProposal.AddResolvables(PROPOSAL_ID, :pattern, [id])
- proposal.solve_dependencies
- selected_patterns_changed
-
- true
- end
-
- def remove_pattern(id)
- return false unless pattern_exist?(id)
-
- res = Yast::Pkg.ResolvableNeutral(id, :pattern, force = false)
- logger.info "Removing pattern #{res.inspect}"
- Yast::PackagesProposal.RemoveResolvables(PROPOSAL_ID, :pattern, [id])
- proposal.solve_dependencies
- selected_patterns_changed
-
- true
- end
-
- def assign_patterns(add, remove)
- wrong_patterns = [add, remove].flatten.reject { |p| pattern_exist?(p) }
- return wrong_patterns unless wrong_patterns.empty?
-
- user_patterns = Yast::PackagesProposal.GetResolvables(PROPOSAL_ID, :pattern)
- user_patterns.each { |p| Yast::Pkg.ResolvableNeutral(p, :pattern, force = false) }
- logger.info "Adding patterns: #{add.inspect}, removing patterns: #{remove.inspect}"
-
- Yast::PackagesProposal.SetResolvables(PROPOSAL_ID, :pattern, add)
- add.each do |id|
- res = Yast::Pkg.ResolvableInstall(id, :pattern)
- logger.info "Adding pattern #{id}: #{res.inspect}"
- end
-
- remove.each do |id|
- res = Yast::Pkg.ResolvableNeutral(id, :pattern, force = false)
- logger.info "Removing pattern #{id}: #{res.inspect}"
- Yast::PackagesProposal.RemoveResolvables(PROPOSAL_ID, :pattern, [id])
- end
-
- proposal.solve_dependencies
-
- selected_patterns_changed
-
- []
- end
-
- # @return [Array,Array] returns pair of arrays where the first one
- # is user selected pattern ids and in other is auto selected ones
- def selected_patterns
- user_patterns = Yast::PackagesProposal.GetResolvables(PROPOSAL_ID, :pattern)
-
- patterns = Y2Packager::Resolvable.find(kind: :pattern, status: :selected)
- patterns.map!(&:name)
- logger.info "Currently selected patterns: #{patterns.inspect}"
-
- patterns.partition { |p| user_patterns.include?(p) }
- end
-
- def update_selected_patterns
- user_patterns = Yast::PackagesProposal.GetResolvables(PROPOSAL_ID, :pattern)
- patterns = Y2Packager::Resolvable.find(kind: :pattern, status: :selected).map!(&:name)
-
- unselect_patterns = user_patterns - patterns
- unselect_patterns.each do |id|
- logger.info "Unselecting pattern #{id}"
- Yast::PackagesProposal.RemoveResolvables(PROPOSAL_ID, :pattern, [id])
- end
-
- selected_patterns_changed if !unselect_patterns.empty?
- end
-
- def on_selected_patterns_change(&block)
- @selected_patterns_change_callbacks << block
- end
-
- # Determines whether a package is installed in the target system.
- #
- # @param name [String] Package name
- # @return [Boolean] true if it is installed; false otherwise
- def package_installed?(name)
- on_target { Yast::Package.Installed(name, target: :system) }
- end
-
- # Determines whether a package is available.
- #
- # @param name [String] Package name
- # @return [Boolean]
- def package_available?(name)
- # Beware: apart from true and false, Available can return nil if things go wrong.
- on_local { !!Yast::Package.Available(name) }
- end
-
- # Counts how much disk space installation will use.
- # @return [String]
- # @note Reimplementation of Yast::Package.CountSizeToBeInstalled
- # @todo move to Software::Proposal
- def used_disk_space
- return "" unless proposal.valid?
-
- # FormatSizeWithPrecision(bytes, precision, omit_zeroes)
- Yast::String.FormatSizeWithPrecision(proposal.packages_size, 1, true)
- end
-
- def registration
- @registration ||= Registration.new(self, logger)
- end
-
- # 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)
- # save repositories before refreshing added services (otherwise
- # pkg-bindings will treat them as removed by the service refresh and
- # unload them)
- if !Yast::Pkg.SourceSaveAll
- # error message
- @logger.error("Saving repository configuration failed.")
- end
-
- @logger.info "Adding service #{service.name.inspect} (#{service.url})"
- if !Yast::Pkg.ServiceAdd(service.name, service.url.to_s)
- raise ServiceError, format(_("Adding service '%s' failed."), service.name)
- end
-
- if !Yast::Pkg.ServiceSet(service.name, "autorefresh" => true)
- # error message
- raise ServiceError, format(_("Updating service '%s' failed."), service.name)
- end
-
- # refresh works only for saved services
- if !Yast::Pkg.ServiceSave(service.name)
- # error message
- raise ServiceError, format(_("Saving service '%s' failed."), service.name)
- end
-
- # Force refreshing due timing issues (bnc#967828)
- if !Yast::Pkg.ServiceForceRefresh(service.name)
- # error message
- raise ServiceError, format(_("Refreshing service '%s' failed."), service.name)
- end
- ensure
- Yast::Pkg.SourceSaveAll
- end
- # rubocop:enable Metrics/AbcSize
-
- def remove_service(service)
- if Yast::Pkg.ServiceDelete(service.name) && !Yast::Pkg.SourceSaveAll
- raise ServiceError, format(_("Removing service '%s' failed."), service_name)
- end
-
- true
- end
-
- # Issues associated to the product.
- #
- # These issues are not considered as software issues, see {#update_issues}.
- #
- # @return [Array]
- def product_issues
- issues = []
- issues << missing_product_issue unless product
- issues << missing_registration_issue if missing_registration?
- issues
- end
-
- # Change the locale and activate new locale in the libzypp backend
- #
- # @param locale [String] the new locale
- def locale=(locale)
- change_process_locale(locale)
- language, = locale.split(".")
-
- # set the locale in the Language module, when changing the repository
- # (product) it calls Pkg.SetTextLocale(Language.language) internally
- Yast::Language.Set(language)
-
- # set libzypp locale (for communication only, Pkg.SetPackageLocale
- # call can be used for *installing* the language packages)
- Yast::Pkg.SetTextLocale(language)
-
- # refresh all enabled repositories to download the missing translation files
- Yast::Pkg.SourceGetCurrent(true).each do |src|
- Yast::Pkg.SourceForceRefreshNow(src)
- end
-
- # remember the currently selected packages and patterns by YaST
- # (ignore the automatic selections done by the solver)
- #
- # NOTE: we will need to handle also the tabooed and soft-locked objects
- # when we allow to set them via UI or CLI
- selected = Y2Packager::Resolvable.find(status: :selected, transact_by: :appl_high)
-
- # save and reload all repositories to activate the new translations
- Yast::Pkg.SourceSaveAll
- Yast::Pkg.SourceFinishAll
- Yast::Pkg.SourceRestore
- Yast::Pkg.SourceLoad
-
- # restore back the selected objects
- selected.each { |s| Yast::Pkg.ResolvableInstall(s.name, s.kind) }
- end
-
- def proposal
- @proposal ||= Proposal.new.tap do |proposal|
- proposal.on_issues_change { update_issues }
- end
- end
-
- private
-
- # @return [Agama::Config]
- attr_reader :config
-
- # @return [Logger]
- attr_reader :logger
-
- # Generates a list of products according to the information of the config file.
- #
- # @return [Array]
- def build_products
- ProductBuilder.new(config).build
- end
-
- # Determines the initially selected product.
- #
- # A product is automatically selected if it is the only product
- # and it does not require acccepting a license.
- def find_initial_product
- product = @products.first
- return product if @products.size == 1 && product.license.to_s.empty?
-
- nil
- end
-
- def import_gpg_keys
- gpg_keys = Dir.glob(GPG_KEYS_GLOB).map(&:to_s)
- logger.info "Importing GPG keys: #{gpg_keys}"
- gpg_keys.each do |path|
- Yast::Pkg.ImportGPGKey(path, true)
- end
- end
-
- def add_base_repos
- add_dud_repo
- return if add_repos_by_label
- return if add_repos_by_dir
-
- # local repositories not found, use the online repositories
- product.repositories.each { |url| repositories.add(url) }
- end
-
- def add_repos_by_dir
- # path to the installation repository present on the Live medium (only on the Full medium)
- dir_path = "/run/initramfs/live/install"
- return false unless File.exist?(dir_path)
-
- logger.info "/install found on Live medium"
- url = full_repo_url(dir_path, "/install")
- return false unless url
-
- logger.info "Using Full media installation repository #{url}"
- # disable autorefresh, the packages on DVD cannot be updated, for USB flash disks it can be
- # manually enabled in the installed system if needed (updating the packages need some user
- # interaction anyway)
- repositories.add(url, repo_alias: product.name, name: product.display_name,
- autorefresh: false)
-
- true
- end
-
- def add_repos_by_label
- # NOTE: support multiple labels/installation media?
- label = product.labels.first
-
- if label
- logger.info "Installation repository label: #{label.inspect}"
- # we cannot use the simple /dev/disk/by-label/* device file as there
- # might be multiple devices with the same label
- device = installation_device(label)
- if device
- logger.info "Installation device: #{device}"
- repositories.add("hd:/?device=" + device)
- return true
- end
- end
-
- false
- end
-
- # add a custom repository provided by DUD
- def add_dud_repo
- return unless File.directory?(DUD_REPOSITORY_DIR) && !Dir.empty?(DUD_REPOSITORY_DIR)
-
- logger.info "Adding DUD repository at #{DUD_REPOSITORY_DIR}"
- # if there is no repository metadata present in the dir:/ repository then libzypp
- # automatically uses the "plaindir" repository type
- repositories.add(self.class.dud_repository_url, repo_alias: DUD_REPOSITORY_NAME,
- name: DUD_REPOSITORY_NAME, priority: DUD_REPOSITORY_PRIORITY)
- end
-
- # find all devices with the required disk label
- # @return [Array] returns list of devices, e.g. `["/dev/sr1"]`,
- # returns empty list if there is no device with the required label
- def disks_with_label(label)
- data = list_disks
- disks = data.fetch("blockdevices", []).map do |device|
- device["kname"] if device["label"] == label
- end
- disks.compact!
- logger.info "Disks with the installation label: #{disks.inspect}"
- disks
- end
-
- # get list of disks, returns parsed data from the `lsblk` call
- # @return [Hash] parsed data
- def list_disks
- # we need only the kernel device name and the label
- output = `lsblk --paths --json --output kname,label`
- JSON.parse(output)
- rescue StandardError => e
- logger.error "ERROR: Cannot read disk devices: #{e}"
- {}
- end
-
- # find the installation device with the required label
- # @return [String,nil] Device name (`/dev/sr1`) or `nil` if not found
- def installation_device(label)
- disks = disks_with_label(label)
-
- # multiple installation media?
- if disks.size > 1
- # prefer optical media (/dev/srX) to disk so the disk can be used as
- # the installation target
- optical = disks.find { |d| d.match(/\A\/dev\/sr[0-9]+\z/) }
- optical || disks.first
- else
- # none or just one disk
- disks.first
- end
- end
-
- # build URL for the Full installation repository
- # @param path [String] Local path where the Full repository is mounted
- # @param url_path [String] Path part of the resulting URL
- # @return [String,nil] URL or `nil` if the Full repository device was not found
- def full_repo_url(path, url_path)
- # find the device which is mounted at the repository location
- live_device = `findmnt -n -o SOURCE --target #{Shellwords.escape(path)}`.chomp
- logger.info "Installation device: #{live_device}"
- return nil unless live_device
-
- # distinguish between DVD and hard disks/USB flash
- if live_device.match(/\A\/dev\/sr[0-9]+\z/)
- "dvd:#{url_path}?devices=#{live_device}"
- else
- # try using a more stable by-id device name, important esp. for USB flash
- by_id_devices = `find -L /dev/disk/by-id -samefile #{Shellwords.escape(live_device)}`
- .chomp
- # if there are more names just use the first one
- by_id_device = by_id_devices.split("\n").first
- device = (by_id_device && !by_id_device.empty?) ? by_id_device : live_device
- "hd:#{url_path}?device=#{device}"
- end
- end
-
- # Adds resolvables for selected product
- def select_resolvables
- proposal.set_resolvables("agama", :pattern, product.mandatory_patterns)
- proposal.set_resolvables("agama", :pattern, product.optional_patterns, optional: true)
- proposal.set_resolvables("agama", :package, product.mandatory_packages)
- proposal.set_resolvables("agama", :package, product.optional_packages, optional: true)
- end
-
- def selected_patterns_changed
- @selected_patterns_change_callbacks.each(&:call)
- end
-
- # Updates the list of software issues.
- def update_issues
- self.issues = current_issues
- end
-
- # List of current software issues.
- #
- # @return [Array]
- def current_issues
- return [] unless product
-
- issues = repos_issues
-
- # If none of the repositories could be probed, then do not report missing patterns and/or
- # packages. Those issues does not make any sense if there are no repositories to install
- # from.
- issues += proposal.issues if repositories.enabled.any?
- issues
- end
-
- # Issues related to the software proposal.
- #
- # Repositories that could not be probed are reported as errors.
- #
- # @return [Array]
- def repos_issues
- repositories.disabled.map do |repo|
- Issue.new(_("Could not read repository \"%s\"") % repo.name,
- source: Issue::Source::SYSTEM,
- severity: Issue::Severity::ERROR)
- end
- end
-
- # Issue when a product is missing
- #
- # @return [Agama::Issue]
- def missing_product_issue
- Issue.new(_("Product not selected yet"),
- source: Issue::Source::CONFIG,
- severity: Issue::Severity::ERROR)
- end
-
- # Issue when a product requires registration but it is not registered yet.
- #
- # @return [Agama::Issue]
- def missing_registration_issue
- Issue.new(_("Product must be registered"),
- kind: :missing_registration,
- source: Issue::Source::SYSTEM,
- severity: Issue::Severity::ERROR)
- end
-
- # Whether the registration is missing.
- #
- # @return [Boolean]
- def missing_registration?
- return false unless product
-
- product.registration && missing_base_product?
- end
-
- # Whether the base product is missing
- #
- # @return [Boolean]
- def missing_base_product?
- products = Y2Packager::Resolvable.find(kind: :product, name: product.name)
- products.empty?
- end
-
- def pattern_exist?(pattern_name)
- !Y2Packager::Resolvable.find(kind: :pattern, name: pattern_name).empty?
- end
-
- # this reimplements the Pkg.SourceCacheCopyTo call which works correctly
- # only from the inst-sys (it copies the data from "/" where is actually
- # the Live system package manager)
- # @see https://github.com/yast/yast-pkg-bindings/blob/3d314480b70070299f90da4c6e87a5574e9c890c/src/Source_Installation.cc#L213-L267
- def copy_zypp_to_target
- # copy the zypp "raw" cache
- cache = File.join(TARGET_DIR, "/var/cache/zypp/raw")
- if Dir.exist?(cache)
- target_cache = File.join(Yast::Installation.destdir, "/var/cache/zypp")
- FileUtils.mkdir_p(target_cache)
- FileUtils.cp_r(cache, target_cache)
- end
-
- # copy the "solv" cache but skip the "@System" directory because it
- # contains empty installed packages (there were no installed packages
- # before moving the target to "/mnt")
- solv_cache = File.join(TARGET_DIR, "/var/cache/zypp/solv")
- target_solv = File.join(Yast::Installation.destdir, "/var/cache/zypp/solv")
- solvs = Dir.entries(solv_cache) - [".", "..", "@System"]
- solvs.each do |s|
- FileUtils.cp_r(File.join(solv_cache, s), target_solv)
- end
-
- # copy the zypp credentials if present
- credentials = File.join(TARGET_DIR, "/etc/zypp/credentials.d")
- if Dir.exist?(credentials)
- target_credentials = File.join(Yast::Installation.destdir, "/etc/zypp")
- FileUtils.mkdir_p(target_credentials)
- FileUtils.cp_r(credentials, target_credentials)
- end
-
- # copy the global credentials if present
- glob_credentials = File.join(TARGET_DIR, "/etc/zypp/credentials.cat")
- return unless File.exist?(glob_credentials)
-
- target_dir = File.join(Yast::Installation.destdir, "/etc/zypp")
- FileUtils.mkdir_p(target_dir)
- FileUtils.copy(glob_credentials, target_dir)
- end
-
- # private class to ensure that cfa reads installed system
- # YaST target file does not work reliably as Agama does not have
- # always switched SCR
- class TargetFile
- # Reads file content with respect of changed root in installation.
- def self.read(path)
- ::File.read(final_path(path))
- end
-
- # Writes file content with respect of changed root in installation.
- def self.write(path, content)
- ::File.write(final_path(path), content)
- end
-
- def self.final_path(path)
- ::File.join(Yast::Installation.destdir, path)
- end
- private_class_method :final_path
- end
-
- def modify_zypp_conf
- # use defaults unless user explicitelly sets flag
- return if proposal.only_required.nil?
-
- # minimal system does not need to have libzypp, so in this case do not
- # modify zypp.conf
- if !File.exist?(File.join(Yast::Installation.destdir, "/etc/zypp/zypp.conf"))
- logger.info "Target system does not have zypp.conf so skipping modification of it"
- return
- end
-
- zypp_conf = Yast::Packager::CFA::ZyppConf.new(file_handler: TargetFile)
- zypp_conf.load
- tree = zypp_conf.generic_get("main")
- if !tree
- tree = ::CFA::AugeasTree.new
- zypp_conf.generic_get("main", tree)
- end
- zypp_conf.generic_set("solver.onlyRequires", (!!proposal.only_required).to_s, tree)
- zypp_conf.save
- end
-
- # Is any local repository (CD/DVD, disk) currently used?
- # @return [Boolean] true if any local repository is used
- def local_repo?
- Agama::Software::Repository.all.any?(&:local?)
- end
-
- # update the zypp repositories for the new product, either delete them
- # or keep them untouched
- # @param new_product [Agama::Software::Product] the new selected product
- def update_repositories(new_product)
- # reuse the repositories when they are the same as for the previously
- # selected product and no local repository is currently used
- # (local repositories are usually product specific)
- # TODO: what about registered products?
- # TODO: allow a partial match? i.e. keep the same repositories, delete
- # additional repositories and add missing ones
- if product&.repositories&.sort == new_product.repositories.sort && !local_repo?
- # the same repositories, we just needed to reset the package selection
- Yast::Pkg.PkgReset()
- else
- # delete all, the #probe call will add the new repos
- repositories.delete_all
- # deleting happens only in memory, to really delete the caches we need
- # to write the repository setup to the disk
- Yast::Pkg.SourceSaveAll
- end
- end
-
- # disable all local repositories, remove device name from the DVD Full repository
- def disable_local_repos
- local_repos = Agama::Software::Repository.all.select(&:local?)
- local_repos.each(&:disable!)
-
- # remove the installation device from the URL, libzypp will probe all present devices,
- # this allows inserting the DVD medium into a different drive later
- full_dvd_repo = local_repos.find { |r| r.url.to_s.start_with?("dvd:/install?devices=") }
- return unless full_dvd_repo
-
- new_url = "dvd:/install"
- logger.info "Changing repository URL from #{full_dvd_repo.url} to #{new_url}"
- full_dvd_repo.url = new_url
- end
-
- def remove_dud_repo
- dud_repo = Agama::Software::Repository.all.find { |r| r.name == DUD_REPOSITORY_NAME }
- return unless dud_repo
-
- logger.info "Removing the temporary DUD repository"
- dud_repo.delete!
- end
-
- # Return all enabled repositories belonging to the base product.
- #
- # @return [Array] List of repository IDs, returns empty list if
- # no repository is defined yet
- def base_repositories
- # process only the enabled repositories
- only_enabled_repos = true
- # the base product repo is the first added repository (the lowest number)
- base_src_id = Yast::Pkg.SourceGetCurrent(only_enabled_repos).min
- # a repository might not be defined yet
- return [] unless base_src_id
-
- # if the base repository comes from a service consider all repositories from that service
- # (SCC uses Pool + Updates, use both of them just in case a pattern is updated)
- service = Yast::Pkg.SourceGeneralData(base_src_id)["service"]
-
- if service.empty?
- [base_src_id]
- else
- logger.info "The base product is from a service"
- Yast::Pkg.SourceGetCurrent(only_enabled_repos).select do |r|
- Yast::Pkg.SourceGeneralData(r)["service"] == service
- end
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/software/product.rb b/service/lib/agama/software/product.rb
deleted file mode 100644
index d2402d6815..0000000000
--- a/service/lib/agama/software/product.rb
+++ /dev/null
@@ -1,178 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/registration"
-
-module Agama
- module Software
- # Represents a user selectable product.
- UserPattern = Struct.new(:name, :selected)
-
- # Represents a product that Agama can install.
- class Product
- # Product id.
- #
- # @return [String]
- attr_reader :id
-
- # Name of the product to be display.
- #
- # @return [String, nil]
- attr_accessor :display_name
-
- # Description of the product.
- #
- # @return [String, nil]
- attr_accessor :description
-
- # Internal name of the product. This is relevant for registering the product.
- #
- # @return [String, nil]
- attr_accessor :name
-
- # Version of the product. This is relevant for registering the product.
- #
- # @return [String, nil] E.g., "1.0".
- attr_accessor :version
-
- # Product icon. Please use specify filename with svg suffix and ensure referenced
- # file exists inside agama/web/src/assets/product.
- # `default.svg` will be used unless specified otherwise.
- #
- # @return [String] E.g. "leap.svg"
- attr_accessor :icon
-
- # List of repositories.
- #
- # @return [Array] Empty if the product requires registration.
- attr_accessor :repositories
-
- # List of disk labels used for installation repository.
- #
- # @return [Array] Empty if the product does not support offline installation.
- attr_accessor :labels
-
- # Mandatory packages.
- #
- # @return [Array]
- attr_accessor :mandatory_packages
-
- # Optional packages.
- #
- # @return [Array]
- attr_accessor :optional_packages
-
- # Mandatory patterns.
- #
- # @return [Array]
- attr_accessor :mandatory_patterns
-
- # Optional patterns.
- #
- # These patterns are always installed if they are available.
- #
- # @return [Array]
- attr_accessor :optional_patterns
-
- # Optional user selectable patterns
- #
- # @return [Array, nil]
- attr_accessor :user_patterns
-
- # Whether the registration is enabled for the product.
- #
- # @return [boolean]
- attr_accessor :registration
-
- # Product translations.
- #
- # @example
- # product.translations #=>
- # {
- # "description" => {
- # "cs" => "Czech translation",
- # "es" => "Spanish translation"
- # }
- #
- # @return [Hash>]
- attr_accessor :translations
-
- # License ID
- attr_accessor :license
-
- # @param id [string] Product id.
- def initialize(id)
- @id = id
- @icon = "default.svg"
- @repositories = []
- @labels = []
- @mandatory_packages = []
- @optional_packages = []
- @mandatory_patterns = []
- @optional_patterns = []
- # nil = display all visible patterns, [] = display no patterns
- @user_patterns = nil
- @registration = false
- @license = nil
- @translations = {}
- end
-
- # Localized product description.
- #
- # If there is no translation for the current language, then the untranslated description is
- # used.
- #
- # @return [String, nil]
- def localized_description
- translations = self.translations["description"]
- lang = ENV["LANG"]
-
- # No translations or language not set, return untranslated value.
- return description unless translations && lang
-
- # Remove the character encoding if present.
- lang = lang.split(".").first
- # Full matching (language + country)
- return translations[lang] if translations[lang]
-
- # Remove the country part.
- lang = lang.split("_").first
- # Partial match (just the language).
- return translations[lang] if translations[lang]
-
- # Fallback to original untranslated description.
- description
- end
-
- # Preselected patterns.
- #
- # These patterns are pre-selected if they are available, but
- # the user can unselect them.
- #
- # @return [Array]
- def preselected_patterns
- return [] if user_patterns.nil?
-
- user_patterns.filter_map { |p| p.name if p.selected }
- end
- end
- end
-end
diff --git a/service/lib/agama/software/product_builder.rb b/service/lib/agama/software/product_builder.rb
deleted file mode 100644
index 687df9056a..0000000000
--- a/service/lib/agama/software/product_builder.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/cmdline_args"
-require "agama/software/product"
-require "logger"
-
-module Agama
- module Software
- # Builds products from the information of a config file.
- class ProductBuilder
- # @param config [Agama::Config]
- def initialize(config, logger: Logger.new($stdout))
- @config = config
- @logger = logger
- end
-
- # Builds the products.
- #
- # @return [Array]
- def build
- cmdline_args = CmdlineArgs.read_from("/run/agama/cmdline.d/agama.conf")
- @logger.info cmdline_args
- config.products.map do |id, attrs|
- data = product_data_from_config(id)
- create_product(id, data, attrs, cmdline_args)
- end
- end
-
- private
-
- # @return [Agama::Config]
- attr_reader :config
-
- # @return [Agama::Software::Product]
- def create_product(id, data, attrs, cmdline_args)
- product = initialize_product(id, data, attrs)
- set_repositories(product, data, cmdline_args)
- set_software(product, data)
- set_translations(product, attrs)
- product
- end
-
- def initialize_product(id, data, attrs)
- Agama::Software::Product.new(id).tap do |product|
- product.display_name = attrs["name"]
- product.description = attrs["description"]
- product.name = data[:name]
- product.version = data[:version]
- product.icon = attrs["icon"] if attrs["icon"]
- product.registration = !!attrs["registration"]
- product.license = attrs["license"] if attrs["license"]
- product.version = attrs["version"] if attrs["version"]
- end
- end
-
- def set_repositories(product, data, cmdline_args)
- install_url = cmdline_args.data["install_url"]
- if install_url
- @logger.info "agama.install_url is set to #{install_url}"
- product.repositories = install_url.split(",")
- else
- product.repositories = data[:repositories]
- end
- end
-
- def set_software(product, data)
- product.labels = data[:labels]
- product.mandatory_packages = data[:mandatory_packages]
- product.optional_packages = data[:optional_packages]
- product.mandatory_patterns = data[:mandatory_patterns]
- product.optional_patterns = data[:optional_patterns]
- product.user_patterns = build_user_patterns(data[:user_patterns])
- end
-
- def set_translations(product, attrs)
- product.translations = attrs["translations"] || {}
- end
-
- # Data from config, filtering by arch.
- #
- # @param id [String]
- # @return [Hash]
- def product_data_from_config(id)
- {
- name: config.products.dig(id, "software", "base_product"),
- icon: config.products.dig(id, "software", "icon"),
- labels: config.arch_elements_from(
- id, "software", "installation_labels", property: :label
- ),
- repositories: config.arch_elements_from(
- id, "software", "installation_repositories", property: :url
- ),
- mandatory_packages: config.arch_elements_from(
- id, "software", "mandatory_packages", property: :package
- ),
- optional_packages: config.arch_elements_from(
- id, "software", "optional_packages", property: :package
- ),
- mandatory_patterns: config.arch_elements_from(
- id, "software", "mandatory_patterns", property: :pattern
- ),
- optional_patterns: config.arch_elements_from(
- id, "software", "optional_patterns", property: :pattern
- ),
- user_patterns: config.arch_elements_from(
- id, "software", "user_patterns", property: nil, default: nil
- )
- }
- end
-
- # Build the list of user patterns.
- #
- # @param [Array, nil] user_patterns
- def build_user_patterns(user_patterns)
- return nil if user_patterns.nil?
-
- user_patterns.map do |d|
- if d.is_a?(Hash)
- UserPattern.new(d["name"], d["selected"])
- else
- UserPattern.new(d, false)
- end
- end
- end
- end
- end
-end
diff --git a/service/lib/agama/software/proposal.rb b/service/lib/agama/software/proposal.rb
deleted file mode 100644
index d63203f84f..0000000000
--- a/service/lib/agama/software/proposal.rb
+++ /dev/null
@@ -1,298 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "agama/issue"
-require "agama/with_issues"
-
-Yast.import "Stage"
-Yast.import "Installation"
-Yast.import "Pkg"
-Yast.import "PackagesProposal"
-Yast.import "Packages"
-
-module Agama
- module Software
- # Backend class to calculate the software proposal
- #
- # This class represents a software proposal. Beware that it is a wrapper around `Yast::Pkg` and
- # `Yast::PackagesProposal` and most of the state is kept in those modules. For that reason, a
- # new instance of this class might has already some implicit state (e.g., the list of
- # repositories to use, the list of packages/patterns to install, etc.).
- #
- # @todo implement a reset mechanism to clear repositories, seleced packages/patterns, etc.
- # @note you might expect that it receives a RepositoriesManager instance. However, as the state
- # is kept in the `Yast::Pkg` module, it is not needed at all.
- #
- # @example Calculate a proposal
- # proposal = Proposal.new
- # proposal.base_product = "openSUSE"
- # proposal.add_resolvables("agama", :pattern, ["enhanced_base"])
- # proposal.languages = ["en_US", "de_DE"]
- # proposal.calculate #=> true
- # proposal.issues #=> []
- class Proposal
- include WithIssues
- include Yast::I18n
-
- # @return [String,nil] Base product
- attr_accessor :base_product
-
- # @return [Array] Addon products
- attr_accessor :addon_products
-
- # @return [Array] List of languages to install
- attr_reader :languages
-
- # @return [Array>>] List of conflicts from the last solver run
- attr_reader :conflicts
-
- # @return [boolean, nil] flag to indicate that solver should add only required packages
- # and not recommended. Nil means not set
- attr_accessor :only_required
-
- # Constructor
- #
- # @param logger [Logger]
- def initialize(logger: nil)
- textdomain "agama"
-
- @logger = logger || Logger.new($stdout)
- @base_product = nil
- @addon_products = []
- @conflicts = []
- @conflicts_change_callbacks = []
- @only_required = nil
- end
-
- # Adds the given list of resolvables to the proposal
- #
- # It relies on the Yast::PackagesProposal module which keeps its own state.
- #
- # @param unique_id [String] Unique identifier for the resolvables list
- # @param type [Symbol] Resolvables type (:package or :pattern)
- # @param resolvables [Array] Resolvables to add
- # @param optional [Boolean] Whether the resolvable is optional (or mandatory)
- def set_resolvables(unique_id, type, resolvables, optional: false)
- Yast::PackagesProposal.SetResolvables(unique_id, type, resolvables, optional: optional)
- end
-
- # Calculates the proposal
- #
- # @return [Boolean]
- def calculate
- initialize_target
- @proposal = Yast::Packages.Proposal(force_reset = true, reinit = false, _simple = true)
- # select the base product after running the Packages.Proposal, the force_reset = true
- # option would reset the selection and a random product would be selected by the solver
- select_base_product
- select_addon_products
- solve_dependencies
-
- valid?
- end
-
- # Runs the solver to satisfy the dependencies.
- #
- # Issues are updated once the solver finishes.
- #
- # @return [Boolean] whether the solver ran successfully
- def solve_dependencies
- res = Yast::Pkg.PkgSolve(unused = true)
- logger.info "Solver run #{res.inspect}"
- update_issues
- update_conflicts
-
- return true if res
-
- logger.error "Solver failed: #{Yast::Pkg.LastError}"
- logger.error "Details: #{Yast::Pkg.LastErrorDetails}"
- logger.error "Solver errors: #{Yast::Pkg.PkgSolveErrors}"
- false
- end
-
- # @param [Array<(Integer, Integer)>] solutions is array of conflict id and solution id
- def solve_conflicts(solutions)
- pkg_solutions = solutions.map do |sol|
- con_id, sol_id = sol
- conflict = @conflicts[con_id] or raise "Unknown conflict id #{con_id.inspect}"
- solution = conflict["solutions"][sol_id] or raise "unknown solution id #{sol_id.inspect}"
- {
- "description" => conflict["description"],
- "details" => conflict["details"],
- "solution_description" => solution["description"],
- "solution_details" => solution["details"]
- }
- end
- logger.info "Sending solver solutions #{pkg_solutions.inspect}"
-
- Yast::Pkg.PkgSetSolveSolutions(pkg_solutions)
-
- # and rerun solver to also update conflicts
- solve_dependencies
- end
-
- def on_conflicts_change(&block)
- @conflicts_change_callbacks << block
- end
-
- # Returns the count of packages to install
- #
- # @return [Integer] count of packages to install
- def packages_count
- Yast::Pkg.PkgMediaCount.reduce(0) { |sum, res| sum + res.reduce(0, :+) }
- end
-
- # Returns the size of the packages to install
- #
- # @return [Integer] size of the installation in bytes
- def packages_size
- Yast::Pkg.PkgMediaSizes.reduce(0) do |res, media_size|
- media_size.reduce(res, :+)
- end
- end
-
- # Determines whether the proposal is valid
- #
- # @return [Boolean]
- def valid?
- !(proposal.nil? || errors?)
- end
-
- # Sets the languages to install
- #
- # @param [Array] value Languages in xx_XX format (e.g., "en_US").
- def languages=(value)
- @languages = value.map { |l| l.split(".").first }.compact
- end
-
- private
-
- # @return [Logger]
- attr_reader :logger
-
- # Proposal result
- #
- # @return [Hash, nil] nil if not calculated yet.
- attr_reader :proposal
-
- # Initializes the target, closing the previous one
- def initialize_target
- preferred, *additional = languages
- Yast::Pkg.SetPackageLocale(preferred || "")
- Yast::Pkg.SetAdditionalLocales(additional)
-
- Yast::Pkg.SetSolverFlags("ignoreAlreadyRecommended" => false,
- "onlyRequires" => !!@only_required)
- end
-
- # Selects the base product
- #
- # @see #base_product
- def select_base_product
- base_product = Y2Packager::Product.available_base_products.find do |product|
- product.name == @base_product
- end
-
- if base_product.nil?
- logger.error "Could not select the base product '#{@base_product}'"
- else
- logger.info "Selecting the base product '#{base_product.name}'"
- base_product&.select
- end
- end
-
- # select the addon products to install
- def select_addon_products
- addon_products.each { |a| Yast::Pkg.ResolvableInstall(a, :product) }
- end
-
- # Updates the issues from the attempt to create a proposal.
- #
- # It collects issues from:
- #
- # * The proposal result.
- # * The last solver execution.
- #
- # @return [Array]
- def update_issues
- msgs = []
- msgs.concat(warning_messages(proposal)) if proposal
-
- issues = msgs.map do |msg|
- Issue.new(msg,
- source: Issue::Source::CONFIG,
- severity: Issue::Severity::ERROR)
- end
-
- solver_issues = solver_messages.map do |msg|
- Issue.new(msg,
- source: Issue::Source::CONFIG,
- severity: Issue::Severity::ERROR,
- kind: :solver)
- end
-
- self.issues = issues + solver_issues
- end
-
- # Extracts the warning messages from the proposal result
- #
- # @param proposal_result [Hash] Proposal result; it might contain a "warning" key with warning
- # messages.
- def warning_messages(proposal_result)
- return [] unless proposal_result["warning_level"] == :blocker
-
- proposal_result["warning"]
- .split("
")
- .grep_v(/Please manually select .*/) # FIXME: it depends on the language
- end
-
- # Returns solver error messages from the last attempt
- #
- # @return [Array] Error messages
- def solver_messages
- solve_errors = Yast::Pkg.PkgSolveErrors
- return [] if solve_errors.zero?
-
- res = []
- res << (_("Found %s dependency issues.") % solve_errors) if solve_errors > 0
- res
- end
-
- def update_conflicts
- pkg_conflicts = Yast::Pkg.PkgSolveProblems
- @conflicts = []
- pkg_conflicts.each_with_index do |pkg_conflict, index|
- conflict = pkg_conflict
- conflict["id"] = index
- conflict["solutions"].each_with_index do |solution, index2|
- solution["id"] = index2
- end
- @conflicts << conflict
- end
-
- @conflicts_change_callbacks.each { |c| c.call(@conflicts) }
-
- @conflicts
- end
- end
- end
-end
diff --git a/service/lib/agama/software/repositories_manager.rb b/service/lib/agama/software/repositories_manager.rb
deleted file mode 100644
index 648b24dd80..0000000000
--- a/service/lib/agama/software/repositories_manager.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "agama/software/repository"
-require "singleton"
-
-module Agama
- module Software
- # Class to manage repositories
- #
- # @see Repository
- class RepositoriesManager
- include Singleton
-
- # @return [Array]
- attr_reader :repositories
-
- def reset
- @repositories = []
- @user_repositories = []
- # remember how exactly user specify repos and return it identical
- @plain_user_repositories = []
- @unsigned_repos = []
- @gpg_fingerprints = {}
- end
-
- # Adds a new repository
- #
- # @param url [String] Repository URL
- # @param name [String] Repository name, if not specified the URL is used
- # @param repo_alias [String] Repository alias, must be unique,
- # if not specified a random one is generated
- # @param autorefresh [Boolean] Whether the repository should be autorefreshed
- # @param priority [Integer] Repository priority, the lower number the higher (!)
- # priority, the default libzypp priority is 99
- def add(url, name: nil, repo_alias: "", autorefresh: true, priority: 99)
- repositories << Repository.create(name: name || url, url: url, repo_alias: repo_alias,
- autorefresh: autorefresh, priority: priority)
- end
-
- # returns user repositories as it was previously specified
- def user_repositories
- @plain_user_repositories
- end
-
- # sets and loads user repositories
- def user_repositories=(repos)
- @plain_user_repositories = repos
- clear_user_repositories
- repos.each do |repo|
- id = Yast::Pkg.RepositoryAdd(
- "name" => repo["name"],
- "base_urls" => [repo["url"].to_s],
- "alias" => repo["alias"],
- "prod_dir" => repo["product_dir"],
- "enabled" => repo["enabled"],
- "priority" => repo["priority"]
- )
- # TODO: better error reporting
- raise "failed to add repo" unless id
-
- zypp_repo = Repository.find(id)
-
- @user_repositories << zypp_repo
- repositories << zypp_repo
-
- @unsigned_repos << repo["alias"] if repo["allow_unsigned"]
- @gpg_fingerprints[repo["alias"]] = repo["gpg_fingerprints"]
- &.map { |f| f.gsub(/\s/, "") } || []
- end
-
- # load new repos
- self.load
- end
-
- def unsigned_allowed?(repo_alias)
- @unsigned_repos.include?(repo_alias)
- end
-
- def trust_gpg?(repo_alias, fingerprint)
- @gpg_fingerprints[repo_alias]&.include?(fingerprint.gsub(/\s/, ""))
- end
-
- # Determines if there are registered repositories
- #
- # @return [Boolean] true if there are not repositories; false otherwise
- def empty?
- repositories.empty?
- end
-
- # Returns the enabled repositories
- #
- # @return [Array]
- def enabled
- repositories.select(&:enabled?)
- end
-
- # Returns the disabled repositories
- #
- # @return [Array]
- def disabled
- repositories.reject(&:enabled?)
- end
-
- # Loads the repository metadata
- #
- # As a side effect, it disables those repositories that cannot be read.
- # The intent is to prevent the proposal from trying to read them
- # again.
- def load
- repositories.each do |repo|
- if repo.probe
- repo.enable!
- # In some rare scenarios although the repository probe succeeded the refresh might fail
- # with network timeout. In that case disable the repository to avoid implicitly
- # refreshing it again in the Pkg.SourceLoad call which could time out again, effectively
- # doubling the total timeout.
- repo.disable! unless repo.refresh
- else
- repo.disable!
- end
- end
- Yast::Pkg.SourceLoad
- end
-
- # Deletes all the repositories
- def delete_all
- repositories.each(&:delete!)
- repositories.clear
- end
-
- private
-
- def clear_user_repositories
- @repositories -= @user_repositories
- @user_repositories.each(&:delete!)
- @user_repositories.clear
-
- @unsigned_repos = []
- @gpg_fingerprints = {}
- end
-
- def initialize
- reset
- end
- end
- end
-end
diff --git a/service/lib/agama/software/repository.rb b/service/lib/agama/software/repository.rb
deleted file mode 100644
index 99a8d3232f..0000000000
--- a/service/lib/agama/software/repository.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "y2packager/repository"
-
-Yast.import "Pkg"
-
-module Agama
- module Software
- # This class represents a software repository
- #
- # It extends the `Y2Packager::Repository` with some methods in the context
- # of Agama.
- #
- # @see RepositoriesManager
- class Repository < Y2Packager::Repository
- # delay before retrying (in seconds)
- RETRY_DELAY = 5
- # number of automatic retries
- RETRY_COUNT = 1
-
- class << self
- # Add a repository
- # Override the Y2Packager::Repository.create which does not accept the alias option
- #
- # @param name [String] Name
- # @param repo_alias [String] Unique alias, if missing some ugly name is generated
- # @param url [URI::Generic, ZyppUrl] Repository URL
- # @param product_dir [String] Product directory
- # @param enabled [Boolean] Is the repository enabled?
- # @param autorefresh [Boolean] Is auto-refresh enabled for this repository?
- # @param priority [Integer] Repository priority, the lower number the higher (!)
- # priority, the default libzypp priority is 99
- # @return [Y2Packager::Repository,nil] New repository or nil if creation failed
- def create(name:, url:, repo_alias: "", product_dir: "", enabled: true, autorefresh: true,
- priority: 99)
-
- repo_id = Yast::Pkg.RepositoryAdd(
- "name" => name, "base_urls" => [url.to_s], "enabled" => enabled,
- "autorefresh" => autorefresh, "prod_dir" => product_dir, "alias" => repo_alias,
- "priority" => priority
- )
-
- repo_id ? find(repo_id) : nil
- end
- end
-
- # Probes a repository
- #
- # @return [Boolean] true if the repository can be read; false otherwise
- def probe
- attempt = 1
- type = nil
-
- loop do
- # on a timeout error the result is nil, retry automatically in that case,
- # note: callbacks are disabled during repo probing call
- type = Yast::Pkg.RepositoryProbe(url.to_s, product_dir)
- break if !type.nil? || attempt > RETRY_COUNT
-
- sleep(RETRY_DELAY)
- attempt += 1
- end
-
- !!type && type != "NONE"
- end
-
- def loaded?
- @loaded
- end
-
- def refresh
- attempt = 1
-
- loop do
- @loaded = !!super
- break if @loaded || attempt > RETRY_COUNT
-
- sleep(RETRY_DELAY)
- attempt += 1
- end
-
- @loaded
- end
- end
- end
-end
diff --git a/service/lib/agama/storage/finisher.rb b/service/lib/agama/storage/finisher.rb
index 31ecf18bd1..0f08409763 100644
--- a/service/lib/agama/storage/finisher.rb
+++ b/service/lib/agama/storage/finisher.rb
@@ -28,8 +28,6 @@
require "y2storage/storage_manager"
require "agama/with_progress_manager"
require "agama/helpers"
-require "agama/http"
-require "agama/network"
require "abstract_method"
require "fileutils"
@@ -46,11 +44,9 @@ class Finisher
# Constructor
# @param logger [Logger]
# @param config [Config]
- # @param security [Security]
- def initialize(logger, config, security)
+ def initialize(logger, config)
@logger = logger
@config = config
- @security = security
end
# Execute the final storage actions, reporting the progress
@@ -75,20 +71,14 @@ def run
# @return [Config]
attr_reader :config
- # @return [Security]
- attr_reader :security
-
# All possible steps, that may or not need to be executed
def possible_steps
[
- SecurityStep.new(logger, security),
CopyFilesStep.new(logger),
StorageStep.new(logger),
IscsiStep.new(logger),
BootloaderStep.new(logger),
SnapshotsStep.new(logger),
- FilesStep.new(logger),
- PostScripts.new(logger),
CopyLogsStep.new(logger),
UnmountStep.new(logger)
]
@@ -174,23 +164,6 @@ def glob_files
end
end
- # Step to write the security settings
- class SecurityStep < Step
- # Constructor
- def initialize(logger, security)
- super(logger)
- @security = security
- end
-
- def label
- _("Writing Linux Security Modules configuration")
- end
-
- def run
- @security.write
- end
- end
-
# Step to write the bootloader configuration
class BootloaderStep < Step
def label
@@ -282,64 +255,6 @@ def logs_dir
end
end
- # Executes post-installation scripts
- class PostScripts < Step
- def label
- _("Running user-defined scripts")
- end
-
- def run
- run_post_scripts
- enable_init_scripts
- end
-
- private
-
- # Run the post scripts
- def run_post_scripts
- network.link_resolv
- client = Agama::HTTP::Clients::Scripts.new(logger)
- client.run("post")
- ensure
- network.unlink_resolv
- end
-
- def network
- @network ||= Agama::Network.new(logger)
- end
-
- # Enables the agama-scripts service to run init scripts
- #
- # The package agama-scripts is only installed when needed.
- # So this method just tries to enable the service.
- def enable_init_scripts
- # systemctl will return 1 if the service does not exist.
- Yast::Execute.on_target!(
- "systemctl", "enable", "agama-scripts",
- allowed_exitstatus: [0, 1]
- )
- end
- end
-
- # Executes post-installation scripts
- class FilesStep < Step
- def label
- _("Deploying user-defined files")
- end
-
- def run
- deploy_files
- end
-
- private
-
- def deploy_files
- require "agama/http"
- client = Agama::HTTP::Clients::Files.new(logger)
- client.write
- end
- end
-
# Step to unmount the target file-systems
class UnmountStep < Step
def label
diff --git a/service/lib/agama/storage/manager.rb b/service/lib/agama/storage/manager.rb
index 92c8e427eb..92070590d9 100644
--- a/service/lib/agama/storage/manager.rb
+++ b/service/lib/agama/storage/manager.rb
@@ -21,7 +21,6 @@
require "agama/http/clients"
require "agama/issue"
-require "agama/security"
require "agama/storage/actions_generator"
require "agama/storage/bootloader"
require "agama/storage/callbacks"
@@ -38,8 +37,6 @@
require "y2storage/luks"
require "y2storage/storage_manager"
-Yast.import "PackagesProposal"
-
module Agama
module Storage
# Manager to handle storage configuration
@@ -121,12 +118,12 @@ def add_packages
return if packages.empty?
logger.info "Selecting these packages for installation: #{packages}"
- Yast::PackagesProposal.SetResolvables(PROPOSAL_ID, :package, packages)
+ http_client.set_resolvables(PROPOSAL_ID, :package, packages)
end
# Performs the final steps on the target file system(s).
def finish
- Finisher.new(logger, product_config, security).run
+ Finisher.new(logger, product_config).run
end
# Storage proposal manager
@@ -164,13 +161,6 @@ def configure_locale(locale)
update_issues
end
- # Security manager
- #
- # @return [Security]
- def security
- @security ||= Security.new(logger, product_config)
- end
-
# Issues from the system
#
# @return [Array]
@@ -237,6 +227,10 @@ def candidate_devices_issue
def questions_client
@questions_client ||= Agama::HTTP::Clients::Questions.new(logger)
end
+
+ def http_client
+ @http_client = Agama::HTTP::Clients::Main.new(::Logger.new($stdout))
+ end
end
end
end
diff --git a/service/lib/agama/users.rb b/service/lib/agama/users.rb
deleted file mode 100644
index c0a250288e..0000000000
--- a/service/lib/agama/users.rb
+++ /dev/null
@@ -1,221 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require "yast"
-require "y2users"
-require "y2users/linux" # FIXME: linux is not in y2users file
-require "yast2/execute"
-require "y2firewall/firewalld"
-require "agama/helpers"
-require "agama/issue"
-require "agama/with_issues"
-
-Yast.import "Service"
-
-module Agama
- # Backend class using YaST code.
- #
- # {Agama::DBus::Users} wraps it with a D-Bus interface and
- class Users
- include Helpers
- include WithIssues
- include Yast::I18n
-
- def initialize(logger)
- textdomain "agama"
- @logger = logger
- update_issues
- end
-
- def root_ssh_key=(value)
- root_user.authorized_keys = [value] # just one supported for now
- update_issues
- end
-
- def assign_root_password(value, hashed)
- pwd = if hashed
- Y2Users::Password.create_encrypted(value)
- else
- Y2Users::Password.create_plain(value)
- end
-
- logger.info "Updating the user password"
- root_user.password = value.empty? ? nil : pwd
- update_issues
- end
-
- # Root user
- #
- # @return [Y2Users::User]
- def root_user
- return @root_user if @root_user
-
- @root_user = config.users.root
- return @root_user if @root_user
-
- @root_user = Y2Users::User.create_root
- config.attach(@root_user)
- @root_user
- end
-
- # First created user
- #
- # @return [Y2Users::User, nil]
- def first_user
- config.users.reject(&:root?).first
- end
-
- # Clears the root password
- def remove_root_password
- root_user.password = nil
- update_issues
- end
-
- # It adds the user with the given parameters to the login config only if there are no error
- # issues detected like no user_name or no password given.
- #
- # @param full_name [String]
- # @param user_name [String]
- # @param password [String]
- # @param hashed_password [Boolean] true = hashed password, false = plain text password
- # @param _data [Hash]
- # @return [Array] the list of fatal issues found
- def assign_first_user(full_name, user_name, password, hashed_password, _data)
- remove_first_user
-
- user = Y2Users::User.new(user_name)
- user.gecos = [full_name]
- user.password = if hashed_password
- Y2Users::Password.create_encrypted(password)
- else
- Y2Users::Password.create_plain(password)
- end
-
- fatal_issues = user.issues.map.select(&:error?)
- return fatal_issues.map(&:message) unless fatal_issues.empty?
-
- config.attach(user)
- add_user_to_group(user_name, "wheel")
- update_issues
- []
- end
-
- # Removes the first user
- def remove_first_user
- old_users = config.users.reject(&:root?)
- config.detach(old_users) unless old_users.empty?
- update_issues
- end
-
- def write
- without_run_mount do
- on_target do
- # if root ssh key is specified ensure that sshd running and firewall has port opened
- enable_ssh if root_ssh_key?
-
- # disable root password if not set
- assign_root_password("!", true) unless root_password?
-
- system_config = Y2Users::ConfigManager.instance.system(force_read: true)
- target_config = system_config.copy
- Y2Users::ConfigMerger.new(target_config, config).merge
-
- writer = Y2Users::Linux::Writer.new(target_config, system_config)
- issues = writer.write
- logger.error(issues.inspect) unless issues.empty?
- end
- end
- end
-
- # Recalculates the list of issues
- def update_issues
- new_issues = []
-
- unless root_password? || root_ssh_key? || first_user?
- new_issues << Issue.new(
- _("Defining a user, setting the root password or a SSH public key is required"),
- source: Issue::Source::CONFIG,
- severity: Issue::Severity::ERROR
- )
- end
-
- self.issues = new_issues
- end
-
- private
-
- attr_reader :logger
-
- # Determines whether a first user is defined or not
- #
- # @return [Boolean]
- def first_user?
- config.users.reject(&:root?).any?
- end
-
- def without_run_mount(&block)
- Yast::Execute.locally!("/usr/bin/umount", "/mnt/run")
- block.call
- ensure
- Yast::Execute.locally!("/usr/bin/mount", "-o", "bind", "/run", "/mnt/run")
- end
-
- def config
- return @config if @config
-
- @config = Y2Users::ConfigManager.instance.target
- if !@config
- @config = Y2Users::Config.new
- Y2Users::ConfigManager.instance.target = @config
- end
-
- @config
- end
-
- # NOTE: the root user is created if it does not exist
- def root_password?
- !!root_user.password_content
- end
-
- def root_ssh_key
- root_user.authorized_keys.first || ""
- end
-
- def root_ssh_key?
- !root_ssh_key.empty?
- end
-
- def enable_ssh
- logger.info "root SSH public key is set, enabling sshd and opening the firewall"
- Yast::Service.Enable("sshd")
- firewalld = Y2Firewall::Firewalld.instance
- # open port only if firewalld is installed, otherwise it will crash
- firewalld.api.add_service(firewalld.default_zone, "ssh") if firewalld.installed?
- end
-
- def add_user_to_group(user_name, group_name)
- group = config.groups.by_name(group_name)
- group ||= Y2Users::Group.new(group_name)
- group.users_name << user_name
- config.attach(group) unless group.attached?
- end
- end
-end
diff --git a/service/package/gem2rpm.yml b/service/package/gem2rpm.yml
index f9dbc6f76f..fa6eb2f194 100644
--- a/service/package/gem2rpm.yml
+++ b/service/package/gem2rpm.yml
@@ -11,7 +11,6 @@
BuildRequires: dbus-1-common
Requires: dbus-1-common
Requires: dbus-1-daemon
- Requires: suseconnect-ruby-bindings
# YaST dependencies
Requires: autoyast2-installation
# ArchFilter
@@ -24,7 +23,6 @@
Requires: yast2-network
Requires: yast2-proxy
Requires: yast2-storage-ng >= 5.0.31
- Requires: yast2-users
%ifarch s390 s390x
Requires: yast2-s390 >= 4.6.4
Requires: yast2-reipl
diff --git a/service/share/agama-proxy-setup.service b/service/share/agama-proxy-setup.service
deleted file mode 100644
index 9436a5fc4b..0000000000
--- a/service/share/agama-proxy-setup.service
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=Configure wide proxy setup for agama and systemd services
-Before=agama.service
-Wants=local-fs.target
-
-[Service]
-Type=oneshot
-ExecStart=/usr/bin/agama-proxy-setup
-
-[Install]
-WantedBy=multi-user.target
-
diff --git a/service/share/dbus.conf b/service/share/dbus.conf
index e7dcecf975..a7d72596c1 100644
--- a/service/share/dbus.conf
+++ b/service/share/dbus.conf
@@ -36,14 +36,10 @@
-
-
-
-
diff --git a/service/share/org.opensuse.Agama.Manager1.service b/service/share/org.opensuse.Agama.Manager1.service
deleted file mode 100644
index 58fbc754ea..0000000000
--- a/service/share/org.opensuse.Agama.Manager1.service
+++ /dev/null
@@ -1,4 +0,0 @@
-[D-BUS Service]
-Name=org.opensuse.Agama.Manager1
-Exec=/usr/bin/agamactl manager
-User=root
diff --git a/service/test/agama/dbus/clients/manager_test.rb b/service/test/agama/dbus/clients/manager_test.rb
deleted file mode 100644
index 9b3ff7e488..0000000000
--- a/service/test/agama/dbus/clients/manager_test.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require_relative "with_service_status_examples"
-require_relative "with_progress_examples"
-require "dbus"
-require "agama/dbus/clients/manager"
-require "agama/dbus/manager"
-require "agama/installation_phase"
-
-describe Agama::DBus::Clients::Manager do
- before do
- allow(Agama::DBus::Bus).to receive(:current).and_return(bus)
- allow(bus).to receive(:service).with("org.opensuse.Agama.Manager1").and_return(service)
- allow(service).to receive(:[]).with("/org/opensuse/Agama/Manager1")
- .and_return(dbus_object)
- allow(dbus_object).to receive(:introspect)
- allow(dbus_object).to receive(:[]).with("org.opensuse.Agama.Manager1")
- .and_return(manager_iface)
- allow(dbus_object).to receive(:[]).with("org.freedesktop.DBus.Properties")
- .and_return(properties_iface)
- end
-
- let(:bus) { instance_double(Agama::DBus::Bus) }
- let(:service) { instance_double(DBus::ProxyService) }
- let(:dbus_object) { instance_double(DBus::ProxyObject) }
- let(:manager_iface) { instance_double(DBus::ProxyObjectInterface) }
- let(:properties_iface) { instance_double(DBus::ProxyObjectInterface, on_signal: nil) }
-
- subject { described_class.new }
-
- describe "#Probe" do
- # Using partial double because methods are dynamically added to the proxy object
- let(:dbus_object) { double(DBus::ProxyObject) }
-
- it "starts the config phase" do
- expect(dbus_object).to receive(:Probe)
-
- subject.probe
- end
- end
-
- describe "#commit" do
- # Using partial double because methods are dynamically added to the proxy object
- let(:dbus_object) { double(DBus::ProxyObject) }
-
- it "starts the install phase" do
- expect(dbus_object).to receive(:Commit)
-
- subject.commit
- end
- end
-
- describe "#current_installation_phase" do
- before do
- expect(manager_iface).to receive(:[]).with("CurrentInstallationPhase")
- .and_return(current_phase)
- end
-
- context "when the current phase is startup" do
- let(:current_phase) { Agama::DBus::Manager::STARTUP_PHASE }
-
- it "returns the startup phase value" do
- expect(subject.current_installation_phase).to eq(Agama::InstallationPhase::STARTUP)
- end
- end
-
- context "when the current phase is config" do
- let(:current_phase) { Agama::DBus::Manager::CONFIG_PHASE }
-
- it "returns the config phase value" do
- expect(subject.current_installation_phase).to eq(Agama::InstallationPhase::CONFIG)
- end
- end
-
- context "when the current phase is install" do
- let(:current_phase) { Agama::DBus::Manager::INSTALL_PHASE }
-
- it "returns the install phase value" do
- expect(subject.current_installation_phase).to eq(Agama::InstallationPhase::INSTALL)
- end
- end
-
- context "when the current phase is finish" do
- let(:current_phase) { Agama::DBus::Manager::FINISH_PHASE }
-
- it "returns the install phase value" do
- expect(subject.current_installation_phase).to eq(Agama::InstallationPhase::FINISH)
- end
- end
- end
-
- include_examples "service status"
- include_examples "progress"
-end
diff --git a/service/test/agama/dbus/clients/network_test.rb b/service/test/agama/dbus/clients/network_test.rb
deleted file mode 100644
index 2de638560f..0000000000
--- a/service/test/agama/dbus/clients/network_test.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/dbus/clients/network"
-
-describe Agama::DBus::Clients::Network do
- before do
- allow(DBus::SystemBus).to receive(:instance).and_return(bus)
- allow(bus).to receive(:service).with("org.freedesktop.NetworkManager").and_return(service)
- allow(service).to receive(:[]).with("/org/freedesktop/NetworkManager")
- .and_return(dbus_object)
- allow(dbus_object).to receive(:introspect)
- allow(dbus_object).to receive(:[]).with("org.freedesktop.NetworkManager")
- .and_return(network_iface)
- end
-
- let(:bus) { instance_double(DBus::SystemBus) }
- let(:service) { instance_double(DBus::ProxyService) }
- let(:dbus_object) { instance_double(DBus::ProxyObject) }
- let(:network_iface) { instance_double(DBus::ProxyObjectInterface) }
-
- describe "#on_connection_changed" do
- it "registers a callback for the StateChanged signal" do
- expect(network_iface).to receive(:on_signal).with("StateChanged")
- subject.on_connection_changed { "test" }
- end
- end
-end
diff --git a/service/test/agama/dbus/clients/software_test.rb b/service/test/agama/dbus/clients/software_test.rb
deleted file mode 100644
index 09fc5dedce..0000000000
--- a/service/test/agama/dbus/clients/software_test.rb
+++ /dev/null
@@ -1,244 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2024] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require_relative "with_issues_examples"
-require_relative "with_service_status_examples"
-require_relative "with_progress_examples"
-require "agama/dbus/clients/software"
-require "agama/dbus/service_status"
-require "agama/dbus/interfaces/service_status"
-require "dbus"
-
-describe Agama::DBus::Clients::Software do
- before do
- allow(Agama::DBus::Bus).to receive(:current).and_return(bus)
- allow(bus).to receive(:service).with("org.opensuse.Agama.Software1").and_return(service)
- allow(service).to receive(:[]).with("/org/opensuse/Agama/Software1")
- .and_return(dbus_object)
- allow(service).to receive(:[]).with("/org/opensuse/Agama/Software1/Product")
- .and_return(dbus_product)
- allow(service).to receive(:[]).with("/org/opensuse/Agama/Software1/Proposal")
- .and_return(dbus_proposal)
- allow(dbus_object).to receive(:[]).with("org.opensuse.Agama.Software1")
- .and_return(software_iface)
- allow(dbus_product).to receive(:[]).with("org.opensuse.Agama.Software1.Product")
- .and_return(product_iface)
- allow(dbus_product).to receive(:[]).with("org.freedesktop.DBus.Properties")
- .and_return(properties_iface)
- end
-
- let(:bus) { instance_double(Agama::DBus::Bus) }
- let(:service) { instance_double(::DBus::ProxyService) }
- let(:dbus_object) { instance_double(::DBus::ProxyObject, introspect: nil) }
- let(:dbus_product) { instance_double(::DBus::ProxyObject, introspect: nil) }
- let(:dbus_proposal) { instance_double(::DBus::ProxyObject, introspect: nil) }
- let(:software_iface) { instance_double(::DBus::ProxyObjectInterface) }
- let(:properties_iface) { instance_double(::DBus::ProxyObjectInterface) }
- let(:product_iface) { instance_double(::DBus::ProxyObjectInterface) }
-
- subject { described_class.new }
-
- describe "#available_products" do
- before do
- allow(product_iface).to receive(:[]).with("AvailableProducts").and_return(
- [
- ["Tumbleweed", "openSUSE Tumbleweed", {}],
- ["Leap15.3", "openSUSE Leap 15.3", {}]
- ]
- )
- end
-
- it "returns the name and display name for all available products" do
- expect(subject.available_products).to contain_exactly(
- ["Tumbleweed", "openSUSE Tumbleweed"],
- ["Leap15.3", "openSUSE Leap 15.3"]
- )
- end
- end
-
- describe "#selected_product" do
- before do
- allow(product_iface).to receive(:[]).with("SelectedProduct").and_return(product)
- end
-
- context "when there is no selected product" do
- let(:product) { "" }
-
- it "returns nil" do
- expect(subject.selected_product).to be_nil
- end
- end
-
- context "when there is a selected product" do
- let(:product) { "Tumbleweed" }
-
- it "returns the name of the selected product" do
- expect(subject.selected_product).to eq("Tumbleweed")
- end
- end
- end
-
- describe "#select_product" do
- # Using partial double because methods are dynamically added to the proxy object
- let(:dbus_product) { double(::DBus::ProxyObject, introspect: nil) }
-
- it "selects the given product" do
- expect(dbus_product).to receive(:SelectProduct).with("Tumbleweed")
-
- subject.select_product("Tumbleweed")
- end
- end
-
- describe "#probe" do
- let(:dbus_object) { double(::DBus::ProxyObject, introspect: nil, Probe: nil) }
-
- it "calls the D-Bus Probe method" do
- expect(dbus_object).to receive(:Probe)
-
- subject.probe
- end
-
- context "when a block is given" do
- it "passes the block to the Probe method (async)" do
- callback = proc {}
- expect(dbus_object).to receive(:Probe) do |&block|
- expect(block).to be(callback)
- end
-
- subject.probe(&callback)
- end
- end
- end
-
- describe "#provisions_selected" do
- let(:dbus_object) { double(::DBus::ProxyObject, introspect: nil) }
-
- it "returns true/false for every tag given" do
- expect(dbus_object).to receive(:ProvisionsSelected)
- .with(["sddm", "gdm"]).and_return([true, false])
- expect(subject.provisions_selected?(["sddm", "gdm"]))
- .to eq([true, false])
- end
- end
-
- describe "#package_installed?" do
- let(:dbus_object) do
- double(::DBus::ProxyObject, introspect: nil, IsPackageInstalled: installed?)
- end
-
- let(:package) { "NetworkManager" }
-
- context "when the package is installed" do
- let(:installed?) { true }
-
- it "returns true" do
- expect(subject.package_installed?(package)).to eq(true)
- end
- end
-
- context "when the package is installed" do
- let(:installed?) { false }
-
- it "returns false" do
- expect(subject.package_installed?(package)).to eq(false)
- end
- end
- end
-
- describe "#package_available?" do
- let(:dbus_object) do
- double(::DBus::ProxyObject, introspect: nil, IsPackageAvailable: available)
- end
-
- let(:package) { "NetworkManager" }
-
- context "when the package is available" do
- let(:available) { true }
-
- it "returns true" do
- expect(subject.package_available?(package)).to eq(true)
- end
- end
-
- context "when the package is available" do
- let(:available) { false }
-
- it "returns false" do
- expect(subject.package_available?(package)).to eq(false)
- end
- end
- end
-
- describe "#on_product_selected" do
- before do
- allow(dbus_product).to receive(:path).and_return("/org/opensuse/Agama/Test")
- allow(properties_iface).to receive(:on_signal)
- end
-
- context "if there are no callbacks for changes in properties" do
- it "subscribes to properties change signal" do
- expect(properties_iface).to receive(:on_signal)
- subject.on_product_selected { "test" }
- end
- end
-
- context "if there already are callbacks for changes in properties" do
- before do
- subject.on_product_selected { "test" }
- end
-
- it "does not subscribe to properties change signal again" do
- expect(properties_iface).to_not receive(:on_signal)
- subject.on_product_selected { "test" }
- end
- end
- end
-
- describe "#on_probe_finished" do
- before do
- allow(dbus_object).to receive(:path).and_return("/org/opensuse/Agama/Test")
- allow(software_iface).to receive(:on_signal)
- end
-
- context "if there are no callbacks for the signal" do
- it "subscribes to the signal" do
- expect(software_iface).to receive(:on_signal)
- subject.on_probe_finished { "test" }
- end
- end
-
- context "if there already are callbacks for the signal" do
- before do
- subject.on_probe_finished { "test" }
- end
-
- it "does not subscribe to the signal again" do
- expect(software_iface).to_not receive(:on_signal)
- subject.on_probe_finished { "test" }
- end
- end
- end
-
- include_examples "issues"
- include_examples "service status"
- include_examples "progress"
-end
diff --git a/service/test/agama/dbus/manager_service_test.rb b/service/test/agama/dbus/manager_service_test.rb
deleted file mode 100644
index 2155cfe098..0000000000
--- a/service/test/agama/dbus/manager_service_test.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "agama/dbus/manager_service"
-require "agama/config"
-
-describe Agama::DBus::ManagerService do
- subject(:service) { described_class.new(config, logger) }
-
- let(:config) { Agama::Config.new }
- let(:logger) { Logger.new($stdout, level: :warn) }
- let(:manager) { Agama::Manager.new(config, logger) }
-
- let(:object_server) { instance_double(DBus::ObjectServer, export: nil) }
- let(:bus) { instance_double(Agama::DBus::Bus, request_name: nil) }
-
- let(:manager_obj) { instance_double(Agama::DBus::Manager, path: "/org/opensuse/Agama/Users1") }
- let(:users_obj) { instance_double(Agama::DBus::Users, path: "/org/opensuse/Agama/Users1") }
-
- before do
- allow(Agama::DBus::Bus).to receive(:current).and_return(bus)
- allow(bus).to receive(:request_service).with("org.opensuse.Agama.Manager1")
- .and_return(object_server)
- allow(Agama::Manager).to receive(:new).with(config, logger).and_return(manager)
- allow(Agama::DBus::Manager).to receive(:new).with(manager, logger).and_return(manager_obj)
- allow(Agama::DBus::Users).to receive(:new).and_return(users_obj)
- end
-
- describe "#start" do
- it "runs the startup phase" do
- expect(manager).to receive(:startup_phase)
- subject.start
- end
- end
-
- describe "#export" do
- it "exports the manager and the user objects" do
- expect(object_server).to receive(:export).with(manager_obj)
- expect(object_server).to receive(:export).with(users_obj)
- service.export
- end
- end
-
- describe "#dispatch" do
- it "dispatches the messages from the bus" do
- expect(bus).to receive(:dispatch_message_queue)
- service.dispatch
- end
- end
-end
diff --git a/service/test/agama/dbus/manager_test.rb b/service/test/agama/dbus/manager_test.rb
deleted file mode 100644
index 51264bbf28..0000000000
--- a/service/test/agama/dbus/manager_test.rb
+++ /dev/null
@@ -1,275 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "agama/dbus/manager"
-require "agama/dbus/service_status"
-require "agama/installation_phase"
-require "agama/service_status_recorder"
-
-describe Agama::DBus::Manager do
- subject { described_class.new(backend, logger) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
- let(:service_status) { Agama::DBus::ServiceStatus.new.idle }
-
- let(:backend) do
- instance_double(Agama::Manager,
- installation_phase: installation_phase,
- software: software_client,
- on_services_status_change: nil,
- valid?: true,
- service_status: service_status)
- end
-
- let(:installation_phase) { Agama::InstallationPhase.new }
- let(:software_client) do
- instance_double(Agama::DBus::Clients::Software, on_product_selected: nil)
- end
- let(:service_status_recorder) { Agama::ServiceStatusRecorder.new }
-
- let(:idle) { Agama::DBus::ServiceStatus::IDLE }
- let(:busy) { Agama::DBus::ServiceStatus::BUSY }
- let(:progress_interface) { Agama::DBus::Interfaces::Progress::PROGRESS_INTERFACE }
- let(:service_status_interface) do
- Agama::DBus::Interfaces::ServiceStatus::SERVICE_STATUS_INTERFACE
- end
-
- before do
- allow_any_instance_of(described_class).to receive(:register_progress_callbacks)
- allow_any_instance_of(described_class).to receive(:register_service_status_callbacks)
- end
-
- it "defines Progress D-Bus interface" do
- expect(subject.intfs.keys).to include(progress_interface)
- end
-
- it "defines ServiceStatus D-Bus interface" do
- expect(subject.intfs.keys).to include(service_status_interface)
- end
-
- describe ".new" do
- it "configures callbacks for changes in the installation phase" do
- expect(subject).to receive(:dbus_properties_changed) do |iface, properties, _|
- expect(iface).to match(/Agama\.Manager1/)
- expect(properties["CurrentInstallationPhase"]).to eq(0)
- end
-
- installation_phase.startup
- end
-
- it "configures callbacks for changes in the status of other services" do
- expect(backend).to receive(:on_services_status_change)
- subject
- end
-
- it "configures callbacks from Progress interface" do
- expect_any_instance_of(described_class).to receive(:register_progress_callbacks)
- subject
- end
-
- it "configures callbacks from ServiceStatus interface" do
- expect_any_instance_of(described_class).to receive(:register_service_status_callbacks)
- subject
- end
- end
-
- describe "#config_phase" do
- context "when the service is idle" do
- before do
- service_status.idle
- end
-
- it "runs the config phase, setting the service as busy meanwhile" do
- expect(subject.service_status).to receive(:busy)
- expect(backend).to receive(:config_phase)
- expect(subject.service_status).to receive(:idle)
-
- subject.config_phase
- end
- end
-
- context "when the service is busy" do
- before do
- service_status.busy
- end
-
- it "raises a D-Bus error" do
- expect { subject.config_phase }.to raise_error(DBus::Error)
- end
- end
- end
-
- describe "#install_phase" do
- context "when the service is idle" do
- before do
- service_status.idle
- end
-
- it "runs the install phase, setting the service as busy meanwhile" do
- expect(subject.service_status).to receive(:busy)
- expect(backend).to receive(:install_phase)
- expect(subject.service_status).to receive(:idle)
-
- subject.install_phase
- end
- end
-
- context "when services configuration is invalid" do
- before do
- allow(backend).to receive(:valid?).and_return(false)
- end
-
- it "raises a DBus::Error" do
- expect { subject.install_phase }.to raise_error(DBus::Error)
- end
- end
-
- context "when the service is busy" do
- before do
- service_status.busy
- end
-
- it "raises a D-Bus error" do
- expect { subject.install_phase }.to raise_error(DBus::Error)
- end
- end
- end
-
- describe "#installation_phases" do
- it "includes all possible values for the installation phase" do
- labels = subject.installation_phases.map { |i| i["label"] }
- expect(labels).to contain_exactly("startup", "config", "install", "finish")
- end
-
- it "associates 'startup' with the id 0" do
- startup = subject.installation_phases.find { |i| i["label"] == "startup" }
- expect(startup["id"]).to eq(described_class::STARTUP_PHASE)
- end
-
- it "associates 'config' with the id 1" do
- config = subject.installation_phases.find { |i| i["label"] == "config" }
- expect(config["id"]).to eq(described_class::CONFIG_PHASE)
- end
-
- it "associates 'install' with the id 2" do
- install = subject.installation_phases.find { |i| i["label"] == "install" }
- expect(install["id"]).to eq(described_class::INSTALL_PHASE)
- end
-
- it "associates 'finish' with the id 3" do
- install = subject.installation_phases.find { |i| i["label"] == "finish" }
- expect(install["id"]).to eq(described_class::FINISH_PHASE)
- end
- end
-
- describe "#current_installation_phase" do
- context "when the current installation phase is startup" do
- before do
- installation_phase.startup
- end
-
- it "returns the startup phase vale" do
- expect(subject.current_installation_phase).to eq(described_class::STARTUP_PHASE)
- end
- end
-
- context "when the current installation phase is config" do
- before do
- installation_phase.config
- end
-
- it "returns the config phase value" do
- expect(subject.current_installation_phase).to eq(described_class::CONFIG_PHASE)
- end
- end
-
- context "when the current installation phase is install" do
- before do
- installation_phase.install
- end
-
- it "returns the install phase value" do
- expect(subject.current_installation_phase).to eq(described_class::INSTALL_PHASE)
- end
- end
- end
-
- describe "#busy_services" do
- before do
- allow(backend).to receive(:busy_services).and_return(["org.opensuse.Agama.Software1"])
- end
-
- it "returns the names of the busy services" do
- expect(subject.busy_services).to contain_exactly("org.opensuse.Agama.Software1")
- end
- end
-
- describe "#can_install?" do
- before do
- allow(backend).to receive(:valid?).and_return(valid?)
- end
-
- context "when installation settings are valid" do
- let(:valid?) { true }
-
- it "returns true" do
- expect(subject.can_install?).to eq(true)
- end
- end
-
- context "when installation settings are valid" do
- let(:valid?) { false }
-
- it "returns false" do
- expect(subject.can_install?).to eq(false)
- end
- end
- end
-
- describe "#finish_phase" do
- let(:finished) { true }
-
- before do
- allow(backend).to receive(:finish_installation).and_return(finished)
- end
-
- it "finish the installation with the method given" do
- method = "reboot"
- expect(backend).to receive(:finish_installation).with(method)
- subject.finish_phase("reboot")
- end
-
- context "when finished" do
- it "returns true" do
- expect(subject.finish_phase("poweroff")).to eq(true)
- end
- end
-
- context "when not finished" do
- let(:finished) { false }
-
- it "returns false" do
- expect(subject.finish_phase("halt")).to eq(false)
- end
- end
- end
-end
diff --git a/service/test/agama/dbus/service_runner_test.rb b/service/test/agama/dbus/service_runner_test.rb
index 1f3632bf8b..5be98a4492 100644
--- a/service/test/agama/dbus/service_runner_test.rb
+++ b/service/test/agama/dbus/service_runner_test.rb
@@ -22,22 +22,22 @@
require_relative "../../test_helper"
require "agama/config"
require "agama/dbus/service_runner"
-require "agama/dbus/manager_service"
-require "agama/manager"
+require "agama/dbus/storage_service"
+require "agama/storage/manager"
describe Agama::DBus::ServiceRunner do
describe "#run" do
- subject(:runner) { Agama::DBus::ServiceRunner.new(:manager) }
+ subject(:runner) { Agama::DBus::ServiceRunner.new(:storage) }
let(:config) { Agama::Config.new }
let(:logger) { Logger.new($stdout) }
- let(:manager) { Agama::Manager.new(config, logger) }
- let(:service) { instance_double(Agama::DBus::ManagerService) }
+ let(:storage) { instance_double(Agama::Storage::Manager) }
+ let(:service) { instance_double(Agama::DBus::StorageService, start: nil) }
before do
allow(Agama::Config).to receive(:current).and_return(config)
- allow(Agama::Manager).to receive(:new).with(config).and_return(manager)
- allow(Agama::DBus::ManagerService).to receive(:new).with(config, Logger)
+ allow(Agama::Storage::Manager).to receive(:new).with(config).and_return(storage)
+ allow(Agama::DBus::StorageService).to receive(:new).with(config, Logger)
.and_return(service)
end
diff --git a/service/test/agama/dbus/software/manager_test.rb b/service/test/agama/dbus/software/manager_test.rb
deleted file mode 100644
index db093d4a48..0000000000
--- a/service/test/agama/dbus/software/manager_test.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2024] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/config"
-require "agama/dbus/clients/network"
-require "agama/dbus/interfaces/issues"
-require "agama/dbus/interfaces/progress"
-require "agama/dbus/interfaces/service_status"
-require "agama/dbus/software/manager"
-require "agama/software"
-
-describe Agama::DBus::Software::Manager do
- subject { described_class.new(backend, logger) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- let(:backend) { Agama::Software::Manager.new(config, logger) }
-
- let(:target_dir) { Dir.mktmpdir }
-
- let(:config) { Agama::Config.new(config_data) }
-
- let(:config_data) do
- path = File.join(FIXTURES_PATH, "root_dir/etc/agama.yaml")
- YAML.safe_load(File.read(path))
- end
-
- let(:progress_interface) { Agama::DBus::Interfaces::Progress::PROGRESS_INTERFACE }
-
- let(:service_status_interface) do
- Agama::DBus::Interfaces::ServiceStatus::SERVICE_STATUS_INTERFACE
- end
-
- let(:issues_interface) { Agama::DBus::Interfaces::Issues::ISSUES_INTERFACE }
-
- before do
- stub_const("Agama::Software::Manager::TARGET_DIR", target_dir)
- allow(Yast::PackageCallbacks).to receive(:InitPackageCallbacks)
- allow(Agama::DBus::Clients::Network).to receive(:new).and_return(network_client)
- allow(backend).to receive(:probe)
- allow(backend).to receive(:propose)
- allow(backend).to receive(:install)
- allow(backend).to receive(:finish)
- allow(subject).to receive(:dbus_properties_changed)
- end
-
- after do
- FileUtils.rm_r(target_dir)
- end
-
- let(:network_client) do
- instance_double(Agama::DBus::Clients::Network, on_connection_changed: nil)
- end
-
- it "defines Issues D-Bus interface" do
- expect(subject.intfs.keys).to include(issues_interface)
- end
-
- it "defines Progress D-Bus interface" do
- expect(subject.intfs.keys).to include(progress_interface)
- end
-
- it "defines ServiceStatus D-Bus interface" do
- expect(subject.intfs.keys).to include(service_status_interface)
- end
-
- it "emits signal when issues changes" do
- expect(subject).to receive(:issues_properties_changed)
- backend.issues = []
- end
-
- describe "#probe" do
- it "runs the probing, setting the service as busy meanwhile, and emits a signal" do
- expect(subject.service_status).to receive(:busy)
- expect(backend).to receive(:probe)
- expect(subject.service_status).to receive(:idle)
- expect(subject).to receive(:ProbeFinished)
-
- subject.probe
- end
- end
-
- describe "#propose" do
- it "calculates the proposal, setting the service as busy meanwhile" do
- expect(subject.service_status).to receive(:busy)
- expect(backend).to receive(:propose)
- expect(subject.service_status).to receive(:idle)
-
- subject.propose
- end
- end
-
- describe "#install" do
- it "installs the software, setting the service as busy meanwhile" do
- expect(subject.service_status).to receive(:busy)
- expect(backend).to receive(:install)
- expect(subject.service_status).to receive(:idle)
-
- subject.install
- end
- end
-
- describe "#finish" do
- it "finishes the software installation, setting the service as busy meanwhile" do
- expect(subject.service_status).to receive(:busy)
- expect(backend).to receive(:finish)
- expect(subject.service_status).to receive(:idle)
-
- subject.finish
- end
- end
-
- describe "D-Bus IsPackageInstalled" do
- it "returns whether the package is installed or not" do
- expect(backend).to receive(:package_installed?).with("NetworkManager").and_return(true)
- installed = subject.public_send(
- "org.opensuse.Agama.Software1%%IsPackageInstalled", "NetworkManager"
- )
- expect(installed).to eq(true)
- end
- end
-
- describe "D-Bus IsPackageAvailable" do
- it "returns whether the package is available or not" do
- expect(backend).to receive(:package_available?).with("NetworkManager").and_return(true)
- available = subject.public_send(
- "org.opensuse.Agama.Software1%%IsPackageAvailable", "NetworkManager"
- )
- expect(available).to eq(true)
- end
- end
-end
diff --git a/service/test/agama/dbus/software/product_test.rb b/service/test/agama/dbus/software/product_test.rb
deleted file mode 100644
index 3516950e7a..0000000000
--- a/service/test/agama/dbus/software/product_test.rb
+++ /dev/null
@@ -1,440 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/dbus/software/product"
-require "agama/config"
-require "agama/registration"
-require "agama/software/manager"
-require "suse/connect"
-
-describe Agama::DBus::Software::Product do
- subject { described_class.new(backend, logger) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- let(:backend) { Agama::Software::Manager.new(config, logger) }
-
- let(:config) { Agama::Config.new }
-
- let(:target_dir) { Dir.mktmpdir }
-
- before do
- stub_const("Agama::Software::Manager::TARGET_DIR", target_dir)
- allow(Yast::PackageCallbacks).to receive(:InitPackageCallbacks)
- allow(config).to receive(:products).and_return(products)
- allow(subject).to receive(:dbus_properties_changed)
- allow(Agama::ProductReader).to receive(:new).and_call_original
- end
-
- after do
- FileUtils.rm_r(target_dir)
- end
-
- let(:products) do
- { "Tumbleweed" => {}, "MicroOS" => {} }
- end
-
- it "defines Product D-Bus interface" do
- expect(subject.intfs.keys).to include("org.opensuse.Agama.Software1.Product")
- end
-
- it "defines Registration D-Bus interface" do
- expect(subject.intfs.keys).to include("org.opensuse.Agama1.Registration")
- end
-
- it "defines Issues D-Bus interface" do
- expect(subject.intfs.keys).to include("org.opensuse.Agama1.Issues")
- end
-
- describe "select_product" do
- context "if the product is correctly selected" do
- it "returns result code 0 with empty description" do
- expect(subject.select_product("Tumbleweed")).to contain_exactly(0, "")
- end
- end
-
- context "if the given product is already selected" do
- before do
- subject.select_product("Tumbleweed")
- end
-
- it "returns result code 1 and description" do
- expect(subject.select_product("Tumbleweed")).to contain_exactly(1, /already selected/)
- end
- end
-
- context "if the current product is registered" do
- before do
- subject.select_product("MicroOS")
- allow(backend.registration).to receive(:registered).and_return(true)
- end
-
- it "returns result code 2 and description" do
- expect(subject.select_product("Tumbleweed")).to contain_exactly(2, /must be deregistered/)
- end
- end
-
- context "if the product is unknown" do
- it "returns result code 3 and description" do
- expect(subject.select_product("Unknown")).to contain_exactly(3, /unknown product/i)
- end
- end
- end
-
- describe "#registered" do
- before do
- allow(backend.registration).to receive(:registered).and_return(registered)
- end
-
- context "if there is no registered product yet" do
- let(:registered) { false }
-
- it "returns false" do
- expect(subject.registered).to eq(false)
- end
- end
-
- context "if there is a registered product" do
- let(:registered) { true }
-
- it "returns true" do
- expect(subject.registered).to eq(true)
- end
- end
- end
-
- describe "#url" do
- before do
- allow(backend.registration).to receive(:registration_url).and_return(url)
- end
-
- context "if there is no registration url yet" do
- let(:url) { nil }
-
- it "returns an empty string" do
- expect(subject.url).to eq("")
- end
- end
-
- context "if there is a registration url" do
- let(:url) { "https://example.com" }
-
- it "returns the registration url" do
- expect(subject.url).to eq("https://example.com")
- end
- end
- end
-
- describe "#reg_code" do
- before do
- allow(backend.registration).to receive(:reg_code).and_return(reg_code)
- end
-
- context "if there is no registration code yet" do
- let(:reg_code) { nil }
-
- it "returns an empty string" do
- expect(subject.reg_code).to eq("")
- end
- end
-
- context "if there is a registration code" do
- let(:reg_code) { "123XX432" }
-
- it "returns the registration code" do
- expect(subject.reg_code).to eq("123XX432")
- end
- end
- end
-
- describe "#email" do
- before do
- allow(backend.registration).to receive(:email).and_return(email)
- end
-
- context "if there is no registration email yet" do
- let(:email) { nil }
-
- it "returns an empty string" do
- expect(subject.email).to eq("")
- end
- end
-
- context "if there is a registration email" do
- let(:email) { "test@suse.com" }
-
- it "returns the registration email" do
- expect(subject.email).to eq("test@suse.com")
- end
- end
- end
-
- describe "#register" do
- before do
- allow(backend.registration).to receive(:reg_code).and_return(nil)
- end
-
- context "if there is no product selected yet" do
- it "returns result code 1 and description" do
- expect(subject.register("123XX432")).to contain_exactly(1, /product not selected/i)
- end
- end
-
- context "if there is a selected product" do
- before do
- backend.select_product("Tumbleweed")
-
- allow(backend.product).to receive(:repositories).and_return(repositories)
- allow(backend.product).to receive(:registration).and_return(true)
- end
-
- let(:repositories) { [] }
-
- context "if the product is already registered" do
- it "returns result code 2 and description if the code is different" do
- allow(backend.registration).to receive(:registered).and_return(true)
- expect(subject.register("123XX432")).to contain_exactly(2, /product already registered/i)
- end
-
- it "returns result code 0 and empty error if the code is the same" do
- allow(backend.registration).to receive(:reg_code).and_return("123XX432")
- allow(backend.registration).to receive(:registered).and_return(true)
- expect(subject.register("123XX432")).to contain_exactly(0, "")
- end
- end
-
- context "if the product does not require registration" do
- let(:repositories) { ["https://repo"] }
-
- before do
- allow(backend.product).to receive(:registration).and_return(false)
- end
-
- it "returns result code 3 and description" do
- expect(subject.register("123XX432")).to contain_exactly(3, /not require registration/i)
- end
- end
-
- context "if there is a network error" do
- before do
- allow(backend.registration).to receive(:register).and_raise(SocketError)
- end
-
- it "returns result code 4 and description" do
- expect(subject.register("123XX432")).to contain_exactly(4, /network error/)
- end
- end
-
- context "if there is a timeout" do
- before do
- allow(backend.registration).to receive(:register).and_raise(Timeout::Error)
- end
-
- it "returns result code 5 and description" do
- expect(subject.register("123XX432")).to contain_exactly(5, /timeout/)
- end
- end
-
- context "if there is an API error" do
- before do
- allow(backend.registration).to receive(:register).and_raise(SUSE::Connect::ApiError, "")
- end
-
- it "returns result code 6 and description" do
- expect(subject.register("123XX432")).to contain_exactly(6, /registration server failed/)
- end
- end
-
- context "if there is a missing credentials error" do
- before do
- allow(backend.registration)
- .to receive(:register).and_raise(SUSE::Connect::MissingSccCredentialsFile)
- end
-
- it "returns result code 7 and description" do
- expect(subject.register("123XX432")).to contain_exactly(7, /missing credentials/)
- end
- end
-
- context "if there is an incorrect credentials error" do
- before do
- allow(backend.registration)
- .to receive(:register).and_raise(SUSE::Connect::MalformedSccCredentialsFile)
- end
-
- it "returns result code 8 and description" do
- expect(subject.register("123XX432")).to contain_exactly(8, /incorrect credentials/)
- end
- end
-
- context "if there is an invalid certificate error" do
- before do
- allow(backend.registration).to receive(:register).and_raise(OpenSSL::SSL::SSLError)
- end
-
- it "returns result code 9 and description" do
- expect(subject.register("123XX432")).to contain_exactly(9, /invalid certificate/)
- end
- end
-
- context "if there is an internal error" do
- before do
- allow(backend.registration).to receive(:register).and_raise(JSON::ParserError)
- end
-
- it "returns result code 10 and description" do
- expect(subject.register("123XX432")).to contain_exactly(10, /registration server failed/)
- end
- end
-
- context "if there is any other error" do
- before do
- allow(backend.registration).to receive(:register).and_raise(RuntimeError)
- end
-
- it "returns result code 14 and description" do
- expect(subject.register("123XX432")).to contain_exactly(14, /registration server failed/)
- end
- end
-
- context "if the registration is correctly done" do
- before do
- allow(backend.registration).to receive(:register)
- end
-
- it "returns result code 0 with empty description" do
- expect(subject.register("123XX432")).to contain_exactly(0, "")
- end
- end
- end
- end
-
- describe "#deregister" do
- before do
- allow(backend.registration).to receive(:registered).and_return(true)
- end
-
- context "if there is no product selected yet" do
- it "returns result code 1 and description" do
- expect(subject.deregister).to contain_exactly(1, /product not selected/i)
- end
- end
-
- context "if there is a selected product" do
- before do
- backend.select_product("Tumbleweed")
- end
-
- context "if the product is not registered yet" do
- before do
- allow(backend.registration).to receive(:registered).and_return(false)
- end
-
- it "returns result code 2 and description" do
- expect(subject.deregister).to contain_exactly(2, /product not registered/i)
- end
- end
-
- context "if there is a network error" do
- before do
- allow(backend.registration).to receive(:deregister).and_raise(SocketError)
- end
-
- it "returns result code 3 and description" do
- expect(subject.deregister).to contain_exactly(3, /network error/)
- end
- end
-
- context "if there is a timeout" do
- before do
- allow(backend.registration).to receive(:deregister).and_raise(Timeout::Error)
- end
-
- it "returns result code 4 and description" do
- expect(subject.deregister).to contain_exactly(4, /timeout/)
- end
- end
-
- context "if there is an API error" do
- before do
- allow(backend.registration).to receive(:deregister).and_raise(SUSE::Connect::ApiError, "")
- end
-
- it "returns result code 5 and description" do
- expect(subject.deregister).to contain_exactly(5, /registration server failed/)
- end
- end
-
- context "if there is a missing credentials error" do
- before do
- allow(backend.registration)
- .to receive(:deregister).and_raise(SUSE::Connect::MissingSccCredentialsFile)
- end
-
- it "returns result code 6 and description" do
- expect(subject.deregister).to contain_exactly(6, /missing credentials/)
- end
- end
-
- context "if there is an incorrect credentials error" do
- before do
- allow(backend.registration)
- .to receive(:deregister).and_raise(SUSE::Connect::MalformedSccCredentialsFile)
- end
-
- it "returns result code 7 and description" do
- expect(subject.deregister).to contain_exactly(7, /incorrect credentials/)
- end
- end
-
- context "if there is an invalid certificate error" do
- before do
- allow(backend.registration).to receive(:deregister).and_raise(OpenSSL::SSL::SSLError)
- end
-
- it "returns result code 8 and description" do
- expect(subject.deregister).to contain_exactly(8, /invalid certificate/)
- end
- end
-
- context "if there is an internal error" do
- before do
- allow(backend.registration).to receive(:deregister).and_raise(JSON::ParserError)
- end
-
- it "returns result code 9 and description" do
- expect(subject.deregister).to contain_exactly(9, /registration server failed/)
- end
- end
-
- context "if the deregistration is correctly done" do
- before do
- allow(backend.registration).to receive(:deregister)
- end
-
- it "returns result code 0 with empty description" do
- expect(subject.deregister).to contain_exactly(0, "")
- end
- end
- end
- end
-end
diff --git a/service/test/agama/dbus/storage/manager_test.rb b/service/test/agama/dbus/storage/manager_test.rb
index 4e46f03755..030dc900f8 100644
--- a/service/test/agama/dbus/storage/manager_test.rb
+++ b/service/test/agama/dbus/storage/manager_test.rb
@@ -31,7 +31,6 @@
require "agama/storage/iscsi/manager"
require "agama/storage/dasd/manager"
require "agama/dbus/storage/dasds_tree"
-require "agama/dbus/clients/software"
require "y2storage"
require "dbus"
@@ -65,10 +64,6 @@ def parse(string)
on_sessions_change: nil)
end
- let(:software) do
- instance_double(Agama::DBus::Clients::Software, on_probe_finished: nil)
- end
-
before do
# Speed up tests by avoiding real check of TPM presence.
allow(Y2Storage::EncryptionMethod::TPM_FDE).to receive(:possible?).and_return(true)
@@ -80,7 +75,6 @@ def parse(string)
allow(backend).to receive(:on_issues_change)
allow(backend).to receive(:actions).and_return([])
allow(backend).to receive(:iscsi).and_return(iscsi)
- allow(backend).to receive(:software).and_return(software)
allow(backend).to receive(:proposal).and_return(proposal)
mock_storage(devicegraph: "empty-hd-50GiB.yaml")
end
diff --git a/service/test/agama/dbus/users_test.rb b/service/test/agama/dbus/users_test.rb
deleted file mode 100644
index 4e60be3eae..0000000000
--- a/service/test/agama/dbus/users_test.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "agama/dbus/interfaces/issues"
-require "agama/dbus/interfaces/service_status"
-require "agama/dbus/users"
-require "agama/users"
-require "y2users"
-
-describe Agama::DBus::Users do
- subject { described_class.new(backend, logger) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- let(:backend) { instance_double(Agama::Users, on_issues_change: nil) }
-
- let(:issues_interface) { Agama::DBus::Interfaces::Issues::ISSUES_INTERFACE }
-
- before do
- allow_any_instance_of(described_class).to receive(:register_service_status_callbacks)
- end
-
- it "defines ServiceStatus D-Bus interface" do
- expect(subject.intfs.keys).to include(
- Agama::DBus::Interfaces::ServiceStatus::SERVICE_STATUS_INTERFACE
- )
- end
-
- it "defines Issues D-Bus interface" do
- expect(subject.intfs.keys).to include(issues_interface)
- end
-
- describe ".new" do
- it "configures callbacks from ServiceStatus interface" do
- expect_any_instance_of(described_class).to receive(:register_service_status_callbacks)
- subject
- end
-
- it "configures callbacks from Issues interface" do
- expect(backend).to receive(:on_issues_change)
- subject
- end
- end
-
- describe "first_user" do
- before do
- allow(backend).to receive(:first_user).and_return(user)
- end
-
- context "if there is no user yet" do
- let(:user) { nil }
-
- it "returns default data" do
- expect(subject.first_user).to eq(["", "", "", false, {}])
- end
- end
-
- context "if there is an user" do
- let(:password) { Y2Users::Password.create_encrypted("12345") }
- let(:user) do
- instance_double(Y2Users::User,
- full_name: "Test user",
- name: "test",
- password: password,
- password_content: password.value.to_s)
- end
-
- it "returns the first user data" do
- expect(subject.first_user).to eq(["Test user", "test", password.value.to_s, true, {}])
- end
- end
- end
-end
diff --git a/service/test/agama/dbus/y2dir/modules/autologin_test.rb b/service/test/agama/dbus/y2dir/modules/autologin_test.rb
deleted file mode 100644
index 3fbc34113c..0000000000
--- a/service/test/agama/dbus/y2dir/modules/autologin_test.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../../test_helper"
-require_relative File.join(
- SRC_PATH, "agama", "dbus", "y2dir", "modules", "Autologin.rb"
-)
-
-describe Yast::Autologin do
- subject { Yast::Autologin }
-
- before do
- subject.main
- allow(Agama::HTTP::Clients::Software).to receive(:new).and_return(client)
- end
-
- let(:client) do
- instance_double(Agama::HTTP::Clients::Software)
- end
-
- describe "#supported?" do
- before do
- allow(client).to receive(:provisions_selected?).with(Array)
- .and_return(provisions_selected?)
- end
-
- context "when some display manager is selected" do
- let(:provisions_selected?) { [true, false] }
-
- xit "returns true" do
- expect(subject.supported?).to eq(true)
- end
- end
-
- context "when no display managers are selected" do
- let(:provisions_selected?) { [false, false] }
-
- it "returns false" do
- expect(subject.supported?).to eq(false)
- end
- end
- end
-end
diff --git a/service/test/agama/http/clients/scripts_test.rb b/service/test/agama/http/clients/scripts_test.rb
deleted file mode 100644
index b7a6517d6a..0000000000
--- a/service/test/agama/http/clients/scripts_test.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-# 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 version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/http/clients/scripts"
-
-describe Agama::HTTP::Clients::Scripts do
- subject(:scripts) { described_class.new(Logger.new($stdout)) }
- let(:response) { instance_double(Net::HTTPResponse, body: "") }
-
- before do
- allow(File).to receive(:read).with("/run/agama/token")
- .and_return("123456")
- end
-
- describe "#run" do
- it "calls the end-point to run the scripts" do
- url = URI("http://localhost/api/scripts/run")
- expect(Net::HTTP).to receive(:post).with(url, "post".to_json, {
- "Content-Type": "application/json",
- Authorization: "Bearer 123456"
- }).and_return(response)
- scripts.run("post")
- end
- end
-end
diff --git a/service/test/agama/manager_test.rb b/service/test/agama/manager_test.rb
deleted file mode 100644
index 539ac8e2b9..0000000000
--- a/service/test/agama/manager_test.rb
+++ /dev/null
@@ -1,287 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../test_helper"
-require_relative "with_progress_examples"
-require "agama/manager"
-require "agama/config"
-require "agama/http"
-require "agama/issue"
-require "agama/question"
-require "agama/dbus/service_status"
-require "agama/users"
-
-describe Agama::Manager do
- subject { described_class.new(config, logger) }
-
- let(:config_path) do
- File.join(FIXTURES_PATH, "root_dir", "etc", "agama.yaml")
- end
- let(:config) { Agama::Config.from_file(config_path) }
- let(:logger) { Logger.new($stdout, level: :warn) }
- let(:proxy) do
- instance_double(Agama::ProxySetup, propose: nil, install: nil)
- end
-
- let(:software) do
- instance_double(
- Agama::HTTP::Clients::Software,
- probe: nil, install: nil, propose: nil, finish: nil,
- config: { "product" => { "id" => product } }, errors?: false,
- selected_product: product
- )
- end
- let(:users) do
- instance_double(
- Agama::Users, write: nil, issues: []
- )
- end
- let(:http_client) { instance_double(Agama::HTTP::Clients::Main, install: nil) }
- let(:network) { instance_double(Agama::Network, install: nil, startup: nil) }
- let(:storage) do
- instance_double(
- Agama::DBus::Clients::Storage, probe: nil, install: nil, finish: nil,
- :product= => nil
- )
- end
- let(:scripts) do
- instance_double(
- Agama::HTTP::Clients::Scripts, run: nil
- )
- end
-
- let(:product) { nil }
-
- before do
- allow(Agama::Network).to receive(:new).and_return(network)
- allow(Agama::ProxySetup).to receive(:instance).and_return(proxy)
- allow(Agama::HTTP::Clients::Main).to receive(:new).and_return(http_client)
- allow(Agama::HTTP::Clients::Software).to receive(:new).and_return(software)
- allow(Agama::DBus::Clients::Storage).to receive(:new).and_return(storage)
- allow(Agama::Users).to receive(:new).and_return(users)
- allow(Agama::HTTP::Clients::Scripts).to receive(:new)
- .and_return(scripts)
- end
-
- describe "#startup_phase" do
- before do
- allow(subject).to receive(:config_phase)
- end
-
- it "sets the installation phase to startup" do
- subject.startup_phase
- expect(subject.installation_phase.startup?).to eq(true)
- end
-
- it "does the network startup configuration" do
- expect(network).to receive(:startup)
- subject.startup_phase
- end
-
- context "when there is no selected product" do
- let(:product) { nil }
-
- it "does not run the config phase" do
- expect(subject).to_not receive(:config_phase)
- subject.startup_phase
- end
- end
-
- context "when there is a selected product" do
- let(:product) { "Tumbleweed" }
-
- it "runs the config phase" do
- expect(subject).to receive(:config_phase)
- subject.startup_phase
- end
- end
- end
-
- describe "#config_phase" do
- let(:product) { "Geecko" }
-
- it "sets the installation phase to config" do
- subject.config_phase
- expect(subject.installation_phase.config?).to eq(true)
- end
-
- it "sets the product for the storage module" do
- expect(storage).to receive(:product=).with product
- subject.config_phase
- end
-
- it "calls #probe method for both software and storage modules if reprobe is requested" do
- expect(storage).to receive(:probe)
- expect(software).to receive(:probe)
- subject.config_phase(reprobe: true)
- end
-
- it "calls #probe method only for the software module if reprobe is not requested" do
- expect(software).to receive(:probe)
- expect(storage).to_not receive(:probe)
- subject.config_phase(reprobe: false)
- end
- end
-
- describe "#install_phase" do
- it "sets the installation phase to install and later to finish" do
- expect(subject.installation_phase).to receive(:install)
- subject.install_phase
- expect(subject.installation_phase.finish?).to eq(true)
- end
-
- it "calls #propose on proxy and software modules" do
- expect(proxy).to receive(:propose)
- expect(software).to receive(:propose)
- subject.install_phase
- end
-
- it "calls #install (or #write) method of each module" do
- expect(network).to receive(:install)
- expect(software).to receive(:install)
- expect(software).to receive(:finish)
- expect(http_client).to receive(:install)
- expect(storage).to receive(:install)
- expect(scripts).to receive(:run).with("postPartitioning")
- expect(storage).to receive(:finish)
- expect(users).to receive(:write)
- subject.install_phase
- end
- end
-
- let(:idle) { Agama::DBus::ServiceStatus::IDLE }
- let(:busy) { Agama::DBus::ServiceStatus::BUSY }
-
- describe "#busy_services" do
- before do
- allow(subject).to receive(:service_status_recorder).and_return(service_status_recorder)
-
- service_status_recorder.save("org.opensuse.Agama.Test1", busy)
- service_status_recorder.save("org.opensuse.Agama.Test2", idle)
- service_status_recorder.save("org.opensuse.Agama.Test3", busy)
- end
-
- let(:service_status_recorder) { Agama::ServiceStatusRecorder.new }
-
- it "returns the name of the busy services" do
- expect(subject.busy_services).to contain_exactly(
- "org.opensuse.Agama.Test1",
- "org.opensuse.Agama.Test3"
- )
- end
- end
-
- describe "#on_services_status_change" do
- before do
- allow(subject).to receive(:service_status_recorder).and_return(service_status_recorder)
- end
-
- let(:service_status_recorder) { Agama::ServiceStatusRecorder.new }
-
- it "add a callback to be run when the status of a service changes" do
- subject.on_services_status_change { logger.info("change status") }
-
- expect(logger).to receive(:info).with(/change status/)
- service_status_recorder.save("org.opensuse.Agama.Test", busy)
- end
- end
-
- describe "#valid?" do
- context "when there are not validation problems" do
- it "returns true" do
- expect(subject.valid?).to eq(true)
- end
- end
-
- context "when the users configuration is not valid" do
- before do
- allow(users).to receive(:issues).and_return([instance_double(Agama::Issue)])
- end
-
- it "returns false" do
- expect(subject.valid?).to eq(false)
- end
- end
-
- context "when the software configuration is not valid" do
- before do
- allow(software).to receive(:errors?).and_return(true)
- end
-
- it "returns false" do
- expect(subject.valid?).to eq(false)
- end
- end
- end
-
- describe "#finish_installation" do
- let(:finished) { false }
- let(:iguana) { true }
- let(:method) { "reboot" }
-
- before do
- allow(subject).to receive(:iguana?).and_return(iguana)
- allow(subject.installation_phase).to receive(:finish?).and_return(finished)
- allow(logger).to receive(:error)
- end
-
- context "when it is not in finish the phase" do
- it "logs the error and returns false" do
- expect(logger).to receive(:error).with(/not finished/)
- expect(subject.finish_installation(method)).to eq(false)
- end
- end
-
- context "when it is in the finish phase" do
- let(:finished) { true }
-
- context "and it is executed using iguana" do
- it "runs agamactl -k" do
- expect(subject).to receive(:system).with(/agamactl -k/).and_return(true)
- expect(subject.finish_installation(method)).to eq(true)
- end
- end
-
- context "and it is not executed using iguana" do
- let(:iguana) { false }
-
- it "executes the command to the finish method given" do
- expect(subject).to receive(:system).with(/shutdown -r now/).and_return(true)
- expect(subject.finish_installation(method)).to eq(true)
- end
- end
- end
- end
-
- describe "#collect_logs" do
- it "collects the logs and returns the path to the archive" do
- # %x returns the command output including trailing \n
- expect(subject).to receive(:`)
- .with("agama logs store ")
- .and_return("/tmp/y2log-hWBn95.tar.xz\n")
-
- path = subject.collect_logs
- expect(path).to eq("/tmp/y2log-hWBn95.tar.xz")
- end
- end
-
- include_examples "progress"
-end
diff --git a/service/test/agama/network_test.rb b/service/test/agama/network_test.rb
deleted file mode 100644
index 20d944cbbf..0000000000
--- a/service/test/agama/network_test.rb
+++ /dev/null
@@ -1,224 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../test_helper"
-require "tmpdir"
-require "agama/network"
-
-describe Agama::Network do
- subject(:network) { described_class.new(logger) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
- let(:targetdir) { File.join(rootdir, "mnt") }
- let(:fixtures) { File.join(FIXTURES_PATH, "root_dir") }
- let(:hostname_path) { File.join(fixtures, "etc", "hostname") }
- let(:agama_dir) { File.join(rootdir, "run", "agama") }
- let(:not_copy_network) { File.join(agama_dir, "not_copy_network") }
-
- before do
- allow(Yast::Installation).to receive(:destdir).and_return(targetdir)
- stub_const("Agama::Network::HOSTNAME", hostname_path)
- stub_const("Agama::Network::RUN_NM_DIR", File.join(rootdir, "run", "NetworkManager"))
- stub_const("Agama::Network::AGAMA_SYSTEMD_LINK",
- File.join(rootdir, Agama::Network::AGAMA_SYSTEMD_LINK))
- stub_const("Agama::Network::NOT_COPY_NETWORK", not_copy_network)
- FileUtils.mkdir_p(agama_dir)
- end
-
- after do
- FileUtils.remove_entry(rootdir)
- end
-
- describe "#install" do
- let(:rootdir) { Dir.mktmpdir }
-
- let(:etcdir) do
- File.join(rootdir, "etc", "NetworkManager", "system-connections")
- end
-
- let(:service) { instance_double(Yast2::Systemd::Service, enable: nil) }
-
- before do
- allow(Yast2::Systemd::Service).to receive(:find).with("NetworkManager").and_return(service)
- stub_const("Agama::Network::ETC_NM_DIR", etcdir)
- end
-
- context "when there is some Agama systemd network link file" do
- before do
- FileUtils.cp_r(Dir["#{fixtures}/*"], rootdir)
- end
-
- it "copies the files to /etc/systemd/network" do
- network.install
- expect(File).to exist(
- File.join(targetdir, "etc", "systemd", "network", "10-agama-ifname-bootdev.link")
- )
- end
- end
-
- context "when NetworkManager configuration files are present" do
- before do
- FileUtils.mkdir_p(File.join(etcdir, "system-connections"))
- FileUtils.touch(File.join(etcdir, "system-connections", "wired.nmconnection"))
- end
-
- context "and the /run/agama/not_copy_network file does not exist" do
- it "copies the configuration files" do
- network.install
- expect(File).to exist(
- File.join(targetdir, etcdir, "system-connections", "wired.nmconnection")
- )
- end
- end
-
- context "and the /run/agama/not_copy_network file exists" do
- around do |block|
- FileUtils.mkdir_p(agama_dir)
- FileUtils.touch(not_copy_network)
- block.call
- FileUtils.rm_f(not_copy_network)
- end
-
- it "does not try to copy any file" do
- expect(FileUtils).to_not receive(:cp_r)
- network.install
- end
- end
- end
-
- context "when NetworkManager configuration files are not available" do
- it "does not try to copy any file" do
- expect(FileUtils).to_not receive(:cp_r)
- network.install
- end
- end
-
- context "when NetworkManager connections are not defined" do
- before do
- FileUtils.mkdir_p(etcdir)
- end
-
- it "does not try to copy any file" do
- network.install
- expect(Dir).to_not exist(
- File.join(targetdir, etcdir, "system-connections")
- )
- end
- end
-
- context "when an static hostname is present" do
- let(:test_path) { File.join(fixtures, "etc", "hostname.test") }
-
- around do |block|
- FileUtils.mv test_path, hostname_path
- block.call
- FileUtils.mv hostname_path, test_path
- end
-
- it "copies it to the target system" do
- network.install
- expect(File.exist?(File.join(targetdir, Agama::Network::HOSTNAME))).to eql(true)
- end
- end
-
- it "enables the NetworkManager service" do
- expect(service).to receive(:enable)
- network.install
- end
-
- context "when the NetworkManager service is not found" do
- let(:service) { nil }
-
- it "logs an error" do
- expect(logger).to receive(:error).with("NetworkManager service was not found")
- network.install
- end
- end
- end
-
- describe "#link_resolv" do
- let(:rootdir) { Dir.mktmpdir }
-
- let(:resolv_fixture) { File.join(FIXTURES_PATH, "etc", "resolv.conf") }
- let(:resolv_flag) { File.join(rootdir, "run", "agama", "manage_resolv") }
- let(:resolv) { File.join(targetdir, "etc", "resolv.conf") }
-
- before do
- stub_const("Agama::Network::RESOLV_FLAG", resolv_flag)
- FileUtils.mkdir_p targetdir
- FileUtils.cp_r(Dir["#{fixtures}/*"], rootdir)
- FileUtils.cp_r(Dir["#{fixtures}/*"], targetdir)
- end
-
- context "when the /etc/resolv.conf exists in the installation destdir" do
- before do
- FileUtils.mkdir_p File.join(targetdir, "etc")
- FileUtils.touch File.join(targetdir, "etc", "resolv.conf")
- end
-
- it "does nothing" do
- expect(FileUtils).to_not receive(:ln_s)
- network.link_resolv
- end
- end
-
- context "when there is no /etc/resolv.conf in the installation destdir" do
- it "symlinks it to /run/NetworkManager/resolv.conf" do
- network.link_resolv
- expect(File.exist?(resolv)).to eql(true)
- expect(File.symlink?(resolv)).to eql(true)
- end
-
- it "creates a flag indicating that the resolv.conf is managed by Agama" do
- network.link_resolv
- expect(File.exist?(resolv_flag)).to eql(true)
- end
- end
- end
-
- describe "#unlink_resolv" do
- let(:rootdir) { Dir.mktmpdir }
-
- let(:fixtures) { File.join(FIXTURES_PATH, "root_dir") }
- let(:resolv_fixture) { File.join(FIXTURES_PATH, "etc", "resolv.conf") }
- let(:resolv_flag) { File.join(rootdir, "run", "agama", "manage_resolv") }
- let(:resolv) { File.join(targetdir, "etc", "resolv.conf") }
-
- before do
- stub_const("Agama::Network::RESOLV_FLAG", resolv_flag)
- stub_const("Agama::Network::RUN_NM_DIR", File.join(rootdir, "run", "NetworkManager"))
- FileUtils.mkdir_p targetdir
- FileUtils.cp_r(Dir["#{fixtures}/*"], rootdir)
- FileUtils.cp_r(Dir["#{fixtures}/*"], targetdir)
- end
-
- context "when the /etc/resolv.conf was marked as managed by Agama" do
- it "removes the /etc/resolv.con symlink from the installation destdir" do
- network.link_resolv
- expect(File.exist?(resolv_flag)).to eql(true)
- expect(File.symlink?(resolv)).to eql(true)
- network.unlink_resolv
- expect(File.exist?(resolv)).to eql(false)
- expect(File.exist?(resolv_flag)).to eql(false)
- end
- end
- end
-end
diff --git a/service/test/agama/proxy_setup_test.rb b/service/test/agama/proxy_setup_test.rb
deleted file mode 100644
index c4246921dc..0000000000
--- a/service/test/agama/proxy_setup_test.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../test_helper"
-require "agama/proxy_setup"
-
-describe Agama::ProxySetup do
- subject(:proxy) { described_class.instance }
- before do
- proxy.proxy = nil
- end
-
- before do
- allow(Yast::Proxy).to receive(:Read)
- allow(Yast::Proxy).to receive(:Write)
- end
-
- describe "#run" do
- let(:file_content) { "proxy=#{proxy_url}" }
- let(:proxy_url) { "https://yast:1234@192.168.122.1:3128" }
-
- context "when some configuration is given through the kernel command line" do
- before do
- allow(proxy).to receive(:proxy_from_cmdline).and_return(URI(proxy_url))
- allow(proxy).to receive(:write)
- end
-
- it "reads the given proxy configuraion" do
- expect(proxy.proxy).to be_nil
- proxy.run
- expect(proxy.proxy).to be_a(URI)
- end
-
- it "writes the proxy configuration to /etc/sysconfig/proxy" do
- allow(proxy).to receive(:write).and_call_original
- expect(Yast::Proxy).to receive(:Write)
- proxy.run
- config = Yast::Proxy.Export
- expect(config).to include("https_proxy" => "https://192.168.122.1:3128",
- "proxy_password" => "1234",
- "proxy_user" => "yast",
- "enabled" => true)
- end
-
- context "when an http url is given" do
- let(:proxy_url) { "http://192.168.122.1:3128" }
-
- it "sets also the https and ftp with the same url" do
- allow(proxy).to receive(:write).and_call_original
- proxy.run
- config = Yast::Proxy.Export
- expect(config).to include("http_proxy" => "http://192.168.122.1:3128",
- "https_proxy" => "http://192.168.122.1:3128",
- "ftp_proxy" => "http://192.168.122.1:3128",
- "enabled" => true)
- end
- end
- end
- end
-
- describe "#propose" do
- let(:config) do
- {
- "enabled" => false
- }
- end
-
- before do
- Yast::Proxy.Import(config)
- allow(Yast::Installation).to receive(:destdir).and_return("/mnt")
- end
-
- context "when the use of proxy is enabled" do
- let(:config) do
- {
- "enabled" => true,
- "http_proxy" => "http://192.168.122.1:3128"
- }
- end
-
- it "adds microos-tools package to the set of resolvables" do
- expect(Yast::PackagesProposal).to receive(:SetResolvables) do |_, _, packages|
- expect(packages).to contain_exactly("microos-tools")
- end
-
- proxy.propose
- end
- end
- end
-
- describe "#install" do
- let(:config) do
- {
- "enabled" => false
- }
- end
-
- before do
- Yast::Proxy.Import(config)
- allow(Yast::Installation).to receive(:destdir).and_return("/mnt")
- end
-
- context "when the use of proxy is disabled" do
- it "does not copy the configuration to the target system" do
- expect(FileUtils).to_not receive(:cp)
- proxy.install
- end
- end
-
- context "when the use of proxy is enabled" do
- let(:config) do
- {
- "enabled" => true,
- "http_proxy" => "http://192.168.122.1:3128"
- }
- end
-
- it "copies the configuration to the target system" do
- expect(FileUtils).to receive(:cp).with(described_class::CONFIG_PATH,
- File.join("/mnt", described_class::CONFIG_PATH))
- proxy.install
- end
- end
- end
-end
diff --git a/service/test/agama/registration_test.rb b/service/test/agama/registration_test.rb
deleted file mode 100644
index 9f376e5554..0000000000
--- a/service/test/agama/registration_test.rb
+++ /dev/null
@@ -1,599 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../test_helper"
-require "agama/answer"
-require "agama/config"
-require "agama/registration"
-require "agama/software/manager"
-require "suse/connect"
-require "yast"
-require "y2packager/new_repository_setup"
-
-Yast.import("Arch")
-
-describe Agama::Registration do
- subject { described_class.new(manager, logger) }
-
- let(:manager) { instance_double(Agama::Software::Manager) }
- let(:product) { Agama::Software::Product.new("test").tap { |p| p.version = "5.0" } }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- before do
- allow(Yast::Arch).to receive(:rpm_arch).and_return("x86_64")
-
- allow(manager).to receive(:product).and_return(product)
- allow(manager).to receive(:add_service)
- allow(manager).to receive(:remove_service)
- allow(manager).to receive(:addon_products)
-
- allow(SUSE::Connect::YaST).to receive(:announce_system).and_return(["test-user", "12345"])
- allow(SUSE::Connect::YaST).to receive(:deactivate_system)
- allow(SUSE::Connect::YaST).to receive(:create_credentials_file)
- allow(SUSE::Connect::YaST).to receive(:activate_product).and_return(service)
- allow(Y2Packager::NewRepositorySetup.instance).to receive(:add_service)
- allow(Agama::CmdlineArgs).to receive(:read).and_return(cmdline_args)
- end
-
- let(:service) { OpenStruct.new(name: "test-service", url: nil) }
- let(:cmdline_args) { Agama::CmdlineArgs.new({}) }
-
- describe "#verify_callback" do
- it "stores to SSL::Error ssl error details" do
- error = double(error: 20, error_string: "Error", current_cert: nil)
-
- subject.send(:verify_callback).call(false, error)
- expect(Agama::SSL::Errors.instance.ssl_error_code).to eq 20
- expect(Agama::SSL::Errors.instance.ssl_error_msg).to eq "Error"
- end
- end
-
- describe "#register" do
- context "if there is no product selected yet" do
- let(:product) { nil }
-
- it "does not try to register" do
- expect(SUSE::Connect::YaST).to_not receive(:announce_system)
-
- subject.register("11112222", email: "test@test.com")
- end
- end
-
- context "if there is a selected product" do
- let(:product) { Agama::Software::Product.new("test").tap { |p| p.version = "5.0" } }
-
- context "and the product is already registered" do
- before do
- subject.register("11112222", email: "test@test.com")
- end
-
- it "does not try to register" do
- expect(SUSE::Connect::YaST).to_not receive(:announce_system)
-
- subject.register("11112222", email: "test@test.com")
- end
- end
-
- context "and the product is not registered yet" do
- it "announces the system" do
- expect(SUSE::Connect::YaST).to receive(:announce_system).with(
- {
- language: anything,
- url: "https://scc.suse.com",
- token: "11112222",
- email: "test@test.com",
- verify_callback: anything
- },
- "test-5-x86_64"
- )
-
- subject.register("11112222", email: "test@test.com")
- end
-
- it "sets the current language in the request" do
- expect(SUSE::Connect::YaST).to receive(:announce_system).with(
- {
- language: "de-de",
- url: anything,
- token: "11112222",
- email: "test@test.com",
- verify_callback: anything
- },
- "test-5-x86_64"
- )
-
- allow(Yast::WFM).to receive(:GetLanguage).and_return("de_DE")
-
- subject.register("11112222", email: "test@test.com")
- end
-
- context "when a registration URL is set through the cmdline" do
- let(:cmdline_args) do
- Agama::CmdlineArgs.new("register_url" => "http://scc.example.net")
- end
-
- it "registers using the given URL" do
- expect(SUSE::Connect::YaST).to receive(:announce_system).with(
- { token: "11112222", email: "test@test.com", url: "http://scc.example.net",
- verify_callback: anything, language: anything },
- "test-5-x86_64"
- )
-
- subject.register("11112222", email: "test@test.com")
- end
- end
-
- it "creates credentials file" do
- expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
- .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
-
- it "activates the selected product" do
- expect(SUSE::Connect::YaST).to receive(:activate_product).with(
- an_object_having_attributes(
- arch: "x86_64", identifier: "test", version: "5.0"
- ), {}, "test@test.com"
- )
-
- subject.register("11112222", email: "test@test.com")
- end
-
- it "adds the service to software manager" do
- expect(Y2Packager::NewRepositorySetup.instance)
- .to receive(:add_service).with("test-service")
-
- subject.register("11112222", email: "test@test.com")
- end
-
- context "if the service requires a creadentials file" do
- let(:service) { OpenStruct.new(name: "test-service", url: "https://credentials/file") }
-
- before do
- allow(subject).to receive(:credentials_from_url)
- .with("https://credentials/file")
- .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/productA")
-
- subject.register("11112222", email: "test@test.com")
- end
- end
-
- context "if the service does not require a creadentials file" do
- let(:service) { OpenStruct.new(name: "test-service", url: nil) }
-
- it "does not create the credentials file" do
- expect(SUSE::Connect::YaST).to receive(:create_credentials_file)
- expect(SUSE::Connect::YaST).to_not receive(:create_credentials_file)
- .with("test-user", "12345", anything)
-
- subject.register("11112222", email: "test@test.com")
- end
- end
-
- context "if the product was correctly registered" do
- before do
- subject.on_change(&callback)
- end
-
- let(:callback) { proc {} }
-
- it "runs the callbacks" do
- expect(callback).to receive(:call)
-
- subject.register("11112222", email: "test@test.com")
- end
-
- it "sets the registration code" do
- subject.register("11112222", email: "test@test.com")
-
- expect(subject.reg_code).to eq("11112222")
- end
-
- it "sets the email" do
- subject.register("11112222", email: "test@test.com")
-
- expect(subject.email).to eq("test@test.com")
- end
- end
-
- context "if the product was not correctly registered" do
- before do
- allow(SUSE::Connect::YaST).to receive(:activate_product).and_raise(Timeout::Error)
- subject.on_change(&callback)
- end
-
- let(:callback) { proc {} }
-
- it "raises an error" do
- expect { subject.register("11112222", email: "test@test.com") }
- .to raise_error(Timeout::Error)
- end
-
- it "sets the registration code" do
- expect { subject.register("11112222", email: "test@test.com") }
- .to raise_error(Timeout::Error)
-
- expect(subject.reg_code).to eq("11112222")
- end
-
- it "sets the email" do
- expect { subject.register("11112222", email: "test@test.com") }
- .to raise_error(Timeout::Error)
-
- expect(subject.email).to eq("test@test.com")
- end
-
- it "runs the callbacks" do
- expect(callback).to receive(:call)
-
- expect { subject.register("11112222", email: "test@test.com") }
- .to raise_error(Timeout::Error)
- end
- end
-
- context "if the registration server has self-signed certificate" do
- let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
-
- let(:certificate) do
- Agama::SSL::Certificate.load(File.read(File.join(FIXTURES_PATH, "test.pem")))
- end
- before do
- Agama::SSL::Errors.instance.ssl_error_code = Agama::SSL::ErrorCodes::SELF_SIGNED_CERT
- Agama::SSL::Errors.instance.ssl_error_msg = "test error"
- Agama::SSL::Errors.instance.ssl_failed_cert = certificate
- # mock reset to avoid deleting of previous setup
- allow(Agama::SSL::Errors.instance).to receive(:reset)
-
- @called = 0
- allow(SUSE::Connect::YaST).to receive(:activate_product) do
- @called += 1
- raise OpenSSL::SSL::SSLError, "test" if @called == 1
-
- service
- end
- end
-
- context "and certificate fingerprint is in storage" do
- before do
- Agama::SSL::Storage.instance.fingerprints
- .replace([certificate.send(:sha256_fingerprint)])
- end
-
- it "tries to import certificate" do
- expect(certificate).to receive(:import)
-
- subject.register("11112222", email: "test@test.com")
- end
-
- after do
- Agama::SSL::Storage.instance.fingerprints.clear
- end
- end
-
- it "opens question" do
- expect(questions_client).to receive(:ask).and_yield(Agama::Answer.new(:Abort))
- expect(Agama::HTTP::Clients::Questions).to receive(:new)
- .and_return(questions_client)
-
- expect { subject.register("11112222", email: "test@test.com") }.to(
- raise_error(OpenSSL::SSL::SSLError)
- )
- end
- end
- end
- end
- end
-
- describe "#register_addon" do
- context "if there is no product selected yet" do
- let(:addon) do
- OpenStruct.new(
- arch: Yast::Arch.rpm_arch,
- identifier: "sle-ha",
- version: "16.0"
- )
- end
-
- let(:code) { "867136984314" }
-
- let(:ha_extension) do
- OpenStruct.new(
- id: 2937,
- identifier: "sle-ha",
- version: "16.0",
- arch: "x86_64",
- isbase: false,
- friendly_name: "SUSE Linux Enterprise High Availability Extension 16.0 x86_64 (BETA)",
- ProductLine: "",
- available: true,
- free: false,
- recommended: false,
- description: "SUSE Linux High Availability Extension provides...",
- former_identifier: "sle-ha",
- product_type: "extension",
- shortname: "SLEHA16",
- name: "SUSE Linux Enterprise High Availability Extension",
- release_stage: "beta"
- )
- end
-
- it "registers addon" do
- expect(SUSE::Connect::YaST).to receive(:activate_product).with(
- addon, { token: code }, anything
- )
-
- subject.register_addon(addon.identifier, addon.version, code)
- end
-
- it "registers addon only once" do
- expect(SUSE::Connect::YaST).to receive(:activate_product).with(
- addon, { token: code }, anything
- ).once
-
- subject.register_addon(addon.identifier, addon.version, code)
- subject.register_addon(addon.identifier, addon.version, code)
- end
-
- context "the requested addon version is not specified" do
- it "finds the version automatically" do
- expect(SUSE::Connect::YaST).to receive(:activate_product).with(
- addon, { token: code }, anything
- )
-
- expect(SUSE::Connect::YaST).to receive(:show_product).and_return(
- OpenStruct.new(
- extensions: [ha_extension]
- )
- )
-
- subject.register_addon(addon.identifier, "", code)
- end
-
- it "raises exception when the requested addon is not found" do
- expect(SUSE::Connect::YaST).to receive(:show_product).and_return(
- OpenStruct.new(extensions: [])
- )
-
- expect do
- subject.register_addon(addon.identifier, "", code)
- end.to raise_error(Agama::Errors::Registration::ExtensionNotFound)
- end
-
- it "raises exception when multiple addon versions are found" do
- ha1 = ha_extension
- ha2 = ha1.dup
- ha2.version = "42"
-
- expect(SUSE::Connect::YaST).to receive(:show_product).and_return(
- OpenStruct.new(extensions: [ha1, ha2])
- )
-
- expect do
- subject.register_addon(addon.identifier, "", code)
- end.to raise_error(Agama::Errors::Registration::MultipleExtensionsFound)
- end
- end
- end
- end
-
- describe "#deregister" do
- before do
- allow(FileUtils).to receive(:rm)
- end
-
- context "if there is no product selected yet" do
- let(:product) { nil }
-
- it "does not try to deregister" do
- expect(SUSE::Connect::YaST).to_not receive(:deactivate_system)
-
- subject.deregister
- end
- end
-
- context "if there is a selected product" do
- let(:product) { Agama::Software::Product.new("test").tap { |p| p.version = "5.0" } }
-
- context "and the product is not registered yet" do
- it "does not try to deregister" do
- expect(SUSE::Connect::YaST).to_not receive(:deactivate_system)
-
- subject.deregister
- end
- end
-
- context "and the product is registered" do
- before do
- allow(subject).to receive(:credentials_from_url)
- allow(subject).to receive(:credentials_from_url)
- .with("https://credentials/file").and_return("credentials")
-
- subject.register("11112222", email: "test@test.com")
- end
-
- it "deletes the service from the software config" do
- expect(manager).to receive(:remove_service).with(service)
-
- subject.deregister
- end
-
- it "deactivates the system" do
- expect(SUSE::Connect::YaST).to receive(:deactivate_system).with(
- {
- url: anything,
- token: "11112222",
- email: "test@test.com",
- verify_callback: anything,
- language: anything
- }
- )
-
- subject.deregister
- end
-
- it "removes the credentials file" do
- expect(FileUtils).to receive(:rm).with(/SCCcredentials/)
-
- subject.deregister
- end
-
- context "if the service has a credentials files" do
- let(:service) { OpenStruct.new(name: "test-service", url: "https://credentials/file") }
-
- it "removes the credentials file" do
- expect(FileUtils).to receive(:rm)
- expect(FileUtils).to receive(:rm).with(/\/credentials$/)
-
- subject.deregister
- end
- end
-
- context "if the product has no credentials file" do
- let(:service) { OpenStruct.new(name: "test-service", url: nil) }
-
- it "does not try to remove the credentials file" do
- expect(FileUtils).to_not receive(:rm).with(/\/credentials$/)
-
- subject.deregister
- end
- end
-
- context "if the product was correctly deregistered" do
- before do
- subject.on_change(&callback)
- end
-
- let(:callback) { proc {} }
-
- it "runs the callbacks" do
- expect(callback).to receive(:call)
-
- subject.deregister
- end
-
- it "removes the registration code" do
- subject.deregister
-
- expect(subject.reg_code).to be_nil
- end
-
- it "removes the email" do
- subject.deregister
-
- expect(subject.email).to be_nil
- end
- end
-
- context "if the product was not correctly deregistered" do
- before do
- allow(SUSE::Connect::YaST).to receive(:deactivate_system).and_raise(Timeout::Error)
- subject.on_change(&callback)
- end
-
- let(:callback) { proc {} }
-
- it "raises an error" do
- expect { subject.deregister }.to raise_error(Timeout::Error)
- end
-
- it "does not run the callbacks" do
- expect(callback).to_not receive(:call)
-
- expect { subject.deregister }.to raise_error(Timeout::Error)
- end
-
- it "does not remove the registration code" do
- expect { subject.deregister }.to raise_error(Timeout::Error)
-
- expect(subject.reg_code).to eq("11112222")
- end
-
- it "does not remove the email" do
- expect { subject.deregister }.to raise_error(Timeout::Error)
-
- expect(subject.email).to eq("test@test.com")
- end
- end
- end
- end
- end
-
- describe "#finish" do
- context "system is not registered" do
- before do
- subject.instance_variable_set(:@registered, false)
- end
-
- it "do nothing" do
- expect(::FileUtils).to_not receive(:cp)
-
- subject.finish
- end
- end
-
- context "system is registered" do
- before do
- subject.instance_variable_set(:@registered, true)
- subject.instance_variable_set(:@reg_code, "test")
- subject.instance_variable_set(:@credentials_files, ["test"])
- Yast::Installation.destdir = "/mnt"
- allow(::FileUtils).to receive(:cp)
- end
-
- it "copies global credentials file" do
- expect(::FileUtils).to receive(:cp).with("/etc/zypp/credentials.d/SCCcredentials",
- "/mnt/etc/zypp/credentials.d/SCCcredentials")
-
- subject.finish
- end
-
- it "copies product credentials file" do
- expect(::FileUtils).to receive(:cp).with("/run/agama/zypp/etc/zypp/credentials.d/test",
- "/mnt/etc/zypp/credentials.d/test")
-
- subject.finish
- end
-
- context "and a registration URL was given" do
- before do
- allow(subject).to receive(:registration_url).and_return("http://reg-server.lan")
- end
-
- it "generates and copies the SUSEConnect configuration" do
- expect(::FileUtils).to receive(:cp).with("/etc/SUSEConnect", "/mnt/etc/SUSEConnect")
- expect(SUSE::Connect::YaST).to receive(:write_config).with("url" => "http://reg-server.lan")
-
- subject.finish
- end
- end
- end
- end
-end
diff --git a/service/test/agama/security_test.rb b/service/test/agama/security_test.rb
deleted file mode 100644
index 72fd9e6c58..0000000000
--- a/service/test/agama/security_test.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../test_helper"
-require "agama/security"
-
-describe Agama::Security do
- subject(:security) { described_class.new(logger, config) }
-
- let(:logger) { Logger.new($stdout) }
-
- let(:config_path) do
- File.join(FIXTURES_PATH, "root_dir", "etc", "agama.yaml")
- end
-
- let(:config) do
- Agama::Config.new(YAML.safe_load(File.read(config_path)))
- end
-
- let(:selected) { nil }
-
- let(:lsm_config) do
- instance_double(Y2Security::LSM::Config, select: nil, selected: selected)
- end
-
- let(:apparmor) do
- instance_double(Y2Security::LSM::AppArmor, id: :apparmor)
- end
-
- let(:selinux) do
- instance_double(Y2Security::LSM::Selinux, id: :selinux)
- end
-
- let(:proposal) do
- {
- "size" => "0 B",
- "patterns" => {
- "documentation" => 1,
- "enhanced_base" => 1,
- "sw_management" => 1,
- "yast2_basis" => 1,
- "apparmor" => 0,
- "minimal_base" => 1,
- "base" => 1,
- "x86_64_v3" => 1
- }
- }
- end
-
- let(:software_client) do
- instance_double(Agama::HTTP::Clients::Software, proposal: proposal, add_patterns: nil)
- end
-
- before do
- allow(Y2Security::LSM::Config).to receive(:instance).and_return(lsm_config)
- allow(security).to receive(:software_client).and_return(software_client)
- allow(Yast::Bootloader).to receive(:modify_kernel_params)
- allow(lsm_config.selected).to receive(:reset_kernel_params)
- allow(lsm_config.selected).to receive(:kernel_params)
- end
-
- describe "#write" do
- let(:selected) { apparmor }
-
- context "when the software proposal patterns includes the LSM patterns" do
- it "saves kernel parameters for the LSM configuration" do
- expect(Yast::Bootloader).to receive(:modify_kernel_params)
- security.write
- end
- end
-
- context "when the software proposal patterns does not include the LSM patterns" do
- let(:selected) { apparmor }
-
- let(:proposal) do
- {
- "size" => "0 B",
- "patterns" => {
- "documentation" => 1,
- "enhanced_base" => 1,
- "sw_management" => 1,
- "yast2_basis" => 1,
- "minimal_base" => 1,
- "base" => 1,
- "x86_64_v3" => 1
- }
- }
- end
-
- it "fallback to the first LSM which patterns are included by the software proposal" do
- expect(lsm_config).to receive(:select).with("none")
- expect(Yast::Bootloader).to receive(:modify_kernel_params)
- security.write
- end
-
- it "resets bootloader params for previous selection" do
- expect(lsm_config.selected).to receive(:reset_kernel_params)
- security.write
- end
- end
- end
-end
diff --git a/service/test/agama/software/callbacks/digest_test.rb b/service/test/agama/software/callbacks/digest_test.rb
deleted file mode 100644
index bd46bcb86c..0000000000
--- a/service/test/agama/software/callbacks/digest_test.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/software/callbacks/digest"
-require "agama/http/clients"
-require "agama/answer"
-require "agama/question"
-
-describe Agama::Software::Callbacks::Digest do
- subject { described_class.new(questions_client, logger) }
-
- let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
- let(:question) { instance_double(Agama::Question, answer: answer) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- before do
- allow(questions_client).to receive(:ask).and_yield(answer)
- end
-
- describe "#accept_file_without_checksum" do
- let(:answer) { Agama::Answer.new(subject.yes_label) }
-
- it "registers a question informing of the error" do
- expect(questions_client).to receive(:ask) do |q|
- expect(q.text).to match("No checksum for the file repomd.xml")
- end
- subject.accept_file_without_checksum("repomd.xml")
- end
-
- context "when the user answers :Yes" do
- let(:answer) { Agama::Answer.new(subject.yes_label) }
-
- it "returns true" do
- expect(subject.accept_file_without_checksum("repomd.xml")).to eq(true)
- end
- end
-
- context "when the user answers :No" do
- let(:answer) { Agama::Answer.new(subject.no_label) }
-
- it "returns false" do
- expect(subject.accept_file_without_checksum("repomd.xml")).to eq(false)
- end
- end
- end
-
- describe "#accept_unknown_digest" do
- let(:answer) { Agama::Answer.new(subject.yes_label) }
-
- it "registers a question informing of the error" do
- expect(questions_client).to receive(:ask) do |q|
- expect(q.text).to include("The checksum of the file repomd.xml is \"123456\"")
- end
- subject.accept_unknown_digest("repomd.xml", "123456")
- end
-
- context "when the user answers :Yes" do
- let(:answer) { Agama::Answer.new(subject.yes_label) }
-
- it "returns true" do
- expect(subject.accept_unknown_digest("repomd.xml", "123456")).to eq(true)
- end
- end
-
- context "when the user answers :No" do
- let(:answer) { Agama::Answer.new(subject.no_label) }
-
- it "returns false" do
- expect(subject.accept_unknown_digest("repomd.xml", "123456")).to eq(false)
- end
- end
- end
-
- describe "#accept_wrong_digest" do
- let(:answer) { Agama::Answer.new(subject.yes_label) }
-
- it "registers a question informing of the error" do
- expect(questions_client).to receive(:ask) do |q|
- expect(q.text).to match(
- /The expected checksum of file repomd.xml is "654321".*expected.*"123456"/
- )
- end
- subject.accept_wrong_digest("repomd.xml", "123456", "654321")
- end
-
- context "when the user answers :Yes" do
- let(:answer) { Agama::Answer.new(subject.yes_label) }
-
- it "returns true" do
- expect(subject.accept_wrong_digest("repomd.xml", "123456", "654321")).to eq(true)
- end
- end
-
- context "when the user answers :No" do
- let(:answer) { Agama::Answer.new(subject.no_label) }
-
- it "returns false" do
- expect(subject.accept_wrong_digest("repomd.xml", "123456", "654321")).to eq(false)
- end
- end
- end
-end
diff --git a/service/test/agama/software/callbacks/media_test.rb b/service/test/agama/software/callbacks/media_test.rb
deleted file mode 100644
index b77056f806..0000000000
--- a/service/test/agama/software/callbacks/media_test.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/software/callbacks/media"
-require "agama/http/clients"
-require "agama/question"
-require "agama/answer"
-
-describe Agama::Software::Callbacks::Media do
- subject { described_class.new(questions_client, logger) }
-
- let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
- let(:question) { instance_double(Agama::Question, answer: answer) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- describe "#media_changed" do
- before do
- allow(questions_client).to receive(:ask).and_yield(answer)
-
- # mock sleep() to speed up test
- allow(subject).to receive(:sleep)
- end
-
- context "when the user answers :Retry" do
- let(:answer) { Agama::Answer.new(subject.retry_label) }
-
- it "returns ''" do
- ret = subject.media_change(
- "NOT_FOUND", "Package not found", "", "", 0, "", 0, "", true, [], 0
- )
- expect(ret).to eq("")
- end
- end
-
- context "when the user answers :Skip" do
- let(:answer) { Agama::Answer.new(subject.continue_label.to_sym) }
-
- it "returns 'S'" do
- ret = subject.media_change(
- "NOT_FOUND", "Package not found", "", "", 0, "", 0, "", true, [], 0
- )
- expect(ret).to eq("S")
- end
- end
-
- context "when a timeout error occurs" do
- # actually not used, just required by the global "before"
- let(:answer) { nil }
-
- it "returns '' without asking" do
- expect(questions_client).to_not receive(:ask)
- ret = subject.media_change(
- "IO_SOFT", "Timeout", "", "", 0, "", 0, "", true, [], 0
- )
- expect(ret).to eq("")
- end
- end
- end
-end
diff --git a/service/test/agama/software/callbacks/pkg_gpg_check_test.rb b/service/test/agama/software/callbacks/pkg_gpg_check_test.rb
deleted file mode 100644
index 63ef576868..0000000000
--- a/service/test/agama/software/callbacks/pkg_gpg_check_test.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-
-require "agama/software/manager"
-require "agama/software/callbacks/pkg_gpg_check"
-
-describe Agama::Software::Callbacks::PkgGpgCheck do
- subject { described_class.new(questions_client, logger) }
-
- let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
- let(:logger) { Logger.new($stdout, level: :error) }
-
- describe "#pkg_gpg_check" do
- # libzypp GPG check result
- let(:check_result) do
- {
- "CheckPackageResult" => result_code,
- "Package" => "foo",
- "RepoMediaUrl" => repo_url
- }
- end
- let(:repo_url) { "http://example.com" }
- let(:boot_params) { {} }
-
- before do
- # set the boot parameters
- allow(Agama::CmdlineArgs).to receive(:read).and_return(Agama::CmdlineArgs.new(boot_params))
- end
-
- context "when GPG check succeeds" do
- let(:result_code) { Agama::Software::Callbacks::PkgGpgCheck::CHK_OK }
-
- it "requests no action" do
- expect(subject.pkg_gpg_check(check_result)).to eq("")
- end
- end
-
- context "when the used GPG key is unknown" do
- let(:result_code) { Agama::Software::Callbacks::PkgGpgCheck::CHK_NOKEY }
-
- context "when no boot parameter is used" do
- context "the package comes from a regular repository" do
- it "requests no action" do
- expect(subject.pkg_gpg_check(check_result)).to eq("")
- end
- end
-
- context "the package comes from the DUD repository" do
- let(:repo_url) { Agama::Software::Manager.dud_repository_url }
-
- it "requests no action" do
- expect(subject.pkg_gpg_check(check_result)).to eq("")
- end
- end
- end
-
- context "when 'inst.dud_packages.gpg=0' boot parameter is used" do
- let(:boot_params) do
- {
- # emulate using the inst.dud_packages.gpg=0 boot option
- "dud_packages" => {
- "gpg" => "0"
- }
- }
- end
-
- context "the package comes from a regular repository" do
- # errors for regular packages are not ignored
- it "requests no action" do
- expect(subject.pkg_gpg_check(check_result)).to eq("")
- end
- end
-
- context "the package comes from the DUD repository" do
- let(:repo_url) { Agama::Software::Manager.dud_repository_url }
-
- # only errors for the DUD packages are ignored
- it "requests to ignore the GPG signature problem" do
- expect(subject.pkg_gpg_check(check_result)).to eq("I")
- end
- end
- end
- end
- end
-end
diff --git a/service/test/agama/software/callbacks/provide_test.rb b/service/test/agama/software/callbacks/provide_test.rb
deleted file mode 100644
index a4070b2940..0000000000
--- a/service/test/agama/software/callbacks/provide_test.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/software/callbacks/provide"
-require "agama/http/clients/questions"
-require "agama/question"
-require "agama/answer"
-
-describe Agama::Software::Callbacks::Provide do
- subject { described_class.new(questions_client, logger) }
-
- let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
- let(:question) { instance_double(Agama::Question, answer: answer) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- let(:answer) { Agama::Answer.new(subject.retry_label) }
-
- describe "#done_provide" do
- before do
- allow(questions_client).to receive(:ask).and_yield(answer)
- end
-
- let(:question_client) { instance_double(Agama::DBus::Clients::Question) }
-
- context "when the file is not found" do
- it "does not register a question" do
- expect(questions_client).to_not receive(:ask)
- subject.done_provide(1, "Some dummy reason", "dummy-package")
- end
- end
-
- context "when the there is an I/O error" do
- it "registers a question informing of the error" do
- reason = "could not be downloaded"
- expect(questions_client).to receive(:ask) do |q|
- expect(q.text).to include(reason)
- end
- subject.done_provide(2, reason, "dummy-package")
- end
- end
-
- context "when the there is an I/O error" do
- it "registers a question informing of the error" do
- reason = "integrity check has failed"
- expect(questions_client).to receive(:ask) do |q|
- expect(q.text).to include(reason)
- end
- subject.done_provide(3, "integrity check has failed", "dummy-package")
- end
- end
-
- context "when the user answers :Retry" do
- it "returns 'R'" do
- ret = subject.done_provide(
- 2, "Some dummy reason", "dummy-package"
- )
- expect(ret).to eq("R")
- end
- end
-
- context "when the user answers :Skip" do
- let(:answer) { Agama::Answer.new(subject.continue_label) }
-
- it "returns 'I'" do
- ret = subject.done_provide(
- 2, "Some dummy reason", "dummy-package"
- )
- expect(ret).to eq("I")
- end
- end
- end
-end
diff --git a/service/test/agama/software/callbacks/script_test.rb b/service/test/agama/software/callbacks/script_test.rb
deleted file mode 100644
index 34251131e9..0000000000
--- a/service/test/agama/software/callbacks/script_test.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/software/callbacks/script"
-require "agama/http/clients/questions"
-require "agama/question"
-require "agama/answer"
-
-describe Agama::Software::Callbacks::Script do
- subject { described_class.new(questions_client, logger) }
-
- let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
- let(:question) { instance_double(Agama::Question, answer: answer) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- let(:answer) { Agama::Answer.new(subject.retry_label) }
-
- let(:description) { "Some description" }
-
- describe "#script_problem" do
- before do
- allow(questions_client).to receive(:ask).and_yield(answer)
- end
-
- it "registers a question with the details" do
- expect(questions_client).to receive(:ask) do |q|
- expect(q.text).to include("running a package script")
- expect(q.data).to include(
- "details" => description
- )
- end
- subject.script_problem(description)
- end
-
- context "when the user asks to retry" do
- let(:answer) { Agama::Answer.new(subject.retry_label) }
-
- it "returns 'R'" do
- ret = subject.script_problem(description)
- expect(ret).to eq("R")
- end
- end
-
- context "when the user asks to continue" do
- let(:answer) { Agama::Answer.new(subject.continue_label) }
-
- it "returns 'I'" do
- ret = subject.script_problem(description)
- expect(ret).to eq("I")
- end
- end
- end
-end
diff --git a/service/test/agama/software/callbacks/signature_test.rb b/service/test/agama/software/callbacks/signature_test.rb
deleted file mode 100644
index e940cd5b7f..0000000000
--- a/service/test/agama/software/callbacks/signature_test.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../../test_helper"
-require "agama/software/callbacks/signature"
-require "agama/http/clients"
-require "agama/question"
-require "agama/answer"
-
-describe Agama::Software::Callbacks::Signature do
- before do
- allow(questions_client).to receive(:ask).and_yield(answer)
- end
-
- let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
- let(:question) { instance_double(Agama::Question, answer: answer) }
-
- let(:answer) { nil }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
-
- subject { described_class.new(questions_client, logger) }
-
- describe "#accept_unsigned_file" do
- context "when the user answers :Yes" do
- let(:answer) { Agama::Answer.new(:Yes) }
-
- it "returns true" do
- expect(subject.accept_unsigned_file("repomd.xml", -1)).to eq(true)
- end
- end
-
- context "when the user answers :No" do
- let(:answer) { Agama::Answer.new(:No) }
-
- it "returns false" do
- expect(subject.accept_unsigned_file("repomd.xml", -1)).to eq(false)
- end
- end
-
- context "when the repo information is available" do
- before do
- allow(Yast::Pkg).to receive(:SourceGeneralData).with(1)
- .and_return("name" => "OSS", "url" => "http://localhost/repo")
- end
-
- it "includes the name and the URL in the question" do
- expect(questions_client).to receive(:ask) do |question|
- expect(question.text).to include("repomd.xml from http://localhost/repo")
- end
-
- expect(subject.accept_unsigned_file("repomd.xml", 1))
- end
- end
-
- context "when the repo information is not available" do
- before do
- allow(Yast::Pkg).to receive(:SourceGeneralData).with(1).and_return(nil)
- end
-
- it "includes a generic message containing the filename" do
- expect(questions_client).to receive(:ask) do |question|
- expect(question.text).to include("repomd.xml")
- end
-
- expect(subject.accept_unsigned_file("repomd.xml", 1))
- end
- end
- end
-
- describe "import_gpg_key" do
- let(:answer) { Agama::Answer.new("Trust") }
-
- let(:key) do
- {
- "id" => "0123456789ABCDEF",
- "fingerprint" => "2E2EA448C9DDD7A91BC28441AEE969E90F05DB9D",
- "name" => "YaST:Head:Agama"
- }
- end
-
- context "when the user answers :Trust" do
- let(:answer) { Agama::Answer.new(:Trust) }
-
- it "returns true" do
- expect(subject.import_gpg_key(key, 1)).to eq(true)
- end
- end
-
- context "when the user answers :Skip" do
- let(:answer) { Agama::Answer.new(:Skip) }
-
- it "returns false" do
- expect(subject.import_gpg_key(key, 1)).to eq(false)
- end
- end
-
- it "includes a message" do
- expect(questions_client).to receive(:ask) do |question|
- expect(question.text).to include(key["id"])
- expect(question.text).to include(key["name"])
- expect(question.text).to include("2E2E A448 C9DD")
- end
- subject.import_gpg_key(key, 1)
- end
- end
-
- describe "#accept_unknown_gpg_key" do
- context "when the user answers :Yes" do
- let(:answer) { Agama::Answer.new(:Yes) }
-
- it "returns true" do
- expect(subject.accept_unknown_gpg_key("repomd.xml", "KEYID", 1)).to eq(true)
- end
- end
-
- context "when the user answers :No" do
- let(:answer) { Agama::Answer.new(:No) }
-
- it "returns false" do
- expect(subject.accept_unknown_gpg_key("repomd.xml", "KEYID", 1)).to eq(false)
- end
- end
-
- context "when the repo information is available" do
- before do
- allow(Yast::Pkg).to receive(:SourceGeneralData).with(1)
- .and_return("name" => "OSS", "url" => "http://localhost/repo")
- end
-
- it "includes the name and the URL in the question" do
- expect(questions_client).to receive(:ask) do |question|
- expect(question.text).to include("repomd.xml from http://localhost/repo")
- end
-
- expect(subject.accept_unknown_gpg_key("repomd.xml", "KEYID", 1))
- end
- end
-
- context "when the repo information is not available" do
- before do
- allow(Yast::Pkg).to receive(:SourceGeneralData).with(1).and_return(nil)
- end
-
- it "includes a generic message containing the filename" do
- expect(questions_client).to receive(:ask) do |question|
- expect(question.text).to include("repomd.xml")
- end
-
- expect(subject.accept_unknown_gpg_key("repomd.xml", "KEYID", 1))
- end
- end
- end
-
- describe "accept_verification_failed" do
- let(:answer) { Agama::Answer.new(:Trust) }
-
- let(:key) do
- {
- "id" => "0123456789ABCDEF",
- "fingerprint" => "2E2EA448C9DDD7A91BC28441AEE969E90F05DB9D",
- "name" => "YaST:Head:Agama"
- }
- end
-
- let(:filename) { "repomd.xml" }
-
- context "when the user answers :Yes" do
- let(:answer) { Agama::Answer.new(:Yes) }
-
- it "returns true" do
- expect(subject.accept_verification_failed(filename, key, 1)).to eq(true)
- end
- end
-
- context "when the user answers :No" do
- let(:answer) { Agama::Answer.new(:No) }
-
- it "returns false" do
- expect(subject.accept_verification_failed(filename, key, 1)).to eq(false)
- end
- end
-
- it "includes a message" do
- expect(questions_client).to receive(:ask) do |question|
- expect(question.text).to include(key["id"])
- expect(question.text).to include(key["name"])
- end
- subject.accept_verification_failed(filename, key, 1)
- end
- end
-end
diff --git a/service/test/agama/software/manager_test.rb b/service/test/agama/software/manager_test.rb
deleted file mode 100644
index 871f8883c4..0000000000
--- a/service/test/agama/software/manager_test.rb
+++ /dev/null
@@ -1,664 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require_relative "../with_issues_examples"
-require_relative "../with_progress_examples"
-require_relative File.join(SRC_PATH, "agama/dbus/y2dir/software/modules/PackageCallbacks.rb")
-require "agama/config"
-require "agama/issue"
-require "agama/registration"
-require "agama/software/manager"
-require "agama/software/product"
-require "agama/software/proposal"
-require "agama/http/clients"
-
-describe Agama::Software::Manager do
- subject { described_class.new(config, logger) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
- let(:base_url) { "" }
- let(:destdir) { "/mnt" }
- let(:gpg_keys) { [] }
-
- let(:repositories) do
- instance_double(
- Agama::Software::RepositoriesManager,
- add: nil,
- load: nil,
- delete_all: nil,
- empty?: true,
- enabled: enabled_repos,
- disabled: disabled_repos
- )
- end
- let(:products) { [] }
-
- let(:proposal) do
- instance_double(
- Agama::Software::Proposal,
- :base_product= => nil,
- calculate: nil,
- :languages= => nil,
- set_resolvables: nil,
- packages_count: "500 MB",
- issues: proposal_issues,
- on_issues_change: nil,
- only_required: nil
- )
- end
-
- let(:enabled_repos) { [] }
- let(:disabled_repos) { [] }
- let(:proposal_issues) { [] }
-
- let(:config_path) do
- File.join(FIXTURES_PATH, "root_dir", "etc", "agama.yaml")
- end
-
- let(:config) do
- Agama::Config.new(YAML.safe_load(File.read(config_path)))
- end
-
- let(:questions_client) do
- instance_double(Agama::HTTP::Clients::Questions)
- end
-
- let(:target_dir) { Dir.mktmpdir }
-
- before do
- stub_const("Agama::Software::Manager::TARGET_DIR", target_dir)
- allow(Yast::Pkg).to receive(:TargetInitialize)
- allow(Yast::Pkg).to receive(:TargetFinish)
- allow(Yast::Pkg).to receive(:TargetLoad)
- allow(Yast::Pkg).to receive(:SourceSaveAll).and_return(true)
- allow(Yast::Pkg).to receive(:SourceDelete)
- allow(Yast::Pkg).to receive(:ImportGPGKey)
- allow(Yast::Pkg).to receive(:ServiceAdd).and_return(true)
- allow(Yast::Pkg).to receive(:ServiceSet).and_return(true)
- allow(Yast::Pkg).to receive(:ServiceSave).and_return(true)
- allow(Yast::Pkg).to receive(:ServiceForceRefresh).and_return(true)
- # allow glob to work for other calls
- allow(Dir).to receive(:glob).and_call_original
- allow(Dir).to receive(:glob).with(/keys/).and_return(gpg_keys)
- allow(Yast::Packages).to receive(:Proposal).and_return({})
- allow(Yast::InstURL).to receive(:installInf2Url).with("")
- .and_return(base_url)
- allow(Yast::Pkg).to receive(:SourceCreate)
- allow(Yast::Installation).to receive(:destdir).and_return(destdir)
- allow(Agama::HTTP::Clients::Questions).to receive(:new).and_return(questions_client)
- allow(Agama::Software::RepositoriesManager).to receive(:instance).and_return(repositories)
- allow(Agama::Software::Proposal).to receive(:new).and_return(proposal)
- allow(Agama::ProductReader).to receive(:new).and_call_original
- allow(FileUtils).to receive(:mkdir_p)
- allow(FileUtils).to receive(:rm_rf)
- allow(FileUtils).to receive(:cp_r)
- allow(File).to receive(:exist?).and_call_original
- end
-
- after do
- FileUtils.rm_r(target_dir)
- end
-
- describe "#new" do
- before do
- allow_any_instance_of(Agama::Software::ProductBuilder)
- .to receive(:build).and_return(products)
- end
-
- context "if there are several products" do
- let(:products) do
- [Agama::Software::Product.new("test1"), Agama::Software::Product.new("test2")]
- end
-
- it "does not select a product by default" do
- manager = described_class.new(config, logger)
-
- expect(manager.product).to be_nil
- end
- end
-
- context "if there is only a product" do
- let(:products) { [product] }
-
- let(:product) { Agama::Software::Product.new("test1") }
-
- it "selects the product" do
- manager = described_class.new(config, logger)
-
- expect(manager.product.id).to eq("test1")
- end
- end
-
- context "when GPG keys are available at /" do
- let(:gpg_keys) { ["/usr/lib/gnupg/keys/gpg-key.asc"] }
-
- it "imports the GPG keys" do
- expect(Yast::Pkg).to receive(:ImportGPGKey).with(gpg_keys.first, true)
- described_class.new(config, logger)
- end
- end
-
- it "initializes the package system" do
- expect(Yast::Pkg).to receive(:TargetInitialize)
- described_class.new(config, logger)
- end
- end
-
- shared_examples "software issues" do |tested_method|
- before do
- allow(subject.registration).to receive(:reg_code).and_return(reg_code)
- end
-
- let(:reg_code) { "123XX432" }
- let(:proposal_issues) { [Agama::Issue.new("Proposal issue")] }
-
- context "if there are disabled repositories" do
- let(:disabled_repos) do
- [
- instance_double(Agama::Software::Repository, name: "Repo #1"),
- instance_double(Agama::Software::Repository, name: "Repo #2")
- ]
- end
-
- it "adds an issue for each disabled repository" do
- subject.public_send(tested_method)
-
- expect(subject.issues).to include(
- an_object_having_attributes(
- description: /Could not read repository "Repo #1"/i
- ),
- an_object_having_attributes(
- description: /Could not read repository "Repo #2"/i
- )
- )
- end
- end
-
- context "if there is any enabled repository" do
- let(:enabled_repos) { [instance_double(Agama::Software::Repository, name: "Repo #1")] }
-
- it "adds the proposal issues" do
- subject.public_send(tested_method)
-
- expect(subject.issues).to include(an_object_having_attributes(
- description: /proposal issue/i
- ))
- end
- end
-
- context "if there is no enabled repository" do
- let(:enabled_repos) { [] }
-
- it "does not add the proposal issues" do
- subject.public_send(tested_method)
-
- expect(subject.issues).to_not include(an_object_having_attributes(
- description: /proposal issue/i
- ))
- end
- end
- end
-
- describe "#probe" do
- before do
- subject.select_product("Tumbleweed")
- allow(subject).to receive(:list_disks).and_return({})
- end
-
- it "creates a packages proposal" do
- expect(proposal).to receive(:calculate)
- subject.probe
- end
-
- it "registers the repository from config" do
- expect(repositories).to receive(:add).with(/tumbleweed/)
- expect(repositories).to receive(:load)
- subject.probe
- end
-
- it "uses the offline medium if available" do
- device = "/dev/sr1"
- expect(subject).to receive(:list_disks).and_return({
- "blockdevices" => [
- {
- "kname" => device,
- "label" => "openSUSE-Tumbleweed-DVD-x86_64"
- }
- ]
- })
-
- expect(repositories).to receive(:add).with("hd:/?device=" + device)
- subject.probe
- end
-
- include_examples "software issues", "probe"
- end
-
- describe "#products" do
- it "returns the list of known products" do
- products = subject.products
- expect(products).to all(be_a(Agama::Software::Product))
- expect(products).to_not be_empty
- end
- end
-
- describe "#patterns" do
- it "returns only the specified patterns" do
- allow(Yast::Pkg).to receive(:SourceGetCurrent).and_return([0])
- allow(Yast::Pkg).to receive(:SourceGeneralData).and_return({ "service" => "" })
- expect(Y2Packager::Resolvable).to receive(:find).and_return(
- [
- double(
- arch: "x86_64",
- category: "Base Technologies",
- description: "YaST tools for installing your system.",
- icon: "./yast",
- kind: :pattern,
- name: "yast2_install_wf",
- order: "1240",
- source: 0,
- summary: "YaST Installation Packages",
- user_visible: false,
- version: "20220411-1.4"
- ),
- double(
- arch: "x86_64",
- category: "Base Technologies",
- description: "YaST tools for basic system administration.",
- icon: "./yast",
- kind: :pattern,
- name: "yast2_basis",
- order: "1220",
- source: 0,
- summary: "YaST Base Utilities",
- user_visible: true,
- version: "20220411-1.4"
- ),
- double(
- arch: "noarch",
- category: "Graphical Environments",
- description:
- "Packages providing the Plasma desktop environment and " \
- "applications from KDE.",
- icon: "./pattern-kde",
- kind: :pattern,
- name: "kde",
- order: "1110",
- source: 0,
- summary: "KDE Applications and Plasma 5 Desktop",
- user_visible: true,
- version: "20230801-1.1"
- )
- ]
- )
-
- kde = Agama::Software::UserPattern.new("kde", false)
- allow(subject.product).to receive(:user_patterns).and_return([kde])
- patterns = subject.patterns(true)
-
- expect(patterns).to contain_exactly(
- an_object_having_attributes(name: "kde")
- )
- end
- end
-
- describe "#propose" do
- before do
- subject.select_product("Tumbleweed")
- end
-
- it "creates a new proposal for the selected product" do
- expect(proposal).to receive(:languages=).with(["en_US"])
- expect(proposal).to receive(:base_product=).with("openSUSE")
- expect(proposal).to receive(:calculate)
- subject.propose
- end
-
- include_examples "software issues", "propose"
-
- it "adds the patterns and packages to install depending on the system architecture" do
- expect(proposal).to receive(:set_resolvables)
- .with("agama", :pattern, ["enhanced_base"])
- expect(proposal).to receive(:set_resolvables)
- .with("agama", :pattern, [], { optional: true })
- expect(proposal).to receive(:set_resolvables)
- .with("agama", :package, [
- "NetworkManager", "kernel-default",
- "openSUSE-repos-Tumbleweed", "sudo-policy-wheel-auth-self"
- ])
- expect(proposal).to receive(:set_resolvables)
- .with("agama", :package, [], { optional: true })
- subject.propose
- end
- end
-
- describe "#install" do
- let(:commit_result) { [250, [], [], [], []] }
-
- before do
- allow(Yast::Pkg).to receive(:Commit).and_return(commit_result)
- end
-
- it "installs the packages" do
- expect(Yast::Pkg).to receive(:Commit).with({})
- .and_return(commit_result)
- subject.install
- end
-
- it "sets up the package callbacks" do
- expect(Agama::Software::Callbacks::Progress).to receive(:setup)
- subject.install
- end
-
- context "when packages installation fails" do
- let(:commit_result) { nil }
-
- it "raises an exception" do
- expect { subject.install }.to raise_error(RuntimeError)
- end
- end
-
- it "moves the packaging target to /mnt" do
- expect(Yast::Pkg).to receive(:TargetFinish)
- expect(Yast::Pkg).to receive(:TargetInitialize).with(destdir)
- expect(Yast::Pkg).to receive(:TargetLoad)
- subject.install
- end
- end
-
- describe "#finish" do
- it "releases the packaging system" do
- allow(subject).to receive(:copy_zypp_to_target)
- expect(Yast::Pkg).to receive(:SourceSaveAll)
- expect(Yast::Pkg).to receive(:TargetFinish)
-
- subject.finish
- end
-
- it "copies the libzypp cache and credentials to the target system" do
- allow(Agama::Software::Repository).to receive(:all).and_return(
- [
- Agama::Software::Repository.new(
- repo_id: 42, repo_alias: "alias", name: "name",
- url: "http://example.com", enabled: true, autorefresh: false
- )
- ]
- )
-
- allow(Dir).to receive(:exist?).and_call_original
- allow(Dir).to receive(:entries).and_call_original
-
- # copying the raw cache
- expect(Dir).to receive(:exist?).with(
- File.join(target_dir, "/var/cache/zypp/raw")
- ).and_return(true)
- expect(FileUtils).to receive(:mkdir_p).with(
- File.join(Yast::Installation.destdir, "/var/cache/zypp")
- )
- expect(FileUtils).to receive(:cp_r).with(
- File.join(target_dir, "/var/cache/zypp/raw"),
- File.join(Yast::Installation.destdir, "/var/cache/zypp")
- )
-
- # copy the solv cache
- repo_alias = "https-download.opensuse.org-94cc89aa"
- expect(Dir).to receive(:entries)
- .with(File.join(target_dir, "/var/cache/zypp/solv"))
- .and_return([".", "..", "@System", repo_alias])
- expect(FileUtils).to receive(:cp_r).with(
- File.join(target_dir, "/var/cache/zypp/solv/", repo_alias),
- File.join(Yast::Installation.destdir, "/var/cache/zypp/solv")
- )
- # ensure the @System cache is not copied
- expect(FileUtils).to_not receive(:cp_r).with(
- File.join(target_dir, "/var/cache/zypp/solv/@System"),
- File.join(Yast::Installation.destdir, "/var/cache/zypp/solv")
- )
-
- # copying the credentials.d directory
- expect(Dir).to receive(:exist?)
- .with(File.join(target_dir, "/etc/zypp/credentials.d"))
- .and_return(true)
- expect(FileUtils).to receive(:mkdir_p)
- .with(File.join(Yast::Installation.destdir, "/etc/zypp"))
- expect(FileUtils).to receive(:cp_r).with(
- File.join(target_dir, "/etc/zypp/credentials.d"),
- File.join(Yast::Installation.destdir, "/etc/zypp")
- )
-
- # copying the global credentials file
- expect(File).to receive(:exist?)
- .with(File.join(target_dir, "/etc/zypp/credentials.cat"))
- .and_return(true)
- expect(FileUtils).to receive(:copy).with(
- File.join(target_dir, "/etc/zypp/credentials.cat"),
- File.join(Yast::Installation.destdir, "/etc/zypp")
- )
-
- subject.finish
- end
-
- context "only a local repository is used" do
- let(:repo_id) { 42 }
- before do
- allow(Agama::Software::Repository).to receive(:all).and_return(
- [
- Agama::Software::Repository.new(
- repo_id: repo_id, repo_alias: "alias", name: "name",
- url: "dvd:/install?devices=/dev/sr0", enabled: true, autorefresh: false
- )
- ]
- )
- end
-
- it "disables the local repository" do
- allow(subject).to receive(:copy_zypp_to_target)
- expect(Yast::Pkg).to receive(:SourceSetEnabled).with(repo_id, false)
-
- subject.finish
- end
-
- it "copies the libzypp cache" do
- expect(subject).to receive(:copy_zypp_to_target)
-
- subject.finish
- end
- end
- end
-
- describe "#package_installed?" do
- before do
- allow(Yast::Package).to receive(:Installed).with(package, target: :system)
- .and_return(installed?)
- end
-
- let(:package) { "NetworkManager" }
-
- context "when the package is installed" do
- let(:installed?) { true }
-
- it "returns true" do
- expect(subject.package_installed?(package)).to eq(true)
- end
- end
-
- context "when the package is not installed" do
- let(:installed?) { false }
-
- it "returns false" do
- expect(subject.package_installed?(package)).to eq(false)
- end
- end
- end
-
- describe "#package_available?" do
- before do
- allow(Yast::Package).to receive(:Available).with(package).and_return(available)
- end
-
- let(:package) { "NetworkManager" }
-
- context "when the package is available" do
- let(:available) { true }
-
- it "returns true" do
- expect(subject.package_available?(package)).to eq(true)
- end
- end
-
- context "when the package is not available" do
- let(:available) { false }
-
- it "returns false" do
- expect(subject.package_available?(package)).to eq(false)
- end
- end
-
- context "when there is an error checking its availability" do
- let(:available) { nil }
-
- it "returns false" do
- expect(subject.package_available?(package)).to eq(false)
- end
- end
- end
-
- describe "#add_service" do
- it "does not raise exception if everything goes well" do
- service = double(name: "test", url: "http://test.com")
- expect { subject.add_service(service) }.to_not raise_error
- end
-
- it "raises ServiceError when failed to add service" do
- expect(Yast::Pkg).to receive(:ServiceForceRefresh).and_return(false)
- service = double(name: "test", url: "http://test.com")
- expect { subject.add_service(service) }.to raise_error(Agama::Software::ServiceError)
- end
- end
-
- describe "#product_issues" do
- before do
- allow_any_instance_of(Agama::Software::ProductBuilder)
- .to receive(:build).and_return([product1, product2])
- end
-
- let(:product1) do
- Agama::Software::Product.new("test1").tap { |p| p.repositories = [] }
- end
-
- let(:product2) do
- Agama::Software::Product.new("test2").tap { |p| p.repositories = ["http://test"] }
- end
-
- context "if no product is selected yet" do
- it "contains a missing product issue" do
- expect(subject.product_issues).to contain_exactly(
- an_object_having_attributes(
- description: /product not selected/i
- )
- )
- end
- end
-
- context "if a product is already selected" do
- before do
- subject.select_product(product_id)
- end
-
- let(:product_id) { "test1" }
-
- it "does not include a missing product issue" do
- expect(subject.product_issues).to_not include(
- an_object_having_attributes(
- description: /product not selected/i
- )
- )
- end
-
- context "and the product does not require registration" do
- let(:product_id) { "test2" }
-
- it "does not contain issues" do
- expect(subject.product_issues).to be_empty
- end
- end
-
- context "and the product requires registration" do
- let(:product_id) { "test1" }
- let(:product) do
- Agama::Software::Product.new("test1").tap do |p|
- p.registration = true
- p.name = "test1"
- end
- end
-
- before do
- allow(subject).to receive(:product).and_return(product)
- allow(Y2Packager::Resolvable).to receive(:find)
- .with(kind: :product, name: product.name)
- .and_return(resolvables)
- end
-
- context "and the base product is not available" do
- let(:resolvables) { [] }
-
- it "contains a missing registration issue" do
- expect(subject.product_issues).to contain_exactly(
- an_object_having_attributes(
- kind: :missing_registration
- )
- )
- end
-
- context "and the base product is available" do
- let(:resolvables) { [instance_double("Product")] }
-
- it "does not contain issues" do
- expect(subject.product_issues).to be_empty
- end
- end
- end
- end
- end
- end
-
- describe "#update_selected_patterns" do
- it "unselects user patterns unselected by conflict resolution" do
- # user selected patterns
- expect(Yast::PackagesProposal).to receive(:GetResolvables).with(anything,
- :pattern).and_return(["pattern1", "pattern2"])
- # patterns selected in libzypp
- expect(Y2Packager::Resolvable).to receive(:find).with(kind: :pattern,
- status: :selected).and_return([double(name: "pattern1")])
- # list of patterns is changed
- expect(subject).to receive(:selected_patterns_changed)
- # the "pattern2" is unselected
- expect(Yast::PackagesProposal).to receive(:RemoveResolvables).with(anything, :pattern,
- ["pattern2"])
-
- subject.update_selected_patterns
- end
- end
-
- include_examples "issues"
- include_examples "progress"
-end
diff --git a/service/test/agama/software/product_builder_test.rb b/service/test/agama/software/product_builder_test.rb
deleted file mode 100644
index 560aef47a7..0000000000
--- a/service/test/agama/software/product_builder_test.rb
+++ /dev/null
@@ -1,308 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023-2025] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "yast"
-require "agama/config"
-require "agama/product_reader"
-require "agama/software/product"
-require "agama/software/product_builder"
-
-Yast.import "Arch"
-
-describe Agama::Software::ProductBuilder do
- before do
- allow(Agama::ProductReader).to receive(:new).and_return(reader)
- end
-
- let(:reader) { instance_double(Agama::ProductReader, load_products: products) }
-
- let(:products) do
- [
- {
- "id" => "Test1",
- "name" => "Product Test 1",
- "description" => "This is a test product named Test 1",
- "version" => "1.0",
- "registration" => true,
- "license" => "suse",
- "translations" => {
- "description" => {
- "cs" => "Czech",
- "es" => "Spanish"
- }
- },
- "software" => {
- "installation_repositories" => [
- {
- "url" => "https://repos/test1/x86_64/product/",
- "archs" => "x86_64"
- },
- {
- "url" => "https://repos/test1/aarch64/product/",
- "archs" => "aarch64"
- }
- ],
- "mandatory_packages" => [
- {
- "package" => "package1-1"
- },
- "package1-2",
- {
- "package" => "package1-3",
- "archs" => "aarch64,x86_64"
- },
- {
- "package" => "package1-4",
- "archs" => "ppc64"
- }
- ],
- "optional_packages" => ["package1-5"],
- "mandatory_patterns" => ["pattern1-1", "pattern1-2"],
- "optional_patterns" => [
- {
- "pattern" => "pattern1-3",
- "archs" => "x86_64"
- },
- {
- "pattern" => "pattern1-4",
- "archs" => "aarch64"
- }
- ],
- "base_product" => "Test1"
- }
- },
- {
- "id" => "Test2",
- "name" => "Product Test 2",
- "description" => "This is a test product named Test 2",
- "archs" => "x86_64,aarch64",
- "version" => "2.0",
- "registration" => true,
- "software" => {
- "mandatory_patterns" => ["pattern2-1"],
- "base_product" => "Test2"
- }
- },
- {
- "id" => "Test3",
- "name" => "Product Test 3",
- "description" => "This is a test product named Test 3",
- "archs" => "ppc64,aarch64",
- "software" => {
- "installation_repositories" => ["https://repos/test3/product/"],
- "optional_patterns" => [
- {
- "pattern" => "pattern3-1",
- "archs" => "aarch64"
- }
- ],
- "base_product" => "Test3"
- }
- }
- ]
- end
-
- subject { described_class.new(config) }
-
- let(:config) { Agama::Config.new }
-
- describe "#build" do
- context "for x86_64" do
- before do
- allow(Yast::Arch).to receive("x86_64").and_return(true)
- allow(Yast::Arch).to receive("aarch64").and_return(false)
- allow(Yast::Arch).to receive("ppc64").and_return(false)
- allow(Yast::Arch).to receive("s390").and_return(false)
- end
-
- it "generates products according to the current architecture" do
- products = subject.build
-
- expect(products).to all(be_a(Agama::Software::Product))
-
- expect(products).to contain_exactly(
- an_object_having_attributes(
- id: "Test1",
- display_name: "Product Test 1",
- description: "This is a test product named Test 1",
- name: "Test1",
- version: "1.0",
- registration: true,
- license: "suse",
- repositories: ["https://repos/test1/x86_64/product/"],
- mandatory_patterns: ["pattern1-1", "pattern1-2"],
- optional_patterns: ["pattern1-3"],
- mandatory_packages: ["package1-1", "package1-2", "package1-3"],
- optional_packages: ["package1-5"],
- translations: { "description" => { "cs" => "Czech", "es" => "Spanish" } }
- ),
- an_object_having_attributes(
- id: "Test2",
- display_name: "Product Test 2",
- description: "This is a test product named Test 2",
- name: "Test2",
- version: "2.0",
- repositories: [],
- mandatory_patterns: ["pattern2-1"],
- optional_patterns: [],
- mandatory_packages: [],
- optional_packages: [],
- translations: {}
- )
- )
- end
- end
-
- context "for aarch64" do
- before do
- allow(Yast::Arch).to receive("x86_64").and_return(false)
- allow(Yast::Arch).to receive("aarch64").and_return(true)
- allow(Yast::Arch).to receive("ppc64").and_return(false)
- allow(Yast::Arch).to receive("s390").and_return(false)
- end
-
- it "generates products according to the current architecture" do
- products = subject.build
-
- expect(products).to all(be_a(Agama::Software::Product))
-
- expect(products).to contain_exactly(
- an_object_having_attributes(
- id: "Test1",
- display_name: "Product Test 1",
- description: "This is a test product named Test 1",
- name: "Test1",
- version: "1.0",
- repositories: ["https://repos/test1/aarch64/product/"],
- mandatory_patterns: ["pattern1-1", "pattern1-2"],
- optional_patterns: ["pattern1-4"],
- mandatory_packages: ["package1-1", "package1-2", "package1-3"],
- optional_packages: ["package1-5"],
- translations: { "description" => { "cs" => "Czech", "es" => "Spanish" } }
- ),
- an_object_having_attributes(
- id: "Test2",
- display_name: "Product Test 2",
- description: "This is a test product named Test 2",
- name: "Test2",
- version: "2.0",
- registration: true,
- repositories: [],
- mandatory_patterns: ["pattern2-1"],
- optional_patterns: [],
- mandatory_packages: [],
- optional_packages: [],
- translations: {}
- ),
- an_object_having_attributes(
- id: "Test3",
- display_name: "Product Test 3",
- description: "This is a test product named Test 3",
- name: "Test3",
- version: nil,
- repositories: ["https://repos/test3/product/"],
- mandatory_patterns: [],
- optional_patterns: ["pattern3-1"],
- mandatory_packages: [],
- optional_packages: [],
- translations: {}
- )
- )
- end
- end
-
- context "for ppc64" do
- before do
- allow(Yast::Arch).to receive("x86_64").and_return(false)
- allow(Yast::Arch).to receive("aarch64").and_return(false)
- allow(Yast::Arch).to receive("ppc64").and_return(true)
- allow(Yast::Arch).to receive("s390").and_return(false)
- end
-
- it "generates products according to the current architecture" do
- products = subject.build
-
- expect(products).to all(be_a(Agama::Software::Product))
-
- expect(products).to contain_exactly(
- an_object_having_attributes(
- id: "Test1",
- display_name: "Product Test 1",
- description: "This is a test product named Test 1",
- name: "Test1",
- version: "1.0",
- repositories: [],
- mandatory_patterns: ["pattern1-1", "pattern1-2"],
- optional_patterns: [],
- mandatory_packages: ["package1-1", "package1-2", "package1-4"],
- optional_packages: ["package1-5"],
- translations: { "description" => { "cs" => "Czech", "es" => "Spanish" } }
- ),
- an_object_having_attributes(
- id: "Test3",
- display_name: "Product Test 3",
- description: "This is a test product named Test 3",
- name: "Test3",
- version: nil,
- repositories: ["https://repos/test3/product/"],
- mandatory_patterns: [],
- optional_patterns: [],
- mandatory_packages: [],
- optional_packages: [],
- translations: {}
- )
- )
- end
- end
-
- context "for s390" do
- before do
- allow(Yast::Arch).to receive("x86_64").and_return(false)
- allow(Yast::Arch).to receive("aarch64").and_return(false)
- allow(Yast::Arch).to receive("ppc64").and_return(false)
- allow(Yast::Arch).to receive("s390").and_return(true)
- end
-
- it "generates products according to the current architecture" do
- products = subject.build
-
- expect(products).to all(be_a(Agama::Software::Product))
-
- expect(products).to contain_exactly(
- an_object_having_attributes(
- id: "Test1",
- display_name: "Product Test 1",
- description: "This is a test product named Test 1",
- name: "Test1",
- version: "1.0",
- repositories: [],
- mandatory_patterns: ["pattern1-1", "pattern1-2"],
- optional_patterns: [],
- mandatory_packages: ["package1-1", "package1-2"],
- optional_packages: ["package1-5"],
- translations: { "description" => { "cs" => "Czech", "es" => "Spanish" } }
- )
- )
- end
- end
- end
-end
diff --git a/service/test/agama/software/product_test.rb b/service/test/agama/software/product_test.rb
deleted file mode 100644
index b02e480b23..0000000000
--- a/service/test/agama/software/product_test.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "agama/software/product"
-
-describe Agama::Software::Product do
- subject do
- described_class.new("Test").tap do |product|
- product.user_patterns = [
- Agama::Software::UserPattern.new("kde", false),
- Agama::Software::UserPattern.new("selinux", true)
- ]
- end
- end
-
- describe "#localized_description" do
- before do
- subject.description = "Original description"
- subject.translations = {
- "description" => {
- "cs" => "Czech translation",
- "es" => "Spanish translation"
- }
- }
- end
-
- it "returns untranslated description when the language is not set" do
- allow(ENV).to receive(:[]).with("LANG").and_return(nil)
-
- expect(subject.localized_description).to eq("Original description")
- end
-
- it "returns Czech translation if locale is \"cs_CZ.UTF-8\"" do
- allow(ENV).to receive(:[]).with("LANG").and_return("cs_CZ.UTF-8")
-
- expect(subject.localized_description).to eq("Czech translation")
- end
-
- it "returns Czech translation if locale is \"cs\"" do
- allow(ENV).to receive(:[]).with("LANG").and_return("cs")
-
- expect(subject.localized_description).to eq("Czech translation")
- end
-
- it "return untranslated description when translation is not available" do
- allow(ENV).to receive(:[]).with("LANG").and_return("cs_CZ.UTF-8")
- subject.translations = {}
-
- expect(subject.localized_description).to eq("Original description")
- end
- end
-
- describe "#preselected_patterns" do
- it "returns the user preselected patterns" do
- expect(subject.preselected_patterns).to eq(["selinux"])
- end
- end
-end
diff --git a/service/test/agama/software/proposal_test.rb b/service/test/agama/software/proposal_test.rb
deleted file mode 100644
index 0aba698009..0000000000
--- a/service/test/agama/software/proposal_test.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022-2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "agama/software/proposal"
-require "agama/config"
-
-describe Agama::Software::Proposal do
- subject(:proposal) { described_class.new(logger: logger) }
-
- let(:logger) { Logger.new($stdout, level: :warn) }
- let(:destdir) { "/mnt" }
- let(:result) { {} }
- let(:last_error) { "" }
- let(:solve_errors) { 0 }
-
- before do
- allow(Yast::Pkg).to receive(:SourceSaveAll)
- allow(Yast::Packages).to receive(:Proposal).and_return(result)
- allow(Yast::Pkg).to receive(:TargetFinish)
- allow(Yast::Pkg).to receive(:TargetInitialize)
- allow(Yast::Pkg).to receive(:TargetLoad)
- allow(Yast::Installation).to receive(:destdir).and_return(destdir)
- allow(Yast::Pkg).to receive(:LastError).and_return(last_error)
- allow(Yast::Pkg).to receive(:PkgSolveErrors).and_return(solve_errors)
- allow(Yast::Pkg).to receive(:SetSolverFlags)
- end
-
- describe "#calculate" do
- it "makes a proposal" do
- expect(Yast::Packages).to receive(:Proposal).and_return(result)
- expect(Yast::Pkg).to receive(:PkgSolve)
- subject.calculate
- end
-
- it "selects the language packages" do
- expect(Yast::Pkg).to receive(:SetPackageLocale).with("cs_CZ")
- expect(Yast::Pkg).to receive(:SetAdditionalLocales).with(["de_DE"])
- subject.languages = ["cs_CZ", "de_DE"]
- subject.calculate
- end
-
- it "returns true" do
- expect(subject.calculate).to eq(true)
- end
-
- context "when a proposal is not possible or contain errors" do
- let(:solve_errors) { 1 }
-
- it "returns false" do
- expect(subject.calculate).to eq(false)
- end
- end
-
- context "when no errors were reported" do
- it "does not register any issue" do
- subject.calculate
- expect(subject.issues).to be_empty
- end
- end
-
- context "when a blocking warning is reported" do
- let(:result) do
- { "warning_level" => :blocker, "warning" => "Could not install..." }
- end
-
- it "registers the corresponding issue" do
- subject.calculate
- expect(subject.issues).to contain_exactly(
- an_object_having_attributes({ description: "Could not install..." })
- )
- end
- end
-
- context "when solver errors are reported" do
- let(:solve_errors) { 5 }
-
- it "registers them as issues" do
- subject.calculate
- expect(subject.issues).to contain_exactly(
- an_object_having_attributes(description: "Found 5 dependency issues.")
- )
- end
- end
- end
-
- describe "#solve_dependencies" do
- it "calls the solver" do
- expect(Yast::Pkg).to receive(:PkgSolve)
- subject.solve_dependencies
- end
-
- context "if the solver successes" do
- before do
- allow(Yast::Pkg).to receive(:PkgSolve).and_return(true)
- end
-
- it "returns true" do
- expect(subject.solve_dependencies).to eq(true)
- end
- end
-
- context "if the solver fails" do
- before do
- allow(Yast::Pkg).to receive(:PkgSolve).and_return(false)
- end
-
- let(:solve_errors) { 2 }
-
- it "returns false" do
- expect(subject.solve_dependencies).to eq(false)
- end
-
- it "registers solver issue" do
- subject.solve_dependencies
- expect(subject.issues).to contain_exactly(
- an_object_having_attributes(description: "Found 2 dependency issues.")
- )
- end
- end
- end
-
- describe "#set_resolvables" do
- it "adds the list of packages/patterns to the proposal" do
- expect(Yast::PackagesProposal).to receive(:SetResolvables)
- .with("agama", :pattern, "alp_base", optional: false)
- subject.set_resolvables("agama", :pattern, "alp_base", optional: false)
- end
- end
-
- describe "#packages_count" do
- before do
- allow(Yast::Pkg).to receive(:PkgMediaCount).and_return([[75], [50], [25], [0]])
- end
-
- it "returns the amount of packages to install" do
- expect(subject.packages_count).to eq(150)
- end
- end
-
- describe "#packages_size" do
- before do
- allow(Yast::Pkg).to receive(:PkgMediaSizes)
- .and_return([[900000000], [0], [500000000]])
- end
-
- it "returns the size of packages to install" do
- expect(subject.packages_size).to eq(1400000000)
- end
- end
-
- describe "#valid?" do
- context "when the proposal was calculated and there were no errors" do
- it "returns true" do
- subject.calculate
- expect(subject.valid?).to eq(true)
- end
- end
-
- context "when the proposal is not calculated yet" do
- it "returns false" do
- expect(subject.valid?).to eq(false)
- end
- end
-
- context "when there are errors" do
- let(:solve_errors) { 1 }
-
- it "returns false" do
- subject.calculate
- expect(subject.valid?).to eq(false)
- end
- end
- end
-
- describe "#languages" do
- it "sets the languages to install removing the encoding" do
- subject.languages = ["es_ES.UTF-8", "en_US"]
- expect(subject.languages).to eq(["es_ES", "en_US"])
- end
- end
-end
diff --git a/service/test/agama/software/repositories_manager_test.rb b/service/test/agama/software/repositories_manager_test.rb
deleted file mode 100644
index e3dc87a923..0000000000
--- a/service/test/agama/software/repositories_manager_test.rb
+++ /dev/null
@@ -1,229 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "agama/software/repositories_manager"
-
-describe Agama::Software::RepositoriesManager do
- subject do
- res = described_class.instance
- res.reset
- res
- end
-
- # probe and refresh succeed
- let(:repo) do
- instance_double(
- Agama::Software::Repository, enable!: nil, probe: true, enabled?: true, refresh: true
- )
- end
-
- let(:disabled_repo) do
- instance_double(Agama::Software::Repository, enable!: nil, enabled?: false)
- end
-
- let(:user_repositories) do
- [{
- "url" => "http://testing.com",
- "alias" => "test",
- "enabled" => true,
- "priority" => 50,
- "allow_unsigned" => true,
- "gpg_fingerprints" => ["0123"]
- }]
- end
-
- describe "#add" do
- it "registers the repository in the packaging system" do
- url = "https://example.net"
- expect(Agama::Software::Repository).to receive(:create)
- .with(autorefresh: true, name: url, repo_alias: "", url: url, priority: 99)
- .and_return(repo)
- subject.add(url)
- expect(subject.repositories).to include(repo)
- end
- end
-
- describe "#load" do
- # probe and refresh fail
- let(:repo1) do
- instance_double(
- Agama::Software::Repository, enable!: nil, disable!: nil, probe: false, refresh: false
- )
- end
-
- # corner case, probe succeeds but refresh fails
- let(:repo2) do
- instance_double(
- Agama::Software::Repository, enable!: nil, disable!: nil, probe: true, refresh: false
- )
- end
-
- before do
- subject.repositories << repo
- subject.repositories << repo1
- subject.repositories << repo2
- allow(Yast::Pkg).to receive(:SourceLoad)
- end
-
- it "enables the repositories that can be read" do
- expect(repo).to receive(:enable!)
- expect(repo).to_not receive(:disable!)
- subject.load
- end
-
- it "disables the repositories that cannot be probed" do
- expect(repo1).to receive(:disable!)
- subject.load
- end
-
- it "disables the repositories that cannot be refreshed" do
- expect(repo2).to receive(:disable!)
- subject.load
- end
-
- it "loads the repositories" do
- expect(Yast::Pkg).to receive(:SourceLoad)
- subject.load
- end
- end
-
- describe "#delete_all" do
- before do
- subject.repositories << repo
- subject.repositories << disabled_repo
- end
-
- it "deletes all the repositories" do
- expect(repo).to receive(:delete!)
- expect(disabled_repo).to receive(:delete!)
- subject.delete_all
- end
- end
-
- describe "#enabled" do
- before do
- subject.repositories << repo
- subject.repositories << disabled_repo
- end
-
- it "returns the enabled repositories" do
- expect(subject.enabled).to eq([repo])
- end
- end
-
- describe "#disabled" do
- before do
- subject.repositories << repo
- subject.repositories << disabled_repo
- end
-
- it "returns the enabled repositories" do
- expect(subject.disabled).to eq([disabled_repo])
- end
- end
-
- describe "unsigned_allowed?" do
- it "returns true if user repo can be unsigned" do
- allow(Yast::Pkg).to receive(:RepositoryAdd).and_return(1)
- allow(Agama::Software::Repository).to receive(:find).and_return(double)
- allow(subject).to receive(:load)
- subject.user_repositories = user_repositories
-
- expect(subject.unsigned_allowed?("test")).to eq true
- end
- end
-
- describe "trust_gpg?" do
- it "returns true if gpg key fingerprint matches user defined one" do
- allow(Yast::Pkg).to receive(:RepositoryAdd).and_return(1)
- allow(Agama::Software::Repository).to receive(:find).and_return(double)
- allow(subject).to receive(:load)
- subject.user_repositories = user_repositories
-
- expect(subject.trust_gpg?("test", "0123")).to eq true
- end
-
- it "ignores any whitespaces in fingerprint" do
- allow(Yast::Pkg).to receive(:RepositoryAdd).and_return(1)
- allow(Agama::Software::Repository).to receive(:find).and_return(double)
- allow(subject).to receive(:load)
- subject.user_repositories = user_repositories
-
- expect(subject.trust_gpg?("test", "01 23")).to eq true
- end
- end
-
- describe "#user_repositories" do
- before do
- allow(Yast::Pkg).to receive(:RepositoryAdd).and_return(1)
- allow(Agama::Software::Repository).to receive(:find).and_return(double)
- allow(subject).to receive(:load)
- end
-
- it "returns list of repositories as defined by user" do
- subject.user_repositories = user_repositories
- expect(subject.user_repositories).to eq user_repositories
- end
- end
-
- describe "#user_repositories=" do
- before do
- allow(Yast::Pkg).to receive(:RepositoryAdd).and_return(1)
- allow(Agama::Software::Repository).to receive(:find).and_return(double)
- allow(subject).to receive(:load)
- end
-
- it "sets list of user repositories" do
- subject.user_repositories = user_repositories
- expect(subject.user_repositories).to eq user_repositories
- end
-
- it "add repositories to repository pool" do
- expect(Yast::Pkg).to receive(:RepositoryAdd).and_return(1)
- repo = double
- allow(Agama::Software::Repository).to receive(:find).with(1).and_return(repo)
-
- subject.user_repositories = user_repositories
- expect(subject.repositories).to include repo
- end
-
- it "loads repositories" do
- expect(subject).to receive(:load)
-
- subject.user_repositories = user_repositories
- end
-
- it "removes previous user repositories" do
- old_repo = double
- allow(Agama::Software::Repository).to receive(:find).and_return(old_repo)
- subject.user_repositories = user_repositories
-
- expect(old_repo).to receive(:delete!)
- new_repo = double
- allow(Agama::Software::Repository).to receive(:find).and_return(new_repo)
-
- subject.user_repositories = user_repositories
- expect(subject.repositories).to_not include old_repo
- expect(subject.repositories).to include new_repo
- end
- end
-end
diff --git a/service/test/agama/software/repository_test.rb b/service/test/agama/software/repository_test.rb
deleted file mode 100644
index e2c105cb26..0000000000
--- a/service/test/agama/software/repository_test.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2023] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../../test_helper"
-require "agama/software/repository"
-
-describe Agama::Software::Repository do
- subject do
- described_class.new(
- repo_id: 1, repo_alias: "tumbleweed", name: "openSUSE Tumbleweed",
- url: "https://example.net/oss", enabled: true, autorefresh: true,
- product_dir: "/"
- )
- end
-
- describe "#probe" do
- before do
- allow(Yast::Pkg).to receive(:RepositoryProbe).with(/example.net/, "/")
- .and_return(repo_type)
-
- # do not call real sleep to make the test faster
- allow_any_instance_of(Agama::Software::Repository).to receive(:sleep)
- end
-
- context "if the repository can be read" do
- let(:repo_type) { "YUM" }
-
- it "returns true" do
- expect(subject.probe).to eq(true)
- end
- end
-
- context "if the repository type cannot be inferred" do
- let(:repo_type) { "NONE" }
-
- it "returns false" do
- expect(subject.probe).to eq(false)
- end
- end
-
- context "if the repository cannot be red" do
- let(:repo_type) { nil }
-
- it "returns false" do
- expect(subject.probe).to eq(false)
- end
-
- it "retries probing automatically" do
- expect(Yast::Pkg).to receive(:RepositoryProbe).at_least(2).times.and_return(nil)
- subject.probe
- end
- end
- end
-
- describe "#refresh" do
- before do
- allow(Yast::Pkg).to receive(:SourceRefreshNow).and_return(refresh_result)
-
- # do not call real sleep to make the test faster
- allow(subject).to receive(:sleep)
- end
-
- context "if the repository can be refreshed" do
- let(:refresh_result) { true }
-
- it "returns true" do
- expect(subject.refresh).to eq(true)
- end
- end
-
- context "if the repository cannot be refreshed" do
- let(:refresh_result) { nil }
-
- it "returns false" do
- expect(subject.refresh).to eq(false)
- end
-
- it "retries refresh automatically" do
- expect(Yast::Pkg).to receive(:SourceRefreshNow).at_least(2).times
- subject.refresh
- end
- end
- end
-end
diff --git a/service/test/agama/storage/finisher_test.rb b/service/test/agama/storage/finisher_test.rb
index 4c2e3ed3a5..572abb52b7 100644
--- a/service/test/agama/storage/finisher_test.rb
+++ b/service/test/agama/storage/finisher_test.rb
@@ -24,13 +24,12 @@
require_relative "../with_progress_examples"
require "agama/helpers"
require "agama/config"
-require "agama/security"
require "agama/storage/finisher"
describe Agama::Storage::Finisher do
include Agama::RSpec::StorageHelpers
- subject(:storage) { described_class.new(logger, config, security) }
+ subject(:storage) { described_class.new(logger, config) }
let(:logger) { Logger.new($stdout, level: :warn) }
let(:config_path) do
@@ -39,7 +38,6 @@
let(:destdir) { File.join(FIXTURES_PATH, "target_dir") }
let(:config) { Agama::Config.from_file(config_path) }
- let(:security) { instance_double(Agama::Security, write: nil) }
let(:copy_files) { Agama::Storage::Finisher::CopyFilesStep.new(logger) }
let(:progress) { instance_double(Agama::OldProgress, step: nil) }
diff --git a/service/test/agama/storage/manager_test.rb b/service/test/agama/storage/manager_test.rb
index c497800c71..80a39ae596 100644
--- a/service/test/agama/storage/manager_test.rb
+++ b/service/test/agama/storage/manager_test.rb
@@ -50,23 +50,17 @@
File.join(FIXTURES_PATH, "root_dir", "etc", "agama.yaml")
end
let(:config) { Agama::Config.from_file(config_path) }
- let(:files_client) { instance_double(Agama::HTTP::Clients::Files, write: nil) }
- let(:scripts_client) { instance_double(Agama::HTTP::Clients::Scripts, run: nil) }
- let(:scripts_dir) { File.join(tmp_dir, "run", "agama", "scripts") }
let(:tmp_dir) { Dir.mktmpdir }
+ let(:http_client) { instance_double(Agama::HTTP::Clients::Main) }
before do
mock_storage(devicegraph: scenario)
allow(Agama::Storage::Proposal).to receive(:new).and_return(proposal)
allow(Agama::HTTP::Clients::Questions).to receive(:new).and_return(questions_client)
- allow(Agama::HTTP::Clients::Software).to receive(:new).and_return(software)
+ allow(Agama::HTTP::Clients::Main).to receive(:new).and_return(http_client)
allow(Bootloader::FinishClient).to receive(:new).and_return(bootloader_finish)
- allow(Agama::Security).to receive(:new).and_return(security)
# mock writting config as proposal call can do storage probing, which fails in CI
allow_any_instance_of(Agama::Storage::Bootloader).to receive(:write_config)
- allow(Agama::HTTP::Clients::Files).to receive(:new).and_return(files_client)
- allow(Agama::HTTP::Clients::Scripts).to receive(:new).and_return(scripts_client)
- allow(Agama::Network).to receive(:new).and_return(network)
allow(Yast::Installation).to receive(:destdir).and_return(File.join(tmp_dir, "mnt"))
stub_const("Agama::Storage::Finisher::CopyLogsStep::SCRIPTS_DIR",
File.join(tmp_dir, "run", "agama", "scripts"))
@@ -79,12 +73,7 @@
let(:y2storage_manager) { Y2Storage::StorageManager.instance }
let(:proposal) { Agama::Storage::Proposal.new(config, logger: logger) }
let(:questions_client) { instance_double(Agama::HTTP::Clients::Questions) }
- let(:software) do
- instance_double(Agama::HTTP::Clients::Software, config: { "product" => "ALP" })
- end
- let(:network) { instance_double(Agama::Network, link_resolv: nil, unlink_resolv: nil) }
let(:bootloader_finish) { instance_double(Bootloader::FinishClient, write: nil) }
- let(:security) { instance_double(Agama::Security, write: nil) }
let(:scenario) { "empty-hd-50GiB.yaml" }
describe "#activate" do
@@ -277,7 +266,7 @@
describe "#add_packages" do
before do
allow(y2storage_manager).to receive(:staging).and_return(proposed_devicegraph)
- allow(Yast::PackagesProposal).to receive(:SetResolvables)
+ allow(Agama::HTTP::Clients::Main).to receive(:new).and_return(http_client)
end
let(:proposed_devicegraph) do
@@ -293,9 +282,8 @@
end
it "adds storage software to install" do
- expect(Yast::PackagesProposal).to receive(:SetResolvables) do |_, _, packages|
- expect(packages).to contain_exactly("btrfsprogs", "snapper")
- end
+ expect(http_client).to receive(:set_resolvables)
+ .with("storage_proposal", :package, match(include("btrfsprogs", "snapper")))
storage.add_packages
end
@@ -307,9 +295,8 @@
end
it "adds the iSCSI software to install" do
- expect(Yast::PackagesProposal).to receive(:SetResolvables) do |_, _, packages|
- expect(packages).to include("open-iscsi", "iscsiuio")
- end
+ expect(http_client).to receive(:set_resolvables)
+ .with("storage_proposal", :package, match(include("iscsiuio")))
storage.add_packages
end
@@ -326,9 +313,8 @@
end
it "adds the iSCSI software to install" do
- expect(Yast::PackagesProposal).to receive(:SetResolvables) do |_, _, packages|
- expect(packages).to include("open-iscsi", "iscsiuio")
- end
+ expect(http_client).to receive(:set_resolvables)
+ .with("storage_proposal", :package, match(include("open-iscsi", "iscsiuio")))
storage.add_packages
end
@@ -358,38 +344,17 @@
it "copy needed files, installs the bootloader, sets up the snapshots, " \
"copy logs, symlink resolv.conf, runs the post-installation scripts, " \
"unlink resolv.conf, and umounts the file systems" do
- expect(security).to receive(:write)
expect(copy_files).to receive(:run)
expect(bootloader_finish).to receive(:write)
expect(Yast::WFM).to receive(:CallFunction).with("storage_finish", ["Write"])
expect(Yast::WFM).to receive(:CallFunction).with("iscsi-client_finish", ["Write"])
expect(Yast2::FsSnapshot).to receive(:configure_snapper)
- expect(network).to receive(:link_resolv)
- expect(scripts_client).to receive(:run).with("post")
- expect(files_client).to receive(:write)
- expect(network).to receive(:unlink_resolv)
- expect(Yast::Execute).to receive(:on_target!)
- .with("systemctl", "enable", "agama-scripts", allowed_exitstatus: [0, 1])
expect(Yast::WFM).to receive(:CallFunction).with("umount_finish", ["Write"])
expect(Yast::Execute).to receive(:locally).with(
"agama", "logs", "store", "--destination", /\/var\/log\/agama-installation\/logs/
)
storage.finish
end
-
- context "when scripts artifacts exist" do
- before do
- FileUtils.mkdir_p(scripts_dir)
- FileUtils.touch(File.join(scripts_dir, "test.sh"))
- allow(Yast::Execute).to receive("locally").with("agama", "logs", "store", any_args)
- end
-
- it "copies the artifacts to the installed system" do
- storage.finish
- expect(File).to exist(File.join(tmp_dir, "mnt", "var", "log", "agama-installation",
- "scripts"))
- end
- end
end
describe "#actions" do
diff --git a/service/test/agama/users_test.rb b/service/test/agama/users_test.rb
deleted file mode 100644
index bd189eda4f..0000000000
--- a/service/test/agama/users_test.rb
+++ /dev/null
@@ -1,247 +0,0 @@
-# frozen_string_literal: true
-
-# Copyright (c) [2022] SUSE LLC
-#
-# All Rights Reserved.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of version 2 of the GNU General Public License as published
-# by the Free Software Foundation.
-#
-# 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.
-
-require_relative "../test_helper"
-require "agama/users"
-
-describe Agama::Users do
- subject(:storage) { described_class.new(logger) }
-
- let(:logger) { Logger.new($stdout) }
-
- let(:users_config) { Y2Users::Config.new }
-
- before do
- allow(Y2Users::ConfigManager.instance).to receive(:target)
- .and_return(users_config)
- end
-
- describe "#assign_root_password" do
- let(:root_user) { instance_double(Y2Users::User) }
-
- describe "#root_user" do
- it "returns the root user" do
- root = subject.root_user
- expect(root.name).to eq("root")
- end
- end
-
- context "when the password is hashed" do
- it "sets the password as hashed" do
- subject.assign_root_password("hashed", true)
- root_user = users_config.users.root
- expect(root_user.password).to eq(Y2Users::Password.create_encrypted("hashed"))
- end
- end
-
- context "when the password is not hashed" do
- it "sets the password in clear text" do
- subject.assign_root_password("12345", false)
- root_user = users_config.users.root
- expect(root_user.password).to eq(Y2Users::Password.create_plain("12345"))
- end
- end
- end
-
- describe "#remove_root_password" do
- it "removes the password" do
- subject.assign_root_password("12345", false)
- root = subject.root_user
- expect(root.password).to be_kind_of(Y2Users::Password)
- subject.remove_root_password
- expect(root.password).to be_nil
- end
- end
-
- describe "#assign_first_user" do
- context "when the options given do not present any issue" do
- it "adds the user to the user's configuration" do
- subject.assign_first_user("Jane Doe", "jane", "12345", false, {})
- user = users_config.users.by_name("jane")
- expect(user.full_name).to eq("Jane Doe")
- expect(user.password).to eq(Y2Users::Password.create_plain("12345"))
- expect(user.groups.map(&:name)).to eq(["wheel"])
- end
-
- context "when a first user exists" do
- before do
- subject.assign_first_user("Jane Doe", "jane", "12345", false, {})
- end
-
- it "replaces the user with the new one" do
- subject.assign_first_user("John Doe", "john", "12345", false, {})
-
- user = users_config.users.by_name("jane")
- expect(user).to be_nil
-
- user = users_config.users.by_name("john")
- expect(user.full_name).to eq("John Doe")
- end
- end
-
- it "returns an empty array of issues" do
- issues = subject.assign_first_user("Jane Doe", "jane", "12345", false, {})
- expect(issues).to be_empty
- end
- end
-
- context "when the given arguments presents some critical error" do
- it "does not add the user to the config" do
- subject.assign_first_user("Jonh Doe", "john", "", false, {})
- user = users_config.users.by_name("john")
- expect(user).to be_nil
- subject.assign_first_user("Ldap user", "ldap", "12345", false, {})
- user = users_config.users.by_name("ldap")
- expect(user).to be_nil
- end
-
- it "returns an array with all the issues" do
- issues = subject.assign_first_user("Root user", "root", "12345", false, {})
- expect(issues.size).to eql(1)
- end
- end
- end
-
- describe "#remove_first_user" do
- before do
- subject.assign_first_user("Jane Doe", "jane", "12345", false, {})
- end
-
- it "removes the already defined first user" do
- expect { subject.remove_first_user }
- .to change { users_config.users.by_name("jane") }
- .from(Y2Users::User).to(nil)
- end
- end
-
- describe "#write" do
- let(:writer) { instance_double(Y2Users::Linux::Writer, write: issues) }
- let(:issues) { [] }
-
- let(:system_config) do
- user = Y2Users::User.create_system("messagebus")
- config = Y2Users::Config.new
- config.attach(user)
- end
-
- before do
- allow(Y2Users::ConfigManager.instance).to receive(:system)
- .with(force_read: true).and_return(system_config)
- allow(Y2Users::Linux::Writer).to receive(:new).and_return(writer)
- allow(Yast::Execute).to receive(:locally!)
- end
-
- it "writes system and installer defined users" do
- subject.assign_first_user("Jane Doe", "jane", "12345", false, {})
-
- expect(Y2Users::Linux::Writer).to receive(:new) do |target_config, _old_config|
- user_names = target_config.users.map(&:name)
- expect(user_names).to include("messagebus", "jane")
- writer
- end
-
- expect(writer).to receive(:write).and_return([])
- subject.write
- end
-
- context "when a SSH public key for the root user is given" do
- let(:firewalld) do
- Y2Firewall::Firewalld.instance
- end
-
- before do
- subject.root_ssh_key = "ssh-rsa ..."
- allow(firewalld).to receive(:installed?).and_return(true)
- end
-
- it "enables the sshd service" do
- expect(Yast::Service).to receive(:Enable).with("sshd")
- expect(firewalld.api).to receive(:add_service).with(firewalld.default_zone, "ssh")
- subject.write
- end
- end
-
- context "when no SSH public key is given" do
- before do
- subject.assign_root_password("", false)
- end
-
- it "disables the root password" do
- expect(subject).to receive(:assign_root_password).with("!", true)
- subject.write
- end
- end
-
- context "if some issue occurs" do
- let(:issues) { [double("issue")] }
-
- it "logs the issue" do
- expect(logger).to receive(:error).with(/issue/)
- subject.write
- end
- end
-
- it "writes without /run bind mounted" do
- expect(Yast::Execute).to receive(:locally!).with(/umount/, anything)
-
- subject.write
- end
-
- describe "#issues" do
- context "when a root password is set" do
- before do
- subject.assign_root_password("123456", true)
- end
-
- it "returns an empty list" do
- expect(subject.issues).to be_empty
- end
- end
-
- context "when a first user is defined" do
- before do
- subject.assign_first_user("Jane Doe", "jdoe", "123456", false, {})
- end
-
- it "returns an empty list" do
- expect(subject.issues).to be_empty
- end
- end
-
- context "when a root SSH key is set" do
- before do
- subject.root_ssh_key = "ssh-rsa ..."
- end
-
- it "returns an empty list" do
- expect(subject.issues).to be_empty
- end
- end
-
- context "when neither a first user is defined nor the root password/SSH key is set" do
- it "returns the problem" do
- error = subject.issues.first
- expect(error.description).to match(/Defining a user/)
- end
- end
- end
- end
-end