Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Install from local DVD if it is present #1372

Merged
merged 8 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/yaml_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions products.d/microos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ 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
- 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
Expand Down
10 changes: 10 additions & 0 deletions products.d/tumbleweed.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ 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
- 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
Expand Down
2 changes: 1 addition & 1 deletion service/lib/agama/product_reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 67 additions & 2 deletions service/lib/agama/software/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# find current contact information at www.suse.com.

require "fileutils"
require "json"
require "yast"
require "y2packager/product"
require "y2packager/resolvable"
Expand Down Expand Up @@ -420,9 +421,66 @@ def import_gpg_keys
end

def add_base_repos
# 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
end
end

# disk label not found or not configured, use the online repositories
product.repositories.each { |url| repositories.add(url) }
end

# find all devices with the required disk label
# @return [Array<String>] 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`
Copy link
Contributor

Choose a reason for hiding this comment

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

lsblk is included in util-linux-systemd. Should we add a dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add that...

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
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess it is a corner case but, what happens if I select the disk for installing the system? Do we need any kind of filtering?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The disk will not be available for partitioning/installation. But if you have more than one disk it should work (one for RPM packages, second as the installation target).

But I guess all these scenarios are rather theoretical and look a bit strange to me...

end
end

# Adds resolvables for selected product
def select_resolvables
proposal.set_resolvables("agama", :pattern, product.mandatory_patterns)
Expand Down Expand Up @@ -538,16 +596,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
Expand Down
6 changes: 6 additions & 0 deletions service/lib/agama/software/product.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class Product
# @return [Array<String>] Empty if the product requires registration.
attr_accessor :repositories

# List of disk labels used for installation repository.
#
# @return [Array<String>] Empty if the product does not support offline installation.
attr_accessor :labels

# Mandatory packages.
#
# @return [Array<String>]
Expand Down Expand Up @@ -95,6 +100,7 @@ class Product
def initialize(id)
@id = id
@repositories = []
@labels = []
@mandatory_packages = []
@optional_packages = []
@mandatory_patterns = []
Expand Down
35 changes: 21 additions & 14 deletions service/lib/agama/software/product_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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]
Expand All @@ -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
),
Expand Down
2 changes: 2 additions & 0 deletions service/package/gem2rpm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions service/test/agama/software/manager_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
Loading