From d5691a3d06824210b532f7475246474cb71fa101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 10:28:52 +0200 Subject: [PATCH 1/8] Install from local DVD if it is present Use the standard DVD installation medium if it is present, otherwise use the online repositories. --- products.d/microos.yaml | 9 +++ products.d/tumbleweed.yaml | 9 +++ service/lib/agama/software/manager.rb | 58 ++++++++++++++++++- service/lib/agama/software/product.rb | 6 ++ service/lib/agama/software/product_builder.rb | 35 ++++++----- 5 files changed, 101 insertions(+), 16 deletions(-) diff --git a/products.d/microos.yaml b/products.d/microos.yaml index 85289b42e2..ec527c2e4f 100644 --- a/products.d/microos.yaml +++ b/products.d/microos.yaml @@ -98,6 +98,15 @@ software: archs: s390 - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ archs: ppc + installation_labels: + - label: openSUSE-MicroOS-DVD-x86_64 + archs: x86_64 + - label: openSUSE-MicroOS-DVD-aarch64 + archs: aarch64 + - label: openSUSE-MicroOS-DVD-s390x + archs: s390 + - label: openSUSE-MicroOS-DVD-ppc64le + archs: ppc mandatory_patterns: - microos_base - microos_base_zypper diff --git a/products.d/tumbleweed.yaml b/products.d/tumbleweed.yaml index ca7fade155..37b56e3609 100644 --- a/products.d/tumbleweed.yaml +++ b/products.d/tumbleweed.yaml @@ -91,6 +91,15 @@ software: archs: s390 - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ archs: ppc + installation_labels: + - label: openSUSE-Tumbleweed-DVD-x86_64 + archs: x86_64 + - label: openSUSE-Tumbleweed-DVD-aarch64 + archs: aarch64 + - label: openSUSE-Tumbleweed-DVD-s390x + archs: s390 + - label: openSUSE-Tumbleweed-DVD-ppc64le + archs: ppc mandatory_patterns: - enhanced_base # only pattern that is shared among all roles on TW optional_patterns: null # no optional pattern shared diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb index 3a8ef6734e..7266e0b06b 100644 --- a/service/lib/agama/software/manager.rb +++ b/service/lib/agama/software/manager.rb @@ -20,6 +20,7 @@ # find current contact information at www.suse.com. require "fileutils" +require "json" require "yast" require "y2packager/product" require "y2packager/resolvable" @@ -420,9 +421,55 @@ def import_gpg_keys end def add_base_repos + # 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 + end + end + + # disk label not found or not configured, use the online repositories product.repositories.each { |url| repositories.add(url) } end + def disks_with_label(label) + data = list_disks + disks = data["blockdevices"].map { |device| device["kname"] if device["label"] == label } + disks.compact! + logger.info "Disks with the installation label: #{disks.inspect}" + disks + end + + # get list of disks + def list_disks + # we need only the kernel device name and the label + output = `lsblk --paths --json --output kname,label` + JSON.parse(output) + end + + 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 + # Adds resolvables for selected product def select_resolvables proposal.set_resolvables("agama", :pattern, product.mandatory_patterns) @@ -538,16 +585,23 @@ def copy_zypp_to_target FileUtils.copy(glob_credentials, target_dir) 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 + # 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 + 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 diff --git a/service/lib/agama/software/product.rb b/service/lib/agama/software/product.rb index 9e1179ce3a..5ea4e1fd69 100644 --- a/service/lib/agama/software/product.rb +++ b/service/lib/agama/software/product.rb @@ -53,6 +53,11 @@ class Product # @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] @@ -95,6 +100,7 @@ class Product def initialize(id) @id = id @repositories = [] + @labels = [] @mandatory_packages = [] @optional_packages = [] @mandatory_patterns = [] diff --git a/service/lib/agama/software/product_builder.rb b/service/lib/agama/software/product_builder.rb index c048be304e..465e2107fd 100644 --- a/service/lib/agama/software/product_builder.rb +++ b/service/lib/agama/software/product_builder.rb @@ -36,20 +36,7 @@ def initialize(config) def build config.products.map do |id, attrs| data = product_data_from_config(id) - - 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.repositories = data[:repositories] - 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 = data[:user_patterns] - product.translations = attrs["translations"] || {} - end + create_product(id, data, attrs) end end @@ -58,6 +45,23 @@ def build # @return [Agama::Config] attr_reader :config + def create_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.repositories = data[:repositories] + 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 = data[:user_patterns] + product.translations = attrs["translations"] || {} + end + end + # Data from config, filtering by arch. # # @param id [String] @@ -66,6 +70,9 @@ def product_data_from_config(id) { name: config.products.dig(id, "software", "base_product"), version: config.products.dig(id, "software", "version"), + labels: config.arch_elements_from( + id, "software", "installation_labels", property: :label + ), repositories: config.arch_elements_from( id, "software", "installation_repositories", property: :url ), From 1919d5c15fa72ffe1e972f569ecbed2d95961369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 14:43:29 +0200 Subject: [PATCH 2/8] Documentation --- doc/yaml_config.md | 10 ++++++++++ products.d/microos.yaml | 1 + products.d/tumbleweed.yaml | 1 + service/lib/agama/software/manager.rb | 8 +++++++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/yaml_config.md b/doc/yaml_config.md index 70210e1395..5bab27c3a7 100644 --- a/doc/yaml_config.md +++ b/doc/yaml_config.md @@ -30,6 +30,16 @@ Array of url for installation repositories. Map can be used instead of string. In such case map should contain url and archs keys. Archs key is used to limit usage of repository on matching hardware architectures. +#### installation\_labels + +Array of disk labels used for finding the local installation repository. Instead +of array of strings it is possible to use an array of maps with `label` and +`archs` keys where `archs` is a string with comma separated list of supported +hardware architectures. + +If the matching disk label is not found then the online installation repository +from the `installation_repositories` section is used. + #### mandatory\_patterns Array of patterns that have to be selected. diff --git a/products.d/microos.yaml b/products.d/microos.yaml index ec527c2e4f..07cbd3bfb8 100644 --- a/products.d/microos.yaml +++ b/products.d/microos.yaml @@ -98,6 +98,7 @@ software: archs: s390 - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ archs: ppc + # device labels for offline installation media installation_labels: - label: openSUSE-MicroOS-DVD-x86_64 archs: x86_64 diff --git a/products.d/tumbleweed.yaml b/products.d/tumbleweed.yaml index 37b56e3609..d66e79f703 100644 --- a/products.d/tumbleweed.yaml +++ b/products.d/tumbleweed.yaml @@ -91,6 +91,7 @@ software: archs: s390 - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ archs: ppc + # device labels for offline installation media installation_labels: - label: openSUSE-Tumbleweed-DVD-x86_64 archs: x86_64 diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb index 7266e0b06b..e44e7fa5ce 100644 --- a/service/lib/agama/software/manager.rb +++ b/service/lib/agama/software/manager.rb @@ -440,6 +440,9 @@ def add_base_repos product.repositories.each { |url| repositories.add(url) } 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["blockdevices"].map { |device| device["kname"] if device["label"] == label } @@ -448,13 +451,16 @@ def disks_with_label(label) disks end - # get list of disks + # 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) 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) From 8b0650f6456c052e8547fca20960f15f65a62e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 15:12:59 +0200 Subject: [PATCH 3/8] Error handling --- service/lib/agama/software/manager.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb index e44e7fa5ce..29b072c61f 100644 --- a/service/lib/agama/software/manager.rb +++ b/service/lib/agama/software/manager.rb @@ -445,7 +445,7 @@ def add_base_repos # returns empty list if there is no device with the required label def disks_with_label(label) data = list_disks - disks = data["blockdevices"].map { |device| device["kname"] if device["label"] == label } + disks = data.fetch("blockdevices", []).map { |device| device["kname"] if device["label"] == label } disks.compact! logger.info "Disks with the installation label: #{disks.inspect}" disks @@ -457,6 +457,9 @@ def list_disks # we need only the kernel device name and the label output = `lsblk --paths --json --output kname,label` JSON.parse(output) + rescue RuntimeError e + logger.error "ERROR: Cannot read disk devices: #{e}" + {} end # find the installation device with the required label From 8c754a62da58029f9c49e626d67b89766ecd29df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 15:18:42 +0200 Subject: [PATCH 4/8] Rubocop fix --- service/lib/agama/software/manager.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb index 29b072c61f..fe99f70c50 100644 --- a/service/lib/agama/software/manager.rb +++ b/service/lib/agama/software/manager.rb @@ -445,7 +445,9 @@ def add_base_repos # 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 { |device| device["kname"] if device["label"] == label } + 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 @@ -457,7 +459,7 @@ def list_disks # we need only the kernel device name and the label output = `lsblk --paths --json --output kname,label` JSON.parse(output) - rescue RuntimeError e + rescue RuntimeError => e logger.error "ERROR: Cannot read disk devices: #{e}" {} end From 72e038d21d8df4f5b91075ff87a4909e445cdf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 15:26:49 +0200 Subject: [PATCH 5/8] Error handling --- service/lib/agama/software/manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb index fe99f70c50..9198bb2d64 100644 --- a/service/lib/agama/software/manager.rb +++ b/service/lib/agama/software/manager.rb @@ -459,7 +459,7 @@ def list_disks # we need only the kernel device name and the label output = `lsblk --paths --json --output kname,label` JSON.parse(output) - rescue RuntimeError => e + rescue StandardError => e logger.error "ERROR: Cannot read disk devices: #{e}" {} end From 68a5a14852e19d9336bb2b0937af84ce27763db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 16:38:37 +0200 Subject: [PATCH 6/8] Update unit tests --- service/lib/agama/product_reader.rb | 2 +- service/test/agama/software/manager_test.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/service/lib/agama/product_reader.rb b/service/lib/agama/product_reader.rb index 453e9a23b6..96acc58ef3 100644 --- a/service/lib/agama/product_reader.rb +++ b/service/lib/agama/product_reader.rb @@ -49,7 +49,7 @@ def initialize(logger: nil) def load_products glob = File.join(default_path, "*.{yaml,yml}") Dir.glob(glob).each_with_object([]) do |path, result| - products = YAML.safe_load_file(path) + products = YAML.safe_load(File.read(path)) products = [products] unless products.is_a?(Array) result.concat(products) end diff --git a/service/test/agama/software/manager_test.rb b/service/test/agama/software/manager_test.rb index d121571a54..34d6458b7c 100644 --- a/service/test/agama/software/manager_test.rb +++ b/service/test/agama/software/manager_test.rb @@ -215,6 +215,7 @@ describe "#probe" do before do subject.select_product("Tumbleweed") + allow(subject).to receive(:list_disks).and_return({}) end it "creates a packages proposal" do @@ -228,6 +229,21 @@ 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 From 205260c913925eb1cf195fc2d4709bcc3b43bbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 17:57:47 +0200 Subject: [PATCH 7/8] Update service/lib/agama/software/manager.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit comment note Co-authored-by: Imobach González Sosa --- service/lib/agama/software/manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb index 9198bb2d64..c7c56f5b7c 100644 --- a/service/lib/agama/software/manager.rb +++ b/service/lib/agama/software/manager.rb @@ -421,7 +421,7 @@ def import_gpg_keys end def add_base_repos - # support multiple labels/installation media? + # NOTE: support multiple labels/installation media? label = product.labels.first if label From b5080e087c1870b50965442c6dc783be425d52cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Tue, 25 Jun 2024 21:00:45 +0200 Subject: [PATCH 8/8] Added util-linux-systemd dependency --- service/package/gem2rpm.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/service/package/gem2rpm.yml b/service/package/gem2rpm.yml index 7fab722649..c95995ae40 100644 --- a/service/package/gem2rpm.yml +++ b/service/package/gem2rpm.yml @@ -74,6 +74,8 @@ Requires: udftools Requires: xfsprogs Requires: yast2-schema + # lsblk + Requires: util-linux-systemd :filelist: "%{_datadir}/dbus-1/agama.conf\n %dir %{_datadir}/dbus-1/agama-services\n %{_datadir}/dbus-1/agama-services/org.opensuse.Agama*.service\n