diff --git a/.github/workflows/ci-integration-tests.yml b/.github/workflows/ci-integration-tests.yml index 7f7fdf87cb..2b604d1c73 100644 --- a/.github/workflows/ci-integration-tests.yml +++ b/.github/workflows/ci-integration-tests.yml @@ -122,8 +122,8 @@ jobs: run: podman exec agama bash -c "cd /checkout; ./setup-service.sh" - name: Set a testing Agama configuration - # copy a simplified ALP config file, it skips the product selection at the beginning - run: podman exec agama bash -c "cp /checkout/playwright/config/agama.yaml /checkout/service/etc/agama.yaml" + # use just one product, it skips the product selection at the beginning + run: podman exec agama bash -c "rm /checkout/products.d/{leap16,ALP-Dolomite}.yaml" - name: Show NetworkManager log run: podman exec agama journalctl -u NetworkManager diff --git a/.github/workflows/obs-staging-products.yml b/.github/workflows/obs-staging-products.yml new file mode 100644 index 0000000000..d67b067714 --- /dev/null +++ b/.github/workflows/obs-staging-products.yml @@ -0,0 +1,19 @@ +name: Submit agama-products + +on: + # runs on pushes targeting the default branch + push: + branches: + - master + paths: + # run only when a Rust source is changed + - products.d/** + +jobs: + update_staging: + uses: ./.github/workflows/obs-staging-shared.yml + # pass all secrets + secrets: inherit + with: + project_name: systemsmanagement:Agama:Staging + package_name: agama-products-opensuse diff --git a/products.d/ALP-Dolomite.yaml b/products.d/ALP-Dolomite.yaml new file mode 100644 index 0000000000..1f9ac18abb --- /dev/null +++ b/products.d/ALP-Dolomite.yaml @@ -0,0 +1,103 @@ +id: ALP-Dolomite +name: SUSE ALP Dolomite +description: 'SUSE ALP Dolomite is a minimum immutable OS core, focused on + security to provide the bare minimum to run workloads and services as + containers or virtual machines.' +software: + installation_repositories: + - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/x86_64/product/ + archs: x86_64 + - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/aarch64/product/ + archs: aarch64 + - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/s390x/product/ + archs: s390 + - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/ppc64le/product/ + archs: ppc + + mandatory_patterns: + - alp_base_zypper + - alp_cockpit + - alp_hardware + optional_patterns: null # no optional pattern shared + mandatory_packages: + - package: device-mapper # Apparently needed if devices at /dev/mapper are used at boot (eg. FDE) + - package: fde-tools # Needed for FDE with TPM, hardcoded here temporarily + archs: aarch64, x86_64 + - package: libtss2-tcti-device0 + - package: ppc64-diag # Needed for hardware-based installations + archs: ppc64 + optional_packages: null + base_product: ALP-Dolomite + +security: + tpm_luks_open: true + lsm: selinux + available_lsms: + # apparmor: + # patterns: + # - apparmor + selinux: + patterns: + - alp_selinux + policy: enforcing + none: + patterns: null + +storage: + space_policy: delete + encryption: + method: luks2 + pbkd_function: pbkdf2 + volumes: + - "/" + volume_templates: + - mount_path: "/" + filesystem: btrfs + btrfs: + snapshots: true + read_only: true + default_subvolume: "@" + subvolumes: + - path: root + - path: home + - path: opt + - path: srv + - path: boot/writable + - path: usr/local + - path: var + copy_on_write: false + # Architecture specific subvolume + - path: boot/grub2/arm64-efi + archs: aarch64 + - path: boot/grub2/i386-pc + archs: x86_64 + - path: boot/grub2/powerpc-ieee1275 + archs: ppc,!board_powernv + - path: boot/grub2/s390x-emu + archs: s390 + - path: boot/grub2/x86_64-efi + archs: x86_64 + size: + auto: true + outline: + required: true + filesystems: + - btrfs + snapshots_configurable: false + auto_size: + base_min: 5 GiB + base_max: 25 GiB + max_fallback_for: + - "/var" + - mount_path: "/var" + filesystem: btrfs + mount_options: + - "x-initrd.mount" + - "nodatacow" + size: + auto: false + min: 5 GiB + outline: + required: false + filesystems: + - btrfs diff --git a/products.d/agama-products-opensuse.changes b/products.d/agama-products-opensuse.changes new file mode 100644 index 0000000000..f84460af9e --- /dev/null +++ b/products.d/agama-products-opensuse.changes @@ -0,0 +1,5 @@ +------------------------------------------------------------------- +Mon Oct 30 14:38:51 UTC 2023 - Josef Reidinger + +- Initial split of products (gh#openSUSE/agama#602, + gh#openSUSE/agama#822) diff --git a/products.d/agama-products-opensuse.spec b/products.d/agama-products-opensuse.spec new file mode 100644 index 0000000000..bc2af88b9d --- /dev/null +++ b/products.d/agama-products-opensuse.spec @@ -0,0 +1,66 @@ +# +# spec file for package agama-products-opensuse +# +# Copyright (c) 2023 SUSE LINUX GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + +Name: agama-products-opensuse +# This will be set by osc services, that will run after this. +Version: 0 +Release: 0 +Summary: Definition of openSUSE products for the Agama installer +License: GPL-2.0-only +Url: https://github.com/opensuse/agama +BuildArch: noarch +Source0: tumbleweed.yaml +Source1: ALP-Dolomite.yaml +Source2: leap16.yaml + +%description +Products definition for Agama installer. This one is for opensuse products. + +%package -n agama-products-ALP-Dolomite +# This will be set by osc services, that will run after this. +Version: 0 +Release: 0 +Summary: Definition of Dolomite product for the Agama installer +License: GPL-2.0-only +Url: https://github.com/opensuse/agama +BuildArch: noarch + +%description -n agama-products-ALP-Dolomite +Products definition for Agama installer. This one is for ALP Dolomite product. + +%prep + +%build + +%install +install -D -d -m 0755 %{buildroot}%{_datadir}/agama/products.d +install -m 0644 %{SOURCE0} %{buildroot}%{_datadir}/agama/products.d +install -m 0644 %{SOURCE1} %{buildroot}%{_datadir}/agama/products.d +install -m 0644 %{SOURCE2} %{buildroot}%{_datadir}/agama/products.d + +%files +%dir %{_datadir}/agama +%dir %{_datadir}/agama/products.d +%{_datadir}/agama/products.d/tumbleweed.yaml +%{_datadir}/agama/products.d/leap16.yaml + +%files -n agama-products-ALP-Dolomite +%dir %{_datadir}/agama +%dir %{_datadir}/agama/products.d +%{_datadir}/agama/products.d/ALP-Dolomite.yaml + +%changelog diff --git a/products.d/leap16.yaml b/products.d/leap16.yaml new file mode 100644 index 0000000000..85d43fc8f3 --- /dev/null +++ b/products.d/leap16.yaml @@ -0,0 +1,91 @@ +id: Leap16 +name: openSUSE Leap 16.0 +archs: x86_64,aarch64 +description: '[Experimental project] openSUSE Leap 16 is built on top of the next generation Adaptable Linux Platform (ALP) from SUSE.' +software: + installation_repositories: + - url: https://download.opensuse.org/repositories/openSUSE:/Leap:/16.0/images/repo/Leap-16.0-x86_64-Media1/ + archs: x86_64 + - url: https://download.opensuse.org/repositories/openSUSE:/Leap:/16.0/images/repo/Leap-16.0-aarch64-Media1/ + archs: aarch64 + mandatory_patterns: + - alp_base + - alp_base_zypper + - alp_cockpit + - alp-container_runtime + - alp_defaults + optional_patterns: null # no optional pattern shared + mandatory_packages: + - package: device-mapper # Apparently needed if devices at /dev/mapper are used at boot (eg. FDE) + - package: fde-tools # Needed for FDE with TPM, hardcoded here temporarily + archs: aarch64, x86_64 + - package: libtss2-tcti-device0 + optional_packages: null + base_product: Leap16 + +security: + tpm_luks_open: true + lsm: selinux + available_lsms: + # apparmor: + # patterns: + # - apparmor + selinux: + patterns: + - alp_selinux + policy: enforcing + none: + patterns: null + +storage: + space_policy: delete + encryption: + method: luks2 + pbkd_function: pbkdf2 + volumes: + - "/" + volume_templates: + - mount_path: "/" + filesystem: btrfs + btrfs: + snapshots: true + read_only: true + default_subvolume: "@" + subvolumes: + - path: root + - path: home + - path: opt + - path: srv + - path: boot/writable + - path: usr/local + - path: boot/grub2/arm64-efi + archs: aarch64 + - path: boot/grub2/i386-pc + archs: x86_64 + - path: boot/grub2/powerpc-ieee1275 + archs: ppc,!board_powernv + - path: boot/grub2/s390x-emu + archs: s390 + - path: boot/grub2/x86_64-efi + archs: x86_64 + - path: var + copy_on_write: false + size: + auto: false + min: 5 GiB + outline: + required: true + filesystems: + - btrfs + snapshots_configurable: false + - filesystem: xfs + size: + auto: false + outline: + required: false + filesystems: + - btrfs + - ext2 + - ext3 + - ext4 + - xfs diff --git a/products.d/tumbleweed.yaml b/products.d/tumbleweed.yaml new file mode 100644 index 0000000000..732b836815 --- /dev/null +++ b/products.d/tumbleweed.yaml @@ -0,0 +1,140 @@ +id: Tumbleweed +name: openSUSE Tumbleweed +description: 'The Tumbleweed distribution is a pure rolling release version + of openSUSE containing the latest "stable" versions of all software + instead of relying on rigid periodic release cycles. The project does + this for users that want the newest stable software.' +software: + installation_repositories: + - url: https://download.opensuse.org/tumbleweed/repo/oss/ + archs: x86_64 + - url: https://download.opensuse.org/ports/aarch64/tumbleweed/repo/oss/ + archs: aarch64 + - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/oss/ + archs: s390 + - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ + archs: ppc + - url: https://download.opensuse.org/tumbleweed/repo/non-oss/ + archs: x86_64 + # aarch64 does not have non-oss ports. Keep eye if it change + - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/non-oss/ + archs: s390 + - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/non-oss/ + archs: ppc + - url: https://download.opensuse.org/update/tumbleweed/ + archs: x86_64 + - url: https://download.opensuse.org/ports/aarch64/update/tumbleweed/ + archs: aarch64 + - url: https://download.opensuse.org/ports/zsystems/update/tumbleweed/ + archs: s390 + - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ + archs: ppc + mandatory_patterns: + - enhanced_base # only pattern that is shared among all roles on TW + optional_patterns: null # no optional pattern shared + mandatory_packages: + - NetworkManager + optional_packages: null + base_product: openSUSE + +security: + lsm: apparmor + available_lsms: + apparmor: + patterns: + - apparmor + selinux: + patterns: + - selinux + policy: permissive + none: + patterns: null + +storage: + space_policy: delete + volumes: + - "/" + - "swap" + volume_templates: + - mount_path: "/" + filesystem: btrfs + btrfs: + snapshots: true + read_only: false + default_subvolume: "@" + subvolumes: + - path: home + - path: opt + - path: root + - path: srv + - path: usr/local + # Unified var subvolume - https://lists.opensuse.org/opensuse-packaging/2017-11/msg00017.html + - path: var + copy_on_write: false + # Architecture specific subvolumes + - path: boot/grub2/arm64-efi + archs: aarch64 + - path: boot/grub2/arm-efi + archs: arm + - path: boot/grub2/i386-pc + archs: x86_64 + - path: boot/grub2/powerpc-ieee1275 + archs: ppc,!board_powernv + - path: boot/grub2/s390x-emu + archs: s390 + - path: boot/grub2/x86_64-efi + archs: x86_64 + - path: boot/grub2/riscv64-efi + archs: riscv64 + size: + auto: true + outline: + required: true + filesystems: + - btrfs + - ext2 + - ext3 + - ext4 + - xfs + auto_size: + base_min: 5 GiB + base_max: 15 GiB + snapshots_increment: 250% + max_fallback_for: + - "/home" + snapshots_configurable: true + - mount_path: "swap" + filesystem: swap + size: + auto: false + min: 1 GiB + max: 2 GiB + outline: + required: false + filesystems: + - swap + - mount_path: "/home" + filesystem: xfs + size: + auto: false + min: 10 GiB + max: unlimited + outline: + required: false + filesystems: + - btrfs + - ext2 + - ext3 + - ext4 + - xfs + - filesystem: xfs + size: + auto: false + outline: + required: false + filesystems: + - btrfs + - ext2 + - ext3 + - ext4 + - xfs diff --git a/service/agama.gemspec b/service/agama.gemspec index bb2b561d4a..4f254b40b7 100644 --- a/service/agama.gemspec +++ b/service/agama.gemspec @@ -39,7 +39,7 @@ Gem::Specification.new do |spec| spec.email = "yast-devel@opensuse.org" spec.homepage = "https://github.com/openSUSE/agama" spec.license = "GPL-2.0-only" - spec.files = Dir["lib/**/*.rb", "bin/*", "share/*", "etc/*"] + spec.files = Dir["lib/**/*.rb", "bin/*", "share/*", "conf.d/*"] spec.executables = ["agamactl", "agama-proxy-setup"] spec.metadata = { "rubygems_mfa_required" => "true" } diff --git a/service/conf.d/web.yaml b/service/conf.d/web.yaml new file mode 100644 index 0000000000..f300d0f273 --- /dev/null +++ b/service/conf.d/web.yaml @@ -0,0 +1,4 @@ +web: + ssl: null + ssl_cert: null + ssl_key: null diff --git a/service/etc/agama.yaml b/service/etc/agama.yaml index 8192ebf72a..f300d0f273 100644 --- a/service/etc/agama.yaml +++ b/service/etc/agama.yaml @@ -1,346 +1,4 @@ -products: - ALP-Dolomite: - name: SUSE ALP Dolomite - description: 'SUSE ALP Dolomite is a minimum immutable OS core, focused on - security to provide the bare minimum to run workloads and services as - containers or virtual machines.' - Tumbleweed: - name: openSUSE Tumbleweed - description: 'The Tumbleweed distribution is a pure rolling release version - of openSUSE containing the latest "stable" versions of all software - instead of relying on rigid periodic release cycles. The project does - this for users that want the newest stable software.' - Leap16: - name: openSUSE Leap 16.0 - archs: x86_64,aarch64 - description: '[Experimental project] openSUSE Leap 16 is built on top of the next generation Adaptable Linux Platform (ALP) from SUSE.' - web: ssl: null ssl_cert: null ssl_key: null - -ALP-Dolomite: - software: - installation_repositories: - - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/x86_64/product/ - archs: x86_64 - - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/aarch64/product/ - archs: aarch64 - - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/s390x/product/ - archs: s390 - - url: https://updates.suse.com/SUSE/Products/ALP-Dolomite/1.0/ppc64le/product/ - archs: ppc - - mandatory_patterns: - - alp_base_zypper - - alp_cockpit - - alp_hardware - optional_patterns: null # no optional pattern shared - mandatory_packages: - - package: device-mapper # Apparently needed if devices at /dev/mapper are used at boot (eg. FDE) - - package: fde-tools # Needed for FDE with TPM, hardcoded here temporarily - archs: aarch64, x86_64 - - package: libtss2-tcti-device0 - - package: ppc64-diag # Needed for hardware-based installations - archs: ppc64 - optional_packages: null - base_product: ALP-Dolomite - - security: - tpm_luks_open: true - lsm: selinux - available_lsms: - # apparmor: - # patterns: - # - apparmor - selinux: - patterns: - - alp_selinux - policy: enforcing - none: - patterns: null - - storage: - space_policy: delete - encryption: - method: luks2 - pbkd_function: pbkdf2 - volumes: - - "/" - volume_templates: - - mount_path: "/" - filesystem: btrfs - btrfs: - snapshots: true - read_only: true - default_subvolume: "@" - subvolumes: - - path: root - - path: home - - path: opt - - path: srv - - path: boot/writable - - path: usr/local - - path: var - copy_on_write: false - # Architecture specific subvolume - - path: boot/grub2/arm64-efi - archs: aarch64 - - path: boot/grub2/i386-pc - archs: x86_64 - - path: boot/grub2/powerpc-ieee1275 - archs: ppc,!board_powernv - - path: boot/grub2/s390x-emu - archs: s390 - - path: boot/grub2/x86_64-efi - archs: x86_64 - size: - auto: true - outline: - required: true - filesystems: - - btrfs - snapshots_configurable: false - auto_size: - base_min: 5 GiB - base_max: 25 GiB - max_fallback_for: - - "/var" - - mount_path: "/var" - filesystem: btrfs - mount_options: - - "x-initrd.mount" - - "nodatacow" - size: - auto: false - min: 5 GiB - outline: - required: false - filesystems: - - btrfs - -Tumbleweed: - software: - installation_repositories: - - url: https://download.opensuse.org/tumbleweed/repo/oss/ - archs: x86_64 - - url: https://download.opensuse.org/ports/aarch64/tumbleweed/repo/oss/ - archs: aarch64 - - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/oss/ - archs: s390 - - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ - archs: ppc - - url: https://download.opensuse.org/tumbleweed/repo/non-oss/ - archs: x86_64 - # aarch64 does not have non-oss ports. Keep eye if it change - - url: https://download.opensuse.org/ports/zsystems/tumbleweed/repo/non-oss/ - archs: s390 - - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/non-oss/ - archs: ppc - - url: https://download.opensuse.org/update/tumbleweed/ - archs: x86_64 - - url: https://download.opensuse.org/ports/aarch64/update/tumbleweed/ - archs: aarch64 - - url: https://download.opensuse.org/ports/zsystems/update/tumbleweed/ - archs: s390 - - url: https://download.opensuse.org/ports/ppc/tumbleweed/repo/oss/ - archs: ppc - mandatory_patterns: - - enhanced_base # only pattern that is shared among all roles on TW - optional_patterns: null # no optional pattern shared - mandatory_packages: - - NetworkManager - optional_packages: null - base_product: openSUSE - - security: - lsm: apparmor - available_lsms: - apparmor: - patterns: - - apparmor - selinux: - patterns: - - selinux - policy: permissive - none: - patterns: null - - storage: - space_policy: delete - volumes: - - "/" - - "swap" - volume_templates: - - mount_path: "/" - filesystem: btrfs - btrfs: - snapshots: true - read_only: false - default_subvolume: "@" - subvolumes: - - path: home - - path: opt - - path: root - - path: srv - - path: usr/local - # Unified var subvolume - https://lists.opensuse.org/opensuse-packaging/2017-11/msg00017.html - - path: var - copy_on_write: false - # Architecture specific subvolumes - - path: boot/grub2/arm64-efi - archs: aarch64 - - path: boot/grub2/arm-efi - archs: arm - - path: boot/grub2/i386-pc - archs: x86_64 - - path: boot/grub2/powerpc-ieee1275 - archs: ppc,!board_powernv - - path: boot/grub2/s390x-emu - archs: s390 - - path: boot/grub2/x86_64-efi - archs: x86_64 - - path: boot/grub2/riscv64-efi - archs: riscv64 - size: - auto: true - outline: - required: true - filesystems: - - btrfs - - ext2 - - ext3 - - ext4 - - xfs - auto_size: - base_min: 5 GiB - base_max: 15 GiB - snapshots_increment: 250% - max_fallback_for: - - "/home" - snapshots_configurable: true - - mount_path: "swap" - filesystem: swap - size: - auto: false - min: 1 GiB - max: 2 GiB - outline: - required: false - filesystems: - - swap - - mount_path: "/home" - filesystem: xfs - size: - auto: false - min: 10 GiB - max: unlimited - outline: - required: false - filesystems: - - btrfs - - ext2 - - ext3 - - ext4 - - xfs - - filesystem: xfs - size: - auto: false - outline: - required: false - filesystems: - - btrfs - - ext2 - - ext3 - - ext4 - - xfs - -Leap16: - software: - installation_repositories: - - url: https://download.opensuse.org/repositories/openSUSE:/Leap:/16.0/images/repo/Leap-16.0-x86_64-Media1/ - archs: x86_64 - - url: https://download.opensuse.org/repositories/openSUSE:/Leap:/16.0/images/repo/Leap-16.0-aarch64-Media1/ - archs: aarch64 - mandatory_patterns: - - alp_base - - alp_base_zypper - - alp_cockpit - - alp-container_runtime - - alp_defaults - optional_patterns: null # no optional pattern shared - mandatory_packages: - - package: device-mapper # Apparently needed if devices at /dev/mapper are used at boot (eg. FDE) - - package: fde-tools # Needed for FDE with TPM, hardcoded here temporarily - archs: aarch64, x86_64 - - package: libtss2-tcti-device0 - optional_packages: null - base_product: Leap16 - - security: - tpm_luks_open: true - lsm: selinux - available_lsms: - # apparmor: - # patterns: - # - apparmor - selinux: - patterns: - - alp_selinux - policy: enforcing - none: - patterns: null - - storage: - space_policy: delete - encryption: - method: luks2 - pbkd_function: pbkdf2 - volumes: - - "/" - volume_templates: - - mount_path: "/" - filesystem: btrfs - btrfs: - snapshots: true - read_only: true - default_subvolume: "@" - subvolumes: - - path: root - - path: home - - path: opt - - path: srv - - path: boot/writable - - path: usr/local - - path: boot/grub2/arm64-efi - archs: aarch64 - - path: boot/grub2/i386-pc - archs: x86_64 - - path: boot/grub2/powerpc-ieee1275 - archs: ppc,!board_powernv - - path: boot/grub2/s390x-emu - archs: s390 - - path: boot/grub2/x86_64-efi - archs: x86_64 - - path: var - copy_on_write: false - size: - auto: false - min: 5 GiB - outline: - required: true - filesystems: - - btrfs - snapshots_configurable: false - - filesystem: xfs - size: - auto: false - outline: - required: false - filesystems: - - btrfs - - ext2 - - ext3 - - ext4 - - xfs diff --git a/service/lib/agama/config.rb b/service/lib/agama/config.rb index 0a038abadf..698bf1bfd4 100644 --- a/service/lib/agama/config.rb +++ b/service/lib/agama/config.rb @@ -22,6 +22,7 @@ require "yaml" require "yast2/arch_filter" require "agama/config_reader" +require "agama/product_reader" module Agama # Class responsible for getting current configuration. @@ -32,6 +33,7 @@ module Agama class Config # @return [Hash] configuration data attr_accessor :pure_data + attr_accessor :logger class << self attr_accessor :current, :base @@ -51,16 +53,17 @@ def reset # Load the configuration from a given file # # @param path [String|Pathname] File path - def from_file(path) - new(YAML.safe_load(File.read(path.to_s))) + def from_file(path, logger = Logger.new($stdout)) + new(YAML.safe_load(File.read(path.to_s)), logger) end end # Constructor # # @param config_data [Hash] configuration data - def initialize(config_data = nil) + def initialize(config_data = nil, logger = Logger.new($stdout)) @pure_data = config_data + @logger = logger end # parse loaded yaml file, so it properly applies conditions @@ -81,20 +84,30 @@ def data @data end - def pick_product(product) - data.merge!(data[product]) + # Currently product merges its config to global config. + # Keys defined in constant are the ones specific to product that + # should not be merged to global config. + PRODUCT_SPECIFIC_KEYS = ["id", "name", "description"].freeze + def pick_product(product_id) + to_merge = products[product_id] + to_merge = to_merge.reject { |k, _v| PRODUCT_SPECIFIC_KEYS.include?(k) } + data.merge!(to_merge) end - # list of available base products for current architecture + # hash of available base products for current architecture + # @return [Hash{String => Hash}] product_id => product def products return @products if @products - return [] unless @pure_data && @pure_data["products"] - # cannot use `data` here to avoid endless loop as in data we use - # pick_product that select product from products - @products = @pure_data["products"].select do |_key, value| - value["archs"].nil? || - Yast2::ArchFilter.from_string(value["archs"]).match? + products = ProductReader.new(logger: @logger).load_products + + products.select! do |product| + product["archs"].nil? || + Yast2::ArchFilter.from_string(product["archs"]).match? + end + + @products = products.each_with_object({}) do |product, result| + result[product["id"]] = product end end @@ -109,7 +122,13 @@ def multi_product? # # @return [Config] def copy - Marshal.load(Marshal.dump(self)) + logger = self.logger + @logger = nil # cannot dump logger as it can contain IO + res = Marshal.load(Marshal.dump(self)) + @logger = logger + res.logger = logger + + res end # Returns a new {Config} with the merge of the given ones diff --git a/service/lib/agama/config_reader.rb b/service/lib/agama/config_reader.rb index 74084b6a9f..05b708ba17 100644 --- a/service/lib/agama/config_reader.rb +++ b/service/lib/agama/config_reader.rb @@ -65,7 +65,7 @@ def config_from_file(path = nil) raise "Missing config file at #{path}" unless File.exist?(path) logger.info "Reading configuration from #{path}" - Config.from_file(path) + Config.from_file(path, logger) end # Return an arry with the different {Config} objects read from the different locations @@ -85,7 +85,7 @@ def configs # Return a {Config} oject # @return [Config] resultant Config after merging all the configurations def config - config = configs.first || Config.new + config = configs.first || Config.new(nil, logger) (configs[1..-1] || []).each { |c| config = config.merge(c) } config end @@ -122,7 +122,7 @@ def cmdline_args # return [Config] def cmdline_config - Config.new(cmdline_args.data) + Config.new(cmdline_args.data, logger) end # return [Config] diff --git a/service/lib/agama/product_reader.rb b/service/lib/agama/product_reader.rb new file mode 100644 index 0000000000..6f07cab05b --- /dev/null +++ b/service/lib/agama/product_reader.rb @@ -0,0 +1,66 @@ +# 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 "yaml" +require "logger" + +module Agama + # This class is responsible for reading available products definition + # either from system path (`/usr/share/agama/products.d) or the git repo. + class ProductReader + include Yast::I18n + + # Default system path + SYSTEM_PATH = "/usr/share/agama/products.d" + GIT_PATH = File.expand_path("#{__dir__}/../../../products.d") + GIT_DIR = File.expand_path("#{__dir__}/../../../.git") + + attr_reader :logger + + # Constructor + # + # @param logger [Logger] + def initialize(logger: nil) + @logger = logger || ::Logger.new($stdout) + end + + def load_products + glob = File.join(default_path, "*.{yaml,yml}") + Dir.glob(glob).each_with_object([]) do |path, result| + # support also single product file + products = load_file(path) + products = [products] unless products.is_a?(Array) + result.concat(products) + end + end + + private + + def default_path + Dir.exist?(GIT_DIR) ? GIT_PATH : SYSTEM_PATH + end + + def load_file(path) + YAML.safe_load(File.read(path.to_s)) + end + end +end diff --git a/service/lib/agama/security.rb b/service/lib/agama/security.rb index e067ca2e7a..1dafd36a03 100644 --- a/service/lib/agama/security.rb +++ b/service/lib/agama/security.rb @@ -73,7 +73,7 @@ def write end def probe - selected_lsm = config.data["security"]["lsm"] + selected_lsm = config.data.dig("security", "lsm") lsm_config.select(selected_lsm) patterns = if selected_lsm.nil? diff --git a/service/lib/agama/software/manager.rb b/service/lib/agama/software/manager.rb index 4cb264034b..e5d9f256ed 100644 --- a/service/lib/agama/software/manager.rb +++ b/service/lib/agama/software/manager.rb @@ -70,7 +70,7 @@ def initialize(config, logger) @logger = logger @languages = DEFAULT_LANGUAGES @products = @config.products - if @config.multi_product? + if @config.multi_product? || @products.empty? @product = nil else @product = @products.keys.first # use the available product as default diff --git a/service/package/gem2rpm.yml b/service/package/gem2rpm.yml index 5ff69bf1f2..588ea4ac70 100644 --- a/service/package/gem2rpm.yml +++ b/service/package/gem2rpm.yml @@ -10,7 +10,7 @@ install -m 0644 --target-directory=%{buildroot}%{_datadir}/dbus-1/agama-services %{buildroot}%{gem_base}/gems/%{mod_full_name}/share/org.opensuse.Agama*.service install -D -m 0644 %{buildroot}%{gem_base}/gems/%{mod_full_name}/share/agama.service %{buildroot}%{_unitdir}/agama.service install -D -m 0644 %{buildroot}%{gem_base}/gems/%{mod_full_name}/share/agama-proxy-setup.service %{buildroot}%{_unitdir}/agama-proxy-setup.service - install -D -m 0644 %{buildroot}%{gem_base}/gems/%{mod_full_name}/etc/agama.yaml %{buildroot}%{_sysconfdir}/agama.yaml + install -D -m 0644 %{buildroot}%{gem_base}/gems/%{mod_full_name}/conf.d/*.yaml %{buildroot}/usr/lib/agama.d/ :main: :preamble: |- # Override build.rpm, see also https://github.com/openSUSE/obs-build/blob/master/configs/ @@ -39,4 +39,4 @@ %{_datadir}/dbus-1/agama-services/org.opensuse.Agama*.service\n %{_unitdir}/agama.service\n %{_unitdir}/agama-proxy-setup.service\n - %config %{_sysconfdir}/agama.yaml\n" + /usr/lib/agama.d\n" diff --git a/service/run_tests_in_container.sh b/service/run_tests_in_container.sh new file mode 100644 index 0000000000..429f2aa6ce --- /dev/null +++ b/service/run_tests_in_container.sh @@ -0,0 +1,18 @@ +#! /bin/bash + +set -ex +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +podman create -ti --rm --entrypoint '["sh", "-c"]' --name agama_ruby_tests -v $SCRIPT_DIR/..:/checkout registry.opensuse.org/yast/head/containers_tumbleweed/yast-ruby sh +podman start agama_ruby_tests +podman exec agama_ruby_tests zypper --non-interactive install yast2-iscsi-client ruby3.2-rubygem-eventmachine +if podman exec --workdir /checkout/service agama_ruby_tests rake test:unit; then + if [ "$KEEP_RUNNING" != "1" ]; then + podman stop agama_ruby_tests + fi + echo "Tests passed" +else + echo "Tests failed" + echo "To get into container use:"; echo " podman attach agama_ruby_tests" + echo " cd /checkout" + echo "To remove container use:"; echo " podman rm agama_ruby_tests" +fi diff --git a/service/test/agama/config_reader_test.rb b/service/test/agama/config_reader_test.rb index c7e115f77e..ea4ea3f7e8 100644 --- a/service/test/agama/config_reader_test.rb +++ b/service/test/agama/config_reader_test.rb @@ -35,7 +35,7 @@ it "returns a Config object with the configuration read from the given file" do config = subject.config_from_file(File.join(workdir, "etc", "agama.yaml")) expect(config).to be_a(Agama::Config) - expect(config.data["products"].keys).to include("Tumbleweed") + expect(config.data["web"].keys).to include("ssl") end end diff --git a/service/test/agama/config_test.rb b/service/test/agama/config_test.rb index c61714ee9c..84ede3e81e 100644 --- a/service/test/agama/config_test.rb +++ b/service/test/agama/config_test.rb @@ -25,10 +25,6 @@ describe Agama::Config do let(:config) { described_class.new("web" => { "ssl" => "SOMETHING" }) } - before do - allow_any_instance_of(Agama::ConfigReader).to receive(:config).and_return(config) - end - describe ".load" do before do described_class.reset @@ -53,12 +49,12 @@ File.join(FIXTURES_PATH, "root_dir", "etc", "agama.yaml") ) expect(config).to be_a(described_class) - expect(config.data["products"].size).to eq(3) end end describe ".reset" do it "resets base and current configuration" do + allow_any_instance_of(Agama::ConfigReader).to receive(:config).and_return(config) described_class.load expect { described_class.reset }.to change { described_class.base }.from(config).to(nil) .and change { described_class.current }.to(nil) @@ -91,17 +87,27 @@ describe "#products" do it "returns products available for current hardware" do - subject = described_class.from_file(File.join(FIXTURES_PATH, "agama-archs.yaml")) - expect(subject.products.size).to eq 2 + allow(Agama::ProductReader).to receive(:new).and_return(double(load_products: + [ + { + "id" => "test", + "archs" => "x86_64" + }, + { + "id" => "test2", + "archs" => "s390x" + } + ])) + expect(Yast2::ArchFilter).to receive(:from_string).twice.and_return(double(match?: true), + double(match?: false)) + expect(subject.products.size).to eq 1 end end describe "#multi_product?" do context "when more than one product is defined" do - subject do - described_class.from_file( - File.join(FIXTURES_PATH, "root_dir", "etc", "agama.yaml") - ) + before do + allow(Agama::ProductReader).to receive(:new).and_call_original end it "returns true" do @@ -110,11 +116,14 @@ end context "when just one product is defined" do - subject do - described_class.from_file(File.join(FIXTURES_PATH, "agama-single.yaml")) + before do + allow(Agama::ProductReader).to receive(:new).and_call_original + products = Agama::ProductReader.new.load_products + allow(Agama::ProductReader).to receive(:new) + .and_return(double(load_products: [products.first])) end - it "returns true" do + it "returns false" do expect(subject.multi_product?).to eq(false) end end diff --git a/service/test/agama/dbus/server_manager_test.rb b/service/test/agama/dbus/server_manager_test.rb index 6fe836c83d..496d49beb4 100644 --- a/service/test/agama/dbus/server_manager_test.rb +++ b/service/test/agama/dbus/server_manager_test.rb @@ -65,7 +65,7 @@ describe "#start_server" do it "starts the dbus-daemon and returns the PID" do expect(Process).to receive(:spawn) - .with(/dbus-daemon/, "--config-file", /dbus.conf/, any_args) + .with(/dbus-daemon/, "--config-file", any_args) # config file location depends on pwd .and_return(1000) expect(Process).to receive(:detach).with(1000) expect(subject.start_server).to eq(1000) diff --git a/service/test/agama/software/manager_test.rb b/service/test/agama/software/manager_test.rb index 1dd557a6f1..a932423199 100644 --- a/service/test/agama/software/manager_test.rb +++ b/service/test/agama/software/manager_test.rb @@ -70,6 +70,8 @@ before do allow(Yast::Pkg).to receive(:TargetInitialize) allow(Yast::Pkg).to receive(:ImportGPGKey) + # 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("") @@ -79,6 +81,7 @@ allow(Agama::DBus::Clients::Questions).to receive(:new).and_return(questions_client) allow(Agama::Software::RepositoriesManager).to receive(:new).and_return(repositories) allow(Agama::Software::Proposal).to receive(:new).and_return(proposal) + allow(Agama::ProductReader).to receive(:new).and_call_original end describe "#probe" do @@ -116,7 +119,7 @@ end it "registers the repository from config" do - expect(repositories).to receive(:add).with(/tumbleweed/) + expect(repositories).to receive(:add).with(/Dolomite/) expect(repositories).to receive(:load) subject.probe end @@ -126,9 +129,8 @@ it "returns the list of known products" do products = subject.products expect(products.size).to eq(3) - id, data = products.first - expect(id).to eq("Tumbleweed") - expect(data).to include( + expect(products["Tumbleweed"]).to_not eq nil + expect(products["Tumbleweed"]).to include( "name" => "openSUSE Tumbleweed", "description" => String ) @@ -152,16 +154,11 @@ expect(proposal).to receive(:set_resolvables) .with("agama", :pattern, ["enhanced_base"]) expect(proposal).to receive(:set_resolvables) - .with("agama", :pattern, ["optional_base"], optional: true) + .with("agama", :pattern, [], { optional: true }) expect(proposal).to receive(:set_resolvables) - .with("agama", :package, ["mandatory_pkg"]) + .with("agama", :package, ["NetworkManager"]) expect(proposal).to receive(:set_resolvables) - .with("agama", :package, ["optional_pkg"], optional: true) - subject.propose - - expect(Yast::Arch).to receive(:s390).and_return(true) - expect(proposal).to receive(:set_resolvables) - .with("agama", :package, ["mandatory_pkg", "mandatory_pkg_s390"]) + .with("agama", :package, [], { optional: true }) subject.propose end end diff --git a/service/test/fixtures/root_dir/etc/agama.yaml b/service/test/fixtures/root_dir/etc/agama.yaml index c87e4013ad..5ae38001f0 100644 --- a/service/test/fixtures/root_dir/etc/agama.yaml +++ b/service/test/fixtures/root_dir/etc/agama.yaml @@ -1,296 +1,17 @@ -products: - Tumbleweed: - name: openSUSE Tumbleweed - description: 'The Tumbleweed distribution is a pure rolling release version - of openSUSE containing the latest "stable" versions of all software - instead of relying on rigid periodic release cycles. The project does - this for users that want the newest stable software.' - Leap Micro: - name: openSUSE Leap Micro 5.2 - description: 'Leap Micro is an ultra-reliable, lightweight operating system - built for containerized and virtualized workloads. This community version - is based on SUSE Linux Enterprise Micro, which leverages the enterprise - hardened security and compliance components of SUSE Linux Enterprise.' - Leap: - name: openSUSE Leap 15.4 - description: 'Leap uses source from SUSE Linux Enterprise (SLE), which - gives Leap a level of stability unmatched by other Linux distributions, - and combines that with community developments to give users, developers - and sysadmins the best stable Linux experience available.' - web: ssl: null ssl_cert: null ssl_key: null - -Tumbleweed: - software: - installation_repositories: - - https://download.opensuse.org/tumbleweed/repo/oss/ - - https://download.opensuse.org/tumbleweed/repo/non-oss/ - - https://download.opensuse.org/update/tumbleweed/ - mandatory_patterns: - - enhanced_base # only pattern that is shared among all roles on TW - optional_patterns: - - optional_base - mandatory_packages: - - package: mandatory_pkg - - package: mandatory_pkg_s390 - archs: s390 - optional_packages: - - optional_pkg - base_product: openSUSE - - security: - lsm: apparmor - available_lsms: - apparmor: - patterns: - - apparmor - selinux: - patterns: - - selinux - policy: permissive - none: - patterns: null - - storage: - volumes: - - "/" - - "swap" - volume_templates: - - mount_path: "/" - filesystem: btrfs - btrfs: - snapshots: true - read_only_root: true - default_subvolume: "@" - subvolumes: - - path: home - - path: opt - - path: root - - path: srv - - path: usr/local - # Unified var subvolume - https://lists.opensuse.org/opensuse-packaging/2017-11/msg00017.html - - path: var - copy_on_write: false - - # Architecture specific subvolumes - - path: boot/grub2/arm64-efi - archs: aarch64 - - path: boot/grub2/arm-efi - archs: arm - - path: boot/grub2/i386-pc - archs: x86_64 - - path: boot/grub2/powerpc-ieee1275 - archs: ppc,!board_powernv - - path: boot/grub2/s390x-emu - archs: s390 - - path: boot/grub2/x86_64-efi - archs: x86_64 - - path: boot/grub2/riscv64-efi - archs: riscv64 - - size: - auto: true - outline: - required: true - auto_size: - base_min: 5 GiB - base_max: 20 GiB - snapshots_increment: 10 GiB - min_fallback_for: - - "/home" - - "/home" - snapshots_configurable: true - - - mount_path: "/home" - filesystem: xfs - size: - auto: false - min: 10 GiB - max: unlimited - outline: - required: false - - - mount_path: "swap" - filesystem: swap - size: - auto: false - min: 1 GiB - max: 2 GiB - outline: - required: false - -Leap: - software: - installation_repositories: - # TODO: support somehow $releasever in URL - - https://download.opensuse.org/distribution/leap/15.4/repo/oss/ - - https://download.opensuse.org/distribution/leap/15.4/repo/non-oss/ - - https://download.opensuse.org/update/leap/15.4/oss/ - - https://download.opensuse.org/update/leap/15.4/non-oss/ - - https://download.opensuse.org/update/leap/15.4/sle/ - - https://download.opensuse.org/update/leap/15.4/backports/ - mandatory_patterns: - - enhanced_base # For now lets pick some minimal one - optional_patterns: null # no optional pattern shared - base_product: Leap - - security: - # TODO: check if skelcd for Leap 15.4 is correct as code is using lsm in globals, but skelcd contain selinux only - lsm: apparmor - available_lsms: - apparmor: - patterns: - - apparmor - selinux: - patterns: - - selinux - policy: disabled - none: - patterns: null - - storage: - volumes: - - "/" - - "swap" - volume_templates: - - mount_path: "/" - filesystem: btrfs - btrfs: - snapshots: true - read_only_root: true - default_subvolume: "@" - subvolumes: - - path: home - - path: opt - - path: root - - path: srv - - path: usr/local - # Unified var subvolume - https://lists.opensuse.org/opensuse-packaging/2017-11/msg00017.html - - path: var - copy_on_write: false - - # Architecture specific subvolumes - - path: boot/grub2/arm64-efi - archs: aarch64 - - path: boot/grub2/arm-efi - archs: arm - - path: boot/grub2/i386-pc - archs: x86_64 - - path: boot/grub2/powerpc-ieee1275 - archs: ppc,!board_powernv - - path: boot/grub2/s390x-emu - archs: s390 - - path: boot/grub2/x86_64-efi - archs: x86_64 - - path: boot/grub2/riscv64-efi - archs: riscv64 - - size: - auto: true - outline: - required: true - auto_size: - base_min: 5 GiB - base_max: 20 GiB - snapshots_increment: 10 GiB - min_fallback_for: - - "/home" - - "/home" - snapshots_configurable: true - - - mount_path: "/home" - filesystem: xfs - size: - auto: false - min: 10 GiB - max: unlimited - outline: - required: false - - - mount_path: "swap" - filesystem: swap - size: - auto: false - min: 1 GiB - max: 2 GiB - outline: - required: false - -Leap Micro: - software: - installation_repositories: - - https://download.opensuse.org/distribution/leap-micro/5.2/product/repo/Leap-Micro-5.2-x86_64-Media/ - mandatory_patterns: - - microos-base - - microos-hardware - - microos-bootloader - - microos-defaults - - microos-basesystem - optional_patterns: null # no optional pattern shared - base_product: Leap-Micro - - security: - lsm: selinux - available_lsms: - selinux: - patterns: - - microos-selinux - policy: enforcing - none: - patterns: null - - storage: - volumes: - - "/" - - "/var" - volume_templates: - - mount_path: "/" - filesystem: btrfs - btrfs: - snapshots: true - read_only_root: true - default_subvolume: "@" - subvolumes: - - path: root - - path: home - - path: opt - - path: srv - - path: root - - path: boot/writable - - path: usr/local - - path: boot/grub2/arm64-efi - archs: aarch64 - - path: boot/grub2/i386-pc - archs: x86_64 - - path: boot/grub2/powerpc-ieee1275 - archs: ppc,!board_powernv - - path: boot/grub2/s390x-emu - archs: s390 - - path: boot/grub2/x86_64-efi - archs: x86_64 - - size: - auto: true - outline: - required: true - auto_size: - base_min: 5 GiB - base_max: 20 GiB - snapshots_increment: 10 GiB - min_fallback_for: - - "/var" - - "/var" - snapshots_configurable: false - - - mount_path: "/var" - filesystem: btrfs - size: - auto: false - min: 5 GiB - max: unlimited - outline: - required: false +security: + lsm: apparmor + available_lsms: + apparmor: + patterns: + - apparmor + selinux: + patterns: + - selinux + policy: permissive + none: + patterns: null diff --git a/service/test/test_helper.rb b/service/test/test_helper.rb index ff1f933d46..4340322ca0 100644 --- a/service/test/test_helper.rb +++ b/service/test/test_helper.rb @@ -26,6 +26,8 @@ FIXTURES_PATH = File.expand_path("fixtures", __dir__) $LOAD_PATH.unshift(SRC_PATH) +require "agama/product_reader" # to globally mock reading real products + # make sure we run the tests in English locale # (some tests check the output which is marked for translation) ENV["LC_ALL"] = "en_US.UTF-8" @@ -42,6 +44,13 @@ def require(path) end end +RSpec.configure do |c| + c.before do + allow(Agama::ProductReader).to receive(:new) + .and_return(double(load_products: [])) + end +end + if ENV["COVERAGE"] require "simplecov" SimpleCov.start do