From 2e507388bc82cbb4e2d1e1b489395046edd927d4 Mon Sep 17 00:00:00 2001 From: Yuriy Kohut Date: Fri, 6 Feb 2026 16:38:03 +0200 Subject: [PATCH] Update Vendors patch against upstream 78640b665ca1c94c51b397cb35f9aac2f3cbb5c4 (0.23.0-3) The package version 0.23.0-3.elevate.3 --- SOURCES/leapp-repository-0.23.0-elevate.patch | 15684 +++++++++++++++- SPECS/leapp-repository.spec | 5 +- 2 files changed, 15129 insertions(+), 560 deletions(-) diff --git a/SOURCES/leapp-repository-0.23.0-elevate.patch b/SOURCES/leapp-repository-0.23.0-elevate.patch index 8aebb6a..bd9f840 100644 --- a/SOURCES/leapp-repository-0.23.0-elevate.patch +++ b/SOURCES/leapp-repository-0.23.0-elevate.patch @@ -63,6 +63,273 @@ index 0bb92d3d..a04c7ded 100644 # pycharm .idea +diff --git a/.packit.yaml b/.packit.yaml +index e158c7e4..37fa7849 100644 +--- a/.packit.yaml ++++ b/.packit.yaml +@@ -123,15 +123,6 @@ jobs: + tmt: + plan_filter: 'tag:8to9' + environments: +- - &tmt-env-settings-810to94 +- tmt: +- context: &tmt-context-810to94 +- distro: "rhel-8.10" +- distro_target: "rhel-9.4" +- settings: +- provisioning: +- tags: +- BusinessUnit: sst_upgrades@leapp_upstream_test + - &tmt-env-settings-810to96 + tmt: + context: &tmt-context-810to96 +@@ -141,15 +132,6 @@ jobs: + provisioning: + tags: + BusinessUnit: sst_upgrades@leapp_upstream_test +- - &tmt-env-settings-810to97 +- tmt: +- context: &tmt-context-810to97 +- distro: "rhel-8.10" +- distro_target: "rhel-9.7" +- settings: +- provisioning: +- tags: +- BusinessUnit: sst_upgrades@leapp_upstream_test + - &tmt-env-settings-810to98 + tmt: + context: &tmt-context-810to98 +@@ -190,67 +172,6 @@ jobs: + # ######################### Individual tests ########################### # + # ###################################################################### # + +-# ###################################################################### # +-# ############################# 8.10 > 9.4 ############################# # +-# ###################################################################### # +- +-- &sanity-810to94 +- <<: *sanity-abstract-8to9 +- trigger: pull_request +- identifier: sanity-8.10to9.4 +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:tier0 & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-810to94 +- env: &env-810to94 +- SOURCE_RELEASE: "8.10" +- TARGET_RELEASE: "9.4" +- LEAPP_TARGET_PRODUCT_CHANNEL: "EUS" +- +-# On-demand minimal beaker tests +-- &beaker-minimal-810to94 +- <<: *beaker-minimal-8to9-abstract-ondemand +- trigger: pull_request +- labels: +- - beaker-minimal +- - beaker-minimal-8.10to9.4 +- - 8.10to9.4 +- identifier: sanity-8.10to9.4-beaker-minimal-ondemand +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:partitioning & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-810to94 +- env: +- <<: *env-810to94 +- +-# On-demand kernel-rt tests +-- &kernel-rt-810to94 +- <<: *kernel-rt-abstract-8to9-ondemand +- trigger: pull_request +- labels: +- - kernel-rt +- - kernel-rt-8.10to9.4 +- - 8.10to9.4 +- identifier: sanity-8.10to9.4-kernel-rt-ondemand +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true & tag:-rhsm' +- environments: +- - tmt: +- context: *tmt-context-810to94 +- settings: +- provisioning: +- tags: +- BusinessUnit: sst_upgrades@leapp_upstream_test +- env: +- <<: *env-810to94 +- +- + # ###################################################################### # + # ############################# 8.10 > 9.6 ############################# # + # ###################################################################### # +@@ -327,60 +248,6 @@ jobs: + <<: *env-810to96 + + +-# ###################################################################### # +-# ############################# 8.10 > 9.7 ############################# # +-# ###################################################################### # +- +-- &sanity-810to97 +- <<: *sanity-abstract-8to9 +- trigger: pull_request +- identifier: sanity-8.10to9.7 +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:tier0 & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-810to97 +- env: &env-810to97 +- SOURCE_RELEASE: "8.10" +- TARGET_RELEASE: "9.7" +- +-# On-demand minimal beaker tests +-- &beaker-minimal-810to97 +- <<: *beaker-minimal-8to9-abstract-ondemand +- trigger: pull_request +- labels: +- - beaker-minimal +- - beaker-minimal-8.10to9.7 +- - 8.10to9.7 +- identifier: sanity-8.10to9.7-beaker-minimal-ondemand +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:partitioning & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-810to97 +- env: +- <<: *env-810to97 +- +-# On-demand kernel-rt tests +-- &kernel-rt-810to97 +- <<: *kernel-rt-abstract-8to9-ondemand +- trigger: pull_request +- labels: +- - kernel-rt +- - kernel-rt-8.10to9.7 +- - 8.10to9.7 +- identifier: sanity-8.10to9.7-kernel-rt-ondemand +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-810to97 +- env: +- <<: *env-810to97 +- + # ###################################################################### # + # ############################# 8.10 > 9.8 ############################# # + # ###################################################################### # +@@ -478,15 +345,6 @@ jobs: + provisioning: + tags: + BusinessUnit: sst_upgrades@leapp_upstream_test +- - &tmt-env-settings-97to101 +- tmt: +- context: &tmt-context-97to101 +- distro: "rhel-9.7" +- distro_target: "rhel-10.1" +- settings: +- provisioning: +- tags: +- BusinessUnit: sst_upgrades@leapp_upstream_test + - &tmt-env-settings-centos9torhel101 + tmt: + context: &tmt-context-centos9torhel101 +@@ -605,70 +463,6 @@ jobs: + env: + <<: *env-96to100 + +-# ###################################################################### # +-# ############################# 9.7 > 10.1 ############################# # +-# ###################################################################### # +- +-- &sanity-97to101 +- <<: *sanity-abstract-9to10 +- trigger: pull_request +- identifier: sanity-9.7to10.1 +- targets: +- epel-9-x86_64: +- distros: [RHEL-9.7.0-Nightly] +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:9to10 & tag:tier0 & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-97to101 +- env: &env-97to101 +- SOURCE_RELEASE: "9.7" +- TARGET_RELEASE: "10.1" +- +-# On-demand minimal beaker tests +-- &beaker-minimal-97to101 +- <<: *beaker-minimal-9to10-abstract-ondemand +- trigger: pull_request +- labels: +- - beaker-minimal +- - beaker-minimal-9.7to10.1 +- - 9.7to10.1 +- identifier: sanity-9.7to10.1-beaker-minimal-ondemand +- targets: +- epel-9-x86_64: +- distros: [RHEL-9.7-Nightly] +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:partitioning & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-97to101 +- env: +- <<: *env-97to101 +- +-# On-demand kernel-rt tests +-- &kernel-rt-97to101 +- <<: *kernel-rt-abstract-9to10-ondemand +- trigger: pull_request +- labels: +- - kernel-rt +- - kernel-rt-9.7to10.1 +- - 9.7to10.1 +- identifier: sanity-9.7to10.1-kernel-rt-ondemand +- targets: +- epel-9-x86_64: +- distros: [RHEL-9.7-Nightly] +- tf_extra_params: +- test: +- tmt: +- plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true & tag:-rhsm' +- environments: +- - *tmt-env-settings-97to101 +- env: +- <<: *env-97to101 +- +- + # ###################################################################### # + # ############################# 9.8 > 10.2 ############################# # + # ###################################################################### # +diff --git a/.pylintrc b/.pylintrc +index a82f8818..bd365788 100644 +--- a/.pylintrc ++++ b/.pylintrc +@@ -42,7 +42,9 @@ disable= + unnecessary-pass, + raise-missing-from, # no 'raise from' in python 2 + consider-using-f-string, # sorry, not gonna happen, still have to support py2 +- logging-format-interpolation ++ logging-format-interpolation, ++# problem betwee Python 3.6 and 3.8+ pylint ++ useless-option-value + + [FORMAT] + # Maximum number of characters on a single line. diff --git a/ci/.gitignore b/ci/.gitignore new file mode 100644 index 00000000..e6f97f0f @@ -3474,6 +3741,27 @@ index 00000000..370758e6 + end + end +end +diff --git a/docs/source/libraries-and-api/deprecations-list.md b/docs/source/libraries-and-api/deprecations-list.md +index e620d70d..817b63c5 100644 +--- a/docs/source/libraries-and-api/deprecations-list.md ++++ b/docs/source/libraries-and-api/deprecations-list.md +@@ -15,6 +15,16 @@ Only the versions in which a deprecation has been made are listed. + ## Next release (till TODO date) + - Shared libraries + - **`leapp.libraries.common.config.get_distro_id()`** - The function has been replaced by variants for source and target distros - `leapp.libraries.common.config.get_source_distro_id()` and `leapp.libraries.common.config.get_target_distro_id()`. ++ - Following UEFI related functions and classes have been moved from `leapp.libraries.common.grub` into `leapp.libraries.common.efi`: ++ - **`EFIBootInfo`** - raises `leapp.libraries.common.efi.EFIError` instead of `leapp.exceptions.StopActorExecutionError` ++ - **`EFIBootLoaderEntry`** ++ - **`canonical_path_to_efi_format()`** ++ - **`get_efi_device()`** - raises `leapp.libraries.common.efi.EFIError` instead of `leapp.exceptions.StopActorExecutionError` ++ - **`get_efi_partition()`** - raises `leapp.libraries.common.efi.EFIError` instead of `leapp.exceptions.StopActorExecutionError` ++ - **`is_efi()`** ++ - Functions related to manipulation of devices and partitions were moved from `leapp.libraries.common.grub` into `leapp.libraries.common.partitions`: ++ - **`get_device_number()`** - replaced by **`get_partition_number()`** ++ - **`blk_dev_from_partition()`** + + ## v0.23.0 (till March 2026) + diff --git a/etc/leapp/files/device_driver_deprecation_data.json b/etc/leapp/files/device_driver_deprecation_data.json index a9c06956..c38c2840 100644 --- a/etc/leapp/files/device_driver_deprecation_data.json @@ -3487,23 +3775,1150 @@ index a9c06956..c38c2840 100644 "data": [ { diff --git a/etc/leapp/files/pes-events.json b/etc/leapp/files/pes-events.json -index 964b7117..f15002d6 100644 +index 964b7117..07a716f0 100644 --- a/etc/leapp/files/pes-events.json +++ b/etc/leapp/files/pes-events.json @@ -1,7 +1,7 @@ { -"timestamp": "202512021706Z", -+"timestamp": "202512221307Z", ++"timestamp": "202602051305Z", "provided_data_streams": [ -"4.1" +"4.2" ], "packageinfo": [ { -@@ -709181,6 +709181,956 @@ null - "minor_version": 8, +@@ -269457,6 +269457,10 @@ null + { + "name": "ruby", + "stream": "3.1" ++}, ++{ ++"name": "ruby", ++"stream": "3.3" + } + ], + "name": "rubygem-abrt", +@@ -269467,7 +269471,7 @@ null + }, + "initial_release": { + "major_version": 8, +-"minor_version": 7, ++"minor_version": 10, + "os_name": "RHEL" + }, + "modulestream_maps": [], +@@ -269510,6 +269514,10 @@ null + { + "name": "ruby", + "stream": "3.1" ++}, ++{ ++"name": "ruby", ++"stream": "3.3" + } + ], + "name": "rubygem-abrt", +@@ -269520,14 +269528,14 @@ null + }, + "initial_release": { + "major_version": 8, +-"minor_version": 6, ++"minor_version": 9, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { + "major_version": 8, +-"minor_version": 7, ++"minor_version": 10, "os_name": "RHEL" } + }, +@@ -269563,6 +269571,10 @@ null + { + "name": "ruby", + "stream": "3.1" ++}, ++{ ++"name": "ruby", ++"stream": "3.3" + } + ], + "name": "rubygem-abrt-doc", +@@ -269573,7 +269585,7 @@ null + }, + "initial_release": { + "major_version": 8, +-"minor_version": 7, ++"minor_version": 10, + "os_name": "RHEL" + }, + "modulestream_maps": [], +@@ -269616,6 +269628,10 @@ null + { + "name": "ruby", + "stream": "3.1" ++}, ++{ ++"name": "ruby", ++"stream": "3.3" + } + ], + "name": "rubygem-abrt-doc", +@@ -269626,14 +269642,14 @@ null + }, + "initial_release": { + "major_version": 8, +-"minor_version": 6, ++"minor_version": 9, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { + "major_version": 8, +-"minor_version": 7, ++"minor_version": 10, + "os_name": "RHEL" + } + }, +@@ -634585,7 +634601,7 @@ null + } + }, + { +-"action": 7, ++"action": 3, + "architectures": [ + "aarch64", + "ppc64le", +@@ -634607,7 +634623,7 @@ null + }, + "initial_release": { + "major_version": 9, +-"minor_version": 7, ++"minor_version": 8, + "os_name": "RHEL" + }, + "modulestream_maps": [ +@@ -634622,7 +634638,7 @@ null + "modulestreams": [ + null + ], +-"name": "tomcat9-el-3.0-api", ++"name": "tomcat-el-5.0-api", + "repository": "rhel10-AppStream" + } + ], +@@ -634635,7 +634651,7 @@ null + } + }, + { +-"action": 7, ++"action": 3, + "architectures": [ + "aarch64", + "ppc64le", +@@ -634657,7 +634673,7 @@ null + }, + "initial_release": { + "major_version": 9, +-"minor_version": 7, ++"minor_version": 8, + "os_name": "RHEL" + }, + "modulestream_maps": [ +@@ -634672,7 +634688,7 @@ null + "modulestreams": [ + null + ], +-"name": "tomcat9-servlet-4.0-api", ++"name": "tomcat-servlet-6.0-api", + "repository": "rhel10-AppStream" + } + ], +@@ -634685,7 +634701,7 @@ null + } + }, + { +-"action": 7, ++"action": 3, + "architectures": [ + "aarch64", + "ppc64le", +@@ -634707,7 +634723,7 @@ null + }, + "initial_release": { + "major_version": 9, +-"minor_version": 7, ++"minor_version": 8, + "os_name": "RHEL" + }, + "modulestream_maps": [ +@@ -634722,7 +634738,7 @@ null + "modulestreams": [ + null + ], +-"name": "tomcat9-jsp-2.3-api", ++"name": "tomcat-jsp-3.1-api", + "repository": "rhel10-AppStream" + } + ], +@@ -635344,14 +635360,14 @@ null + } + }, + { +-"action": 7, ++"action": 0, + "architectures": [ + "aarch64", + "ppc64le", + "s390x", + "x86_64" + ], +-"id": 17728, ++"id": 17733, + "in_packageset": { + "package": [ + { +@@ -635359,234 +635375,18 @@ null + null + ], + "name": "tomcat", +-"repository": "rhel9-AppStream" +-} +-], +-"set_id": 24313 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [ +-{ +-"in_modulestream": null, +-"out_modulestream": null +-} +-], +-"out_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat9", + "repository": "rhel10-AppStream" + } + ], +-"set_id": 24314 +-}, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 7, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], +-"id": 17729, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-admin-webapps", +-"repository": "rhel9-AppStream" +-} +-], +-"set_id": 24315 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [ +-{ +-"in_modulestream": null, +-"out_modulestream": null +-} +-], +-"out_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat9-admin-webapps", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24316 +-}, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 7, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], +-"id": 17730, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-docs-webapp", +-"repository": "rhel9-AppStream" +-} +-], +-"set_id": 24317 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [ +-{ +-"in_modulestream": null, +-"out_modulestream": null +-} +-], +-"out_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat9-docs-webapp", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24318 +-}, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 7, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], +-"id": 17731, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-lib", +-"repository": "rhel9-AppStream" +-} +-], +-"set_id": 24323 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [ +-{ +-"in_modulestream": null, +-"out_modulestream": null +-} +-], +-"out_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat9-lib", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24324 +-}, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 7, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], +-"id": 17732, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-webapps", +-"repository": "rhel9-AppStream" +-} +-], +-"set_id": 24327 ++"set_id": 24329 + }, + "initial_release": { + "major_version": 9, + "minor_version": 7, + "os_name": "RHEL" + }, +-"modulestream_maps": [ +-{ +-"in_modulestream": null, +-"out_modulestream": null +-} +-], +-"out_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat9-webapps", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24328 +-}, ++"modulestream_maps": [], ++"out_packageset": null, + "release": { + "major_version": 10, + "minor_version": 0, +@@ -635601,18 +635401,18 @@ null + "s390x", + "x86_64" + ], +-"id": 17733, ++"id": 17734, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "tomcat", ++"name": "tomcat-admin-webapps", + "repository": "rhel10-AppStream" + } + ], +-"set_id": 24329 ++"set_id": 24330 + }, + "initial_release": { + "major_version": 9, +@@ -635635,120 +635435,18 @@ null + "s390x", + "x86_64" + ], +-"id": 17734, ++"id": 17735, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "tomcat-admin-webapps", ++"name": "tomcat-docs-webapp", + "repository": "rhel10-AppStream" + } + ], +-"set_id": 24330 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [], +-"out_packageset": null, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 0, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], +-"id": 17735, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-docs-webapp", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24331 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [], +-"out_packageset": null, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 0, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], +-"id": 17736, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-el-5.0-api", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24332 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [], +-"out_packageset": null, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 0, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], +-"id": 17737, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-jsp-3.1-api", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24333 ++"set_id": 24331 + }, + "initial_release": { + "major_version": 9, +@@ -635805,40 +635503,6 @@ null + "s390x", + "x86_64" + ], +-"id": 17739, +-"in_packageset": { +-"package": [ +-{ +-"modulestreams": [ +-null +-], +-"name": "tomcat-servlet-6.0-api", +-"repository": "rhel10-AppStream" +-} +-], +-"set_id": 24335 +-}, +-"initial_release": { +-"major_version": 9, +-"minor_version": 7, +-"os_name": "RHEL" +-}, +-"modulestream_maps": [], +-"out_packageset": null, +-"release": { +-"major_version": 10, +-"minor_version": 0, +-"os_name": "RHEL" +-} +-}, +-{ +-"action": 0, +-"architectures": [ +-"aarch64", +-"ppc64le", +-"s390x", +-"x86_64" +-], + "id": 17740, + "in_packageset": { + "package": [ +@@ -708674,18 +708338,7515 @@ null + "s390x", + "x86_64" + ], +-"id": 19907, ++"id": 19907, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "capnproto-devel", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26584 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19908, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "capnproto-libs", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26585 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19909, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "rhc-playbook-verifier", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26586 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19910, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "rhc-playbook-verifier", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26587 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19911, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3-zstandard", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26588 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19912, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "unbound-utils", ++"repository": "rhel10-BaseOS" ++} ++], ++"set_id": 26589 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 5, ++"architectures": [ ++"x86_64" ++], ++"id": 19913, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "kernel-rt", ++"repository": "rhel10-NFV" ++}, ++{ ++"modulestreams": [ ++null ++], ++"name": "kernel-rt-kvm", ++"repository": "rhel10-NFV" ++} ++], ++"set_id": 26593 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 0, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [ ++{ ++"in_modulestream": null, ++"out_modulestream": null ++} ++], ++"out_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "kernel-rt", ++"repository": "rhel10-NFV" ++} ++], ++"set_id": 26600 ++}, ++"release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 5, ++"architectures": [ ++"x86_64" ++], ++"id": 19915, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "kernel-rt", ++"repository": "rhel9-NFV" ++}, ++{ ++"modulestreams": [ ++null ++], ++"name": "kernel-rt-kvm", ++"repository": "rhel9-NFV" ++} ++], ++"set_id": 26595 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 6, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [ ++{ ++"in_modulestream": null, ++"out_modulestream": null ++} ++], ++"out_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "kernel-rt", ++"repository": "rhel9-NFV" ++} ++], ++"set_id": 26601 ++}, ++"release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19916, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "gnome-autoar-devel", ++"repository": "rhel8-CRB" ++} ++], ++"set_id": 26596 ++}, ++"initial_release": { ++"major_version": 8, ++"minor_version": 9, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 8, ++"minor_version": 10, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19917, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "rest-devel", ++"repository": "rhel8-CRB" ++} ++], ++"set_id": 26597 ++}, ++"initial_release": { ++"major_version": 8, ++"minor_version": 9, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 8, ++"minor_version": 10, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19918, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "plymouth-devel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26598 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19919, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "plymouth-devel", ++"repository": "rhel8-AppStream" ++} ++], ++"set_id": 26599 ++}, ++"initial_release": { ++"major_version": 8, ++"minor_version": 9, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 8, ++"minor_version": 10, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19920, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "libdmx-devel", ++"repository": "rhel8-CRB" ++} ++], ++"set_id": 26602 ++}, ++"initial_release": { ++"major_version": 8, ++"minor_version": 9, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 8, ++"minor_version": 10, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19921, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "libdmx-devel", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26603 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} +}, +{ +"action": 0, @@ -4454,17 +5869,6671 @@ index 964b7117..f15002d6 100644 +"minor_version": 2, +"os_name": "RHEL" +} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19950, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26633 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19951, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "pgaudit", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26634 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19952, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "pg_repack", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26635 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19953, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "pgvector", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26636 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19954, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgis", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26637 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19955, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgis-client", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26638 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19956, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgis-docs", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26639 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19957, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgis-upgrade", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26640 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19958, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgis-utils", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26641 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19959, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgres-decoderbufs", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26642 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19960, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26643 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19961, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-contrib", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26644 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19962, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-docs", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26645 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19963, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-plperl", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26646 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19964, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-plpython3", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26647 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19965, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-private-devel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26648 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19966, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-private-libs", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26649 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19967, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-server", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26650 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19968, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-server-devel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26651 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19969, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-static", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26652 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19970, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-test", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26653 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19971, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-test-rpm-macros", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26654 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19972, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-upgrade", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26655 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19973, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "postgresql", ++"stream": "18" ++} ++], ++"name": "postgresql-upgrade-devel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26656 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19974, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mesa-compat", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26661 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19975, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mesa-compat", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26662 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19977, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26660 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19978, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-cffi", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26663 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19979, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-charset-normalizer", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26664 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19980, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-cryptography", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26665 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19981, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-Cython", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26666 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19982, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-debug", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26667 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19983, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-devel", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26668 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19984, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-flit-core", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26669 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19985, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-debug", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26670 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19986, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-devel", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26671 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19987, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-idle", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26672 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19988, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26673 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19989, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-libs", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26674 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19990, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-test", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26675 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19991, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-tkinter", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26676 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19992, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-idle", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26677 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19993, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-idna", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26678 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19994, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-iniconfig", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26679 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19995, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-libs", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26680 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19996, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-lxml", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26681 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19997, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-meson-python", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26682 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19998, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-mod_wsgi", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26683 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 19999, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-numpy-f2py", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26684 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20000, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-numpy", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26685 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20001, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-packaging", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26686 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20002, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pip", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26687 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20003, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pip-wheel", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26688 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20004, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pluggy", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26689 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20005, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-ply", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26690 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20006, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-psycopg2", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26691 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20007, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-psycopg2-tests", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26692 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20008, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pybind11-devel", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26693 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20009, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pybind11", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26694 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20010, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pycparser", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26695 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20011, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-PyMySQL", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26696 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20012, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-PyMySQL+rsa", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26697 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20013, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pyproject-metadata", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26698 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20014, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pytest", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26699 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20015, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pyyaml", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26700 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20016, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-requests", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26701 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20017, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-scipy", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26702 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20018, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-semantic_version", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26703 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20019, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26704 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20020, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools-rust", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26705 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20021, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools_scm", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26706 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20022, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools_scm+toml", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26707 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20023, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools-wheel", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26708 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20024, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-test", ++"repository": "rhel10-CRB" ++} ++], ++"set_id": 26709 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20025, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-tkinter", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26710 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20026, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-urllib3", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26711 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20028, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-cffi", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26713 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20029, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-charset-normalizer", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26714 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20030, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-cryptography", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26715 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20031, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-Cython", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26716 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20032, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-debug", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26717 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20033, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-devel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26718 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20034, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-flit-core", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26719 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20035, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-debug", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26720 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20036, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-devel", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26721 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20037, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-idle", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26722 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20038, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26723 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20039, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-libs", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26724 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20040, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-test", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26725 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20041, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-freethreading-tkinter", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26726 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20042, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-idle", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26727 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20043, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-idna", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26728 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20044, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-iniconfig", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26729 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20045, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26730 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20046, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-libs", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26731 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20047, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-lxml", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26732 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20048, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-meson-python", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26733 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20049, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-mod_wsgi", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26734 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20050, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-numpy-f2py", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26735 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20051, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-numpy", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26736 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20052, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-packaging", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26737 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20053, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pip", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26738 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20054, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pip-wheel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26739 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20055, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pluggy", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26740 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20056, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-ply", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26741 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20057, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-psycopg2", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26742 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20058, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-psycopg2-tests", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26743 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20059, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pybind11-devel", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26744 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20060, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pybind11", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26745 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20061, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pycparser", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26746 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20062, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-PyMySQL", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26747 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20063, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-PyMySQL+rsa", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26748 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20064, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pyproject-metadata", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26749 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20065, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pytest", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26750 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20066, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-pyyaml", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26751 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20067, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-requests", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26752 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20068, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-scipy", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26753 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20069, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-semantic_version", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26754 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20070, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26755 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20071, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools-rust", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26756 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20072, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools_scm", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26757 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20073, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools_scm+toml", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26758 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20074, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-setuptools-wheel", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26759 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20075, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-test", ++"repository": "rhel9-CRB" ++} ++], ++"set_id": 26760 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20076, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-tkinter", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26761 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20077, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "python3.14-urllib3", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26762 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20078, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "frr10", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26763 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"x86_64" ++], ++"id": 20084, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "greenboot", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26786 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"x86_64" ++], ++"id": 20085, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "greenboot", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26787 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20086, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "ansible-collection-redhat-leapp", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26788 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20087, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "clevis-pin-trustee", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26789 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20088, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26790 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20089, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-backup", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26791 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20090, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-client-utils", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26792 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20091, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-common", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26793 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20092, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-devel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26794 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20093, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-embedded", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26795 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20094, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-embedded-devel", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26796 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20095, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-errmsg", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26797 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20096, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-gssapi-server", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26798 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20097, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-oqgraph-engine", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26799 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20098, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-pam", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26800 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20099, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-server", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26801 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20100, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-server-galera", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26802 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20101, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-server-utils", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26803 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20102, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "mariadb-test", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26804 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20103, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++{ ++"name": "mariadb", ++"stream": "11.8" ++} ++], ++"name": "galera", ++"repository": "rhel9-AppStream" ++} ++], ++"set_id": 26805 ++}, ++"initial_release": { ++"major_version": 9, ++"minor_version": 7, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 9, ++"minor_version": 8, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20104, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-backup", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26806 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20105, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-client-utils", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26807 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20106, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-common", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26808 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20107, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-devel", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26809 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20108, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-embedded", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26810 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20109, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-embedded-devel", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26811 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20110, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-errmsg", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26812 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20111, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-gssapi-server", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26813 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20112, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-oqgraph-engine", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26814 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20113, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-pam", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26815 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20114, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-server", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26816 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20115, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-server-galera", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26817 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20116, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-server-utils", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26818 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20117, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "mariadb11.8-test", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26819 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20118, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26820 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20119, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-bcmath", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26821 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20120, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-cli", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26822 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20121, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-common", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26823 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20122, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-dba", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26824 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20123, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-dbg", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26825 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20124, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-devel", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26826 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20125, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-embedded", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26827 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20126, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-enchant", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26828 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20127, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-ffi", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26829 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20128, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-fpm", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26830 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20129, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-gd", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26831 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20130, ++"in_packageset": { ++"package": [ ++{ ++"modulestreams": [ ++null ++], ++"name": "php8.4-gmp", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26832 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20131, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "capnproto-devel", +-"repository": "rhel10-CRB" ++"name": "php8.4-intl", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26584 ++"set_id": 26833 + }, + "initial_release": { + "major_version": 10, +@@ -708708,18 +715869,18 @@ null + "s390x", + "x86_64" + ], +-"id": 19908, ++"id": 20132, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "capnproto-libs", +-"repository": "rhel10-CRB" ++"name": "php8.4-ldap", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26585 ++"set_id": 26834 + }, + "initial_release": { + "major_version": 10, +@@ -708742,18 +715903,18 @@ null + "s390x", + "x86_64" + ], +-"id": 19909, ++"id": 20133, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "rhc-playbook-verifier", ++"name": "php8.4-mbstring", + "repository": "rhel10-AppStream" + } + ], +-"set_id": 26586 ++"set_id": 26835 + }, + "initial_release": { + "major_version": 10, +@@ -708776,29 +715937,29 @@ null + "s390x", + "x86_64" + ], +-"id": 19910, ++"id": 20134, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "rhc-playbook-verifier", +-"repository": "rhel9-AppStream" ++"name": "php8.4-mysqlnd", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26587 ++"set_id": 26836 + }, + "initial_release": { +-"major_version": 9, +-"minor_version": 7, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { +-"major_version": 9, +-"minor_version": 8, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" + } + }, +@@ -708810,18 +715971,18 @@ null + "s390x", + "x86_64" + ], +-"id": 19911, ++"id": 20135, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "python3-zstandard", ++"name": "php8.4-odbc", + "repository": "rhel10-AppStream" + } + ], +-"set_id": 26588 ++"set_id": 26837 + }, + "initial_release": { + "major_version": 10, +@@ -708844,18 +716005,18 @@ null + "s390x", + "x86_64" + ], +-"id": 19912, ++"id": 20136, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "unbound-utils", +-"repository": "rhel10-BaseOS" ++"name": "php8.4-opcache", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26589 ++"set_id": 26838 + }, + "initial_release": { + "major_version": 10, +@@ -708871,110 +716032,206 @@ null + } + }, + { +-"action": 5, ++"action": 0, + "architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", + "x86_64" + ], +-"id": 19913, ++"id": 20137, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "kernel-rt", +-"repository": "rhel10-NFV" ++"name": "php8.4-pdo", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26839 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} + }, + { ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20138, ++"in_packageset": { ++"package": [ ++{ + "modulestreams": [ + null + ], +-"name": "kernel-rt-kvm", +-"repository": "rhel10-NFV" ++"name": "php8.4-pgsql", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26593 ++"set_id": 26840 + }, + "initial_release": { + "major_version": 10, +-"minor_version": 0, ++"minor_version": 1, + "os_name": "RHEL" + }, +-"modulestream_maps": [ +-{ +-"in_modulestream": null, +-"out_modulestream": null ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" + } ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" + ], +-"out_packageset": { ++"id": 20139, ++"in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "kernel-rt", +-"repository": "rhel10-NFV" ++"name": "php8.4-process", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26600 ++"set_id": 26841 + }, +-"release": { ++"initial_release": { + "major_version": 10, + "minor_version": 1, + "os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" + } + }, + { +-"action": 5, ++"action": 0, + "architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", + "x86_64" + ], +-"id": 19915, ++"id": 20140, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "kernel-rt", +-"repository": "rhel9-NFV" ++"name": "php8.4-snmp", ++"repository": "rhel10-AppStream" ++} ++], ++"set_id": 26842 ++}, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" ++} + }, + { ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" ++], ++"id": 20141, ++"in_packageset": { ++"package": [ ++{ + "modulestreams": [ + null + ], +-"name": "kernel-rt-kvm", +-"repository": "rhel9-NFV" ++"name": "php8.4-soap", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26595 ++"set_id": 26843 + }, + "initial_release": { +-"major_version": 9, +-"minor_version": 6, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, +-"modulestream_maps": [ +-{ +-"in_modulestream": null, +-"out_modulestream": null ++"modulestream_maps": [], ++"out_packageset": null, ++"release": { ++"major_version": 10, ++"minor_version": 2, ++"os_name": "RHEL" + } ++}, ++{ ++"action": 0, ++"architectures": [ ++"aarch64", ++"ppc64le", ++"s390x", ++"x86_64" + ], +-"out_packageset": { ++"id": 20142, ++"in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "kernel-rt", +-"repository": "rhel9-NFV" ++"name": "php8.4-xml", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26601 ++"set_id": 26844 + }, ++"initial_release": { ++"major_version": 10, ++"minor_version": 1, ++"os_name": "RHEL" ++}, ++"modulestream_maps": [], ++"out_packageset": null, + "release": { +-"major_version": 9, +-"minor_version": 7, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" + } + }, +@@ -708986,29 +716243,29 @@ null + "s390x", + "x86_64" + ], +-"id": 19916, ++"id": 20143, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "gnome-autoar-devel", +-"repository": "rhel8-CRB" ++"name": "php8.4-pecl-xdebug3", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26596 ++"set_id": 26845 + }, + "initial_release": { +-"major_version": 8, +-"minor_version": 9, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { +-"major_version": 8, +-"minor_version": 10, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" + } + }, +@@ -709020,29 +716277,29 @@ null + "s390x", + "x86_64" + ], +-"id": 19917, ++"id": 20144, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "rest-devel", +-"repository": "rhel8-CRB" ++"name": "php8.4-pecl-rrd", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26597 ++"set_id": 26846 + }, + "initial_release": { +-"major_version": 8, +-"minor_version": 9, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { +-"major_version": 8, +-"minor_version": 10, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" + } + }, +@@ -709054,29 +716311,29 @@ null + "s390x", + "x86_64" + ], +-"id": 19918, ++"id": 20145, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "plymouth-devel", +-"repository": "rhel9-AppStream" ++"name": "php8.4-pecl-zip", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26598 ++"set_id": 26847 + }, + "initial_release": { +-"major_version": 9, +-"minor_version": 7, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { +-"major_version": 9, +-"minor_version": 8, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" + } + }, +@@ -709088,29 +716345,29 @@ null + "s390x", + "x86_64" + ], +-"id": 19919, ++"id": 20146, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "plymouth-devel", +-"repository": "rhel8-AppStream" ++"name": "php8.4-pecl-apcu", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26599 ++"set_id": 26848 + }, + "initial_release": { +-"major_version": 8, +-"minor_version": 9, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { +-"major_version": 8, +-"minor_version": 10, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" + } + }, +@@ -709122,29 +716379,29 @@ null + "s390x", + "x86_64" + ], +-"id": 19920, ++"id": 20147, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "libdmx-devel", +-"repository": "rhel8-CRB" ++"name": "php8.4-pecl-apcu-devel", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26602 ++"set_id": 26849 + }, + "initial_release": { +-"major_version": 8, +-"minor_version": 9, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { +-"major_version": 8, +-"minor_version": 10, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" + } + }, +@@ -709156,29 +716413,29 @@ null + "s390x", + "x86_64" + ], +-"id": 19921, ++"id": 20148, + "in_packageset": { + "package": [ + { + "modulestreams": [ + null + ], +-"name": "libdmx-devel", +-"repository": "rhel9-CRB" ++"name": "php8.4-pecl-redis6", ++"repository": "rhel10-AppStream" + } + ], +-"set_id": 26603 ++"set_id": 26850 + }, + "initial_release": { +-"major_version": 9, +-"minor_version": 7, ++"major_version": 10, ++"minor_version": 1, + "os_name": "RHEL" + }, + "modulestream_maps": [], + "out_packageset": null, + "release": { +-"major_version": 9, +-"minor_version": 8, ++"major_version": 10, ++"minor_version": 2, + "os_name": "RHEL" } - ] } diff --git a/etc/leapp/files/repomap.json b/etc/leapp/files/repomap.json -index c4ae9038..bdd8c4f6 100644 +index c4ae9038..a57a04e4 100644 --- a/etc/leapp/files/repomap.json +++ b/etc/leapp/files/repomap.json @@ -1,202 +1,138 @@ { - "datetime": "202511131423Z", -+ "datetime": "202601071719Z", ++ "datetime": "202602061951Z", "version_format": "1.3.0", "provided_data_streams": [ - "4.1" @@ -4746,205 +12815,490 @@ index c4ae9038..bdd8c4f6 100644 { "source": "rhel9-rhui-client-config-server-9", "target": [ -@@ -1666,6 +1590,91 @@ - } - ] - }, -+ { -+ "pesid": "rhel10-satellite-client-6", -+ "entries": [ -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-aarch64-eus-rpms", -+ "arch": "aarch64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-aarch64-rpms", -+ "arch": "aarch64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-ppc64le-e4s-rpms", -+ "arch": "ppc64le", -+ "channel": "e4s", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-ppc64le-eus-rpms", -+ "arch": "ppc64le", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-ppc64le-rpms", -+ "arch": "ppc64le", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-s390x-eus-rpms", -+ "arch": "s390x", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-s390x-rpms", -+ "arch": "s390x", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-x86_64-e4s-rpms", -+ "arch": "x86_64", -+ "channel": "e4s", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-x86_64-eus-rpms", -+ "arch": "x86_64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "satellite-client-6-for-rhel-10-x86_64-rpms", -+ "arch": "x86_64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ } -+ ] -+ }, +@@ -356,6 +280,38 @@ { - "pesid": "rhel10-rhui-microsoft-azure-rhel10", + "pesid": "rhel10-BaseOS", "entries": [ -@@ -1769,1385 +1778,75 @@ - ] - }, ++ { ++ "major_version": "10", ++ "repoid": "baseos", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "baseos", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "baseos", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "baseos", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "10", + "repoid": "baseos", +@@ -602,6 +558,38 @@ { -- "pesid": "rhel7-base", -+ "pesid": "rhel8-BaseOS", + "pesid": "rhel10-AppStream", + "entries": [ ++ { ++ "major_version": "10", ++ "repoid": "appstream", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "appstream", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "appstream", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "appstream", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "10", + "repoid": "appstream", +@@ -980,6 +968,38 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "crb", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "crb", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "crb", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "crb", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "10", + "repoid": "crb", +@@ -1196,6 +1216,22 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-ppc64le-rt-rpms", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-s390x-rt-rpms", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-rt-beta-rpms", +@@ -1220,6 +1256,14 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "rt", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "10", + "repoid": "rt", +@@ -1233,6 +1277,14 @@ + { + "pesid": "rhel10-NFV", + "entries": [ ++ { ++ "major_version": "10", ++ "repoid": "nfv", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "10", + "repoid": "nfv", +@@ -1265,6 +1317,22 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-ppc64le-nfv-rpms", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-s390x-nfv-rpms", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-nfv-beta-rpms", +@@ -1294,6 +1362,14 @@ + { + "pesid": "rhel10-SAP-NetWeaver", + "entries": [ ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-aarch64-sap-netweaver-rpms", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-ppc64le-sap-netweaver-beta-rpms", +@@ -1424,9 +1500,17 @@ "entries": [ { -- "major_version": "7", -- "repoid": "rhel-7-for-arm-64-rhui-rpms", -+ "major_version": "8", -+ "repoid": "baseos", - "arch": "aarch64", - "channel": "ga", + "major_version": "10", +- "repoid": "rhel-10-for-ppc64le-sap-solutions-e4s-rpms", +- "arch": "ppc64le", +- "channel": "e4s", ++ "repoid": "rhel-10-for-aarch64-sap-solutions-rpms", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-ppc64le-sap-solutions-e4s-rpms", ++ "arch": "ppc64le", ++ "channel": "e4s", "repo_type": "rpm", + "distro": "rhel" + }, +@@ -1438,6 +1522,14 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-s390x-sap-solutions-rpms", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-sap-solutions-e4s-rhui-rpms", +@@ -1477,6 +1569,38 @@ + { + "pesid": "rhel10-HighAvailability", + "entries": [ ++ { ++ "major_version": "10", ++ "repoid": "highavailability", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "highavailability", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "highavailability", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "10", ++ "repoid": "highavailability", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "10", + "repoid": "highavailability", +@@ -1637,1323 +1761,126 @@ + "arch": "x86_64", + "channel": "e4s", + "repo_type": "rpm", +- "distro": "rhel" +- }, +- { +- "major_version": "10", +- "repoid": "rhel-10-for-x86_64-highavailability-eus-rpms", +- "arch": "x86_64", +- "channel": "eus", +- "repo_type": "rpm", +- "distro": "rhel" +- }, +- { +- "major_version": "10", +- "repoid": "rhel-10-for-x86_64-highavailability-rpms", +- "arch": "x86_64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel" +- }, +- { +- "major_version": "10", +- "repoid": "rhui-rhel-10-for-x86_64-highavailability-rhui-rpms", +- "arch": "x86_64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "alibaba" +- } +- ] +- }, +- { +- "pesid": "rhel10-rhui-microsoft-azure-rhel10", +- "entries": [ +- { +- "major_version": "10", +- "repoid": "rhui-microsoft-azure-rhel10", +- "arch": "x86_64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "azure" +- } +- ] +- }, +- { +- "pesid": "rhel10-rhui-client-config-server-10", +- "entries": [ +- { +- "major_version": "10", +- "repoid": "rhui-client-config-server-10", +- "arch": "aarch64", +- "channel": "ga", +- "repo_type": "rpm", - "distro": "rhel", - "rhui": "aws" -+ "distro": "centos" - }, - { +- }, +- { +- "major_version": "10", +- "repoid": "rhui-client-config-server-10", +- "arch": "x86_64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "aws" +- } +- ] +- }, +- { +- "pesid": "rhel10-rhui-custom-client-at-alibaba", +- "entries": [ +- { +- "major_version": "10", +- "repoid": "rhui-custom-rhui_client_at_alibaba-rhel-10", +- "arch": "aarch64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "alibaba" +- }, +- { +- "major_version": "10", +- "repoid": "rhui-custom-rhui_client_at_alibaba-rhel-10", +- "arch": "x86_64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "alibaba" +- } +- ] +- }, +- { +- "pesid": "rhel10-rhui-client-config-server-10-sap", +- "entries": [ +- { +- "major_version": "10", +- "repoid": "rhui-client-config-server-10-sap-bundle", +- "arch": "x86_64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "aws" +- } +- ] +- }, +- { +- "pesid": "rhel10-rhui-microsoft-azure-sap-apps", +- "entries": [ +- { +- "major_version": "10", +- "repoid": "rhui-microsoft-azure-rhel10-sapapps", +- "arch": "x86_64", +- "channel": "eus", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "azure" +- } +- ] +- }, +- { +- "pesid": "rhel10-rhui-microsoft-sap-ha", +- "entries": [ +- { +- "major_version": "10", +- "repoid": "rhui-microsoft-azure-rhel10-sap-ha", +- "arch": "x86_64", +- "channel": "e4s", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "azure" +- } +- ] +- }, +- { +- "pesid": "rhel7-base", +- "entries": [ +- { +- "major_version": "7", +- "repoid": "rhel-7-for-arm-64-rhui-rpms", +- "arch": "aarch64", +- "channel": "ga", +- "repo_type": "rpm", +- "distro": "rhel", +- "rhui": "aws" +- }, +- { - "major_version": "7", - "repoid": "rhel-7-for-arm-64-rpms", - "arch": "aarch64", -+ "major_version": "8", -+ "repoid": "baseos", -+ "arch": "ppc64le", - "channel": "ga", - "repo_type": "rpm", +- "channel": "ga", +- "repo_type": "rpm", - "distro": "rhel" -+ "distro": "centos" - }, - { +- }, +- { - "major_version": "7", - "repoid": "rhel-7-for-power-9-rpms", - "arch": "ppc64le", -+ "major_version": "8", -+ "repoid": "baseos", -+ "arch": "s390x", - "channel": "ga", - "repo_type": "rpm", +- "channel": "ga", +- "repo_type": "rpm", - "distro": "rhel" -+ "distro": "centos" - }, - { +- }, +- { - "major_version": "7", - "repoid": "rhel-7-for-power-le-beta-rpms", - "arch": "ppc64le", - "channel": "beta", -+ "major_version": "8", -+ "repoid": "baseos", -+ "arch": "x86_64", -+ "channel": "ga", - "repo_type": "rpm", +- "repo_type": "rpm", - "distro": "rhel" -+ "distro": "centos" - }, - { +- }, +- { - "major_version": "7", - "repoid": "rhel-7-for-power-le-e4s-rpms", - "arch": "ppc64le", - "channel": "e4s", -+ "major_version": "8", -+ "repoid": "rhel-8-baseos-beta-rhui-rpms", -+ "arch": "aarch64", -+ "channel": "beta", - "repo_type": "rpm", +- "repo_type": "rpm", - "distro": "rhel" -+ "distro": "rhel", -+ "rhui": "aws" - }, - { +- }, +- { - "major_version": "7", - "repoid": "rhel-7-for-power-le-els-rpms", - "arch": "ppc64le", - "channel": "els", -+ "major_version": "8", -+ "repoid": "rhel-8-baseos-beta-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "beta", - "repo_type": "rpm", +- "repo_type": "rpm", - "distro": "rhel" -+ "distro": "rhel", -+ "rhui": "aws" - }, - { +- }, +- { - "major_version": "7", - "repoid": "rhel-7-for-power-le-eus-rpms", - "arch": "ppc64le", - "channel": "eus", -+ "major_version": "8", -+ "repoid": "rhel-8-baseos-rhui-rpms", -+ "arch": "aarch64", -+ "channel": "ga", - "repo_type": "rpm", +- "repo_type": "rpm", - "distro": "rhel" -+ "distro": "rhel", -+ "rhui": "aws" - }, - { +- }, +- { - "major_version": "7", - "repoid": "rhel-7-for-power-le-rpms", - "arch": "ppc64le", -+ "major_version": "8", -+ "repoid": "rhel-8-baseos-rhui-rpms", -+ "arch": "x86_64", - "channel": "ga", - "repo_type": "rpm", +- "channel": "ga", +- "repo_type": "rpm", - "distro": "rhel" - }, - { @@ -5924,55 +14278,74 @@ index c4ae9038..bdd8c4f6 100644 - "repo_type": "rpm", - "distro": "rhel", - "rhui": "azure" -- }, -- { ++ "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-sap-hana-for-rhel-7-server-rpms", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-highavailability-eus-rpms", + "arch": "x86_64", - "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel" -- }, -- { ++ "channel": "eus", + "repo_type": "rpm", + "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhui-rhel-sap-hana-for-rhel-7-server-rhui-e4s-rpms", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-highavailability-rpms", + "arch": "x86_64", - "channel": "e4s", -- "repo_type": "rpm", ++ "channel": "ga", + "repo_type": "rpm", - "distro": "rhel", - "rhui": "google" -- }, -- { ++ "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhui-rhel-sap-hana-for-rhel-7-server-rhui-rpms", -- "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", ++ "major_version": "10", ++ "repoid": "rhui-rhel-10-for-x86_64-highavailability-rhui-rpms", + "arch": "x86_64", + "channel": "ga", + "repo_type": "rpm", + "distro": "rhel", - "rhui": "google" -- } -- ] -- }, -- { ++ "rhui": "alibaba" + } + ] + }, + { - "pesid": "rhel7-highavailability", -- "entries": [ -- { ++ "pesid": "rhel10-satellite-client-6", + "entries": [ + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-for-system-z-beta-rpms", - "arch": "s390x", - "channel": "beta", -- "repo_type": "rpm", -- "distro": "rhel" -- }, -- { ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-aarch64-eus-rpms", ++ "arch": "aarch64", ++ "channel": "eus", + "repo_type": "rpm", + "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-for-system-z-rpms", - "arch": "s390x", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel" -- }, -- { ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-aarch64-rpms", ++ "arch": "aarch64", + "channel": "ga", + "repo_type": "rpm", + "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-beta-rpms", - "arch": "x86_64", @@ -5993,90 +14366,122 @@ index c4ae9038..bdd8c4f6 100644 - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-e4s-rpms", - "arch": "x86_64", -- "channel": "e4s", -- "repo_type": "rpm", -- "distro": "rhel" -- }, -- { ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-ppc64le-e4s-rpms", ++ "arch": "ppc64le", + "channel": "e4s", + "repo_type": "rpm", + "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-els-rpms", - "arch": "x86_64", - "channel": "els", -- "repo_type": "rpm", -- "distro": "rhel" -- }, -- { ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-ppc64le-eus-rpms", ++ "arch": "ppc64le", ++ "channel": "eus", + "repo_type": "rpm", + "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-eus-rhui-rpms", - "arch": "x86_64", - "channel": "eus", -- "repo_type": "rpm", ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-ppc64le-rpms", ++ "arch": "ppc64le", ++ "channel": "ga", + "repo_type": "rpm", - "distro": "rhel", - "rhui": "aws" -- }, -- { ++ "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-eus-rpms", - "arch": "x86_64", -- "channel": "eus", -- "repo_type": "rpm", -- "distro": "rhel" -- }, -- { ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-s390x-eus-rpms", ++ "arch": "s390x", + "channel": "eus", + "repo_type": "rpm", + "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-rhui-rpms", - "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-s390x-rpms", ++ "arch": "s390x", + "channel": "ga", + "repo_type": "rpm", - "distro": "rhel", - "rhui": "aws" -- }, -- { ++ "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-rhui-rpms", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-x86_64-e4s-rpms", + "arch": "x86_64", - "channel": "ga", -- "repo_type": "rpm", ++ "channel": "e4s", + "repo_type": "rpm", - "distro": "rhel", - "rhui": "azure" -- }, -- { ++ "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhel-ha-for-rhel-7-server-rpms", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-x86_64-eus-rpms", + "arch": "x86_64", - "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel" -- }, -- { ++ "channel": "eus", + "repo_type": "rpm", + "distro": "rhel" + }, + { - "major_version": "7", - "repoid": "rhui-rhel-ha-for-rhel-7-server-e4s-rhui-rpms", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "satellite-client-6-for-rhel-10-x86_64-rpms", + "arch": "x86_64", - "channel": "e4s", -- "repo_type": "rpm", ++ "channel": "ga", + "repo_type": "rpm", - "distro": "rhel", - "rhui": "google" -- } -- ] -- }, -- { ++ "distro": "rhel" + } + ] + }, + { - "pesid": "rhel7-ansible-2", -- "entries": [ -- { ++ "pesid": "rhel10-rhui-microsoft-azure-rhel10", + "entries": [ + { - "major_version": "7", - "repoid": "rhel-7-server-ansible-2-rhui-rpms", -- "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "azure" -- } -- ] -- }, -- { ++ "major_version": "10", ++ "repoid": "rhui-microsoft-azure-rhel10", + "arch": "x86_64", + "channel": "ga", + "repo_type": "rpm", +@@ -2963,34 +1890,20 @@ + ] + }, + { - "pesid": "rhel7-rhui-client-config-server-7", -- "entries": [ -- { ++ "pesid": "rhel10-rhui-client-config-server-10", + "entries": [ + { - "major_version": "7", - "repoid": "rhui-client-config-server-7", - "arch": "x86_64", @@ -6088,102 +14493,154 @@ index c4ae9038..bdd8c4f6 100644 - { - "major_version": "7", - "repoid": "rhui-client-config-server-7-arm", -- "arch": "aarch64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "aws" ++ "major_version": "10", ++ "repoid": "rhui-client-config-server-10", + "arch": "aarch64", + "channel": "ga", + "repo_type": "rpm", + "distro": "rhel", + "rhui": "aws" - } - ] - }, - { - "pesid": "rhel7-rhui-client-config-server-7-sap", - "entries": [ -- { ++ }, + { - "major_version": "7", - "repoid": "rhui-client-config-server-7-sap-bundle", -- "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "aws" -- } -- ] -- }, -- { ++ "major_version": "10", ++ "repoid": "rhui-client-config-server-10", + "arch": "x86_64", + "channel": "ga", + "repo_type": "rpm", +@@ -3000,64 +1913,64 @@ + ] + }, + { - "pesid": "rhel7-rhui-microsoft-azure-rhel7", -- "entries": [ -- { ++ "pesid": "rhel10-rhui-custom-client-at-alibaba", + "entries": [ + { - "major_version": "7", - "repoid": "rhui-microsoft-azure-rhel7", - "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", ++ "major_version": "10", ++ "repoid": "rhui-custom-rhui_client_at_alibaba-rhel-10", ++ "arch": "aarch64", + "channel": "ga", + "repo_type": "rpm", + "distro": "rhel", - "rhui": "azure" -- }, -- { ++ "rhui": "alibaba" + }, + { - "major_version": "7", - "repoid": "rhui-microsoft-azure-rhel7-eus", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "rhui-custom-rhui_client_at_alibaba-rhel-10", + "arch": "x86_64", - "channel": "eus", -- "repo_type": "rpm", -- "distro": "rhel", ++ "channel": "ga", + "repo_type": "rpm", + "distro": "rhel", - "rhui": "azure" -- } -- ] -- }, -- { ++ "rhui": "alibaba" + } + ] + }, + { - "pesid": "rhel7-rhui-microsoft-sap-ha", -- "entries": [ -- { ++ "pesid": "rhel10-rhui-client-config-server-10-sap", + "entries": [ + { - "major_version": "7", - "repoid": "rhui-microsoft-azure-rhel7-base-sap-ha", -- "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", ++ "major_version": "10", ++ "repoid": "rhui-client-config-server-10-sap-bundle", + "arch": "x86_64", + "channel": "ga", + "repo_type": "rpm", + "distro": "rhel", - "rhui": "azure" -- } -- ] -- }, -- { ++ "rhui": "aws" + } + ] + }, + { - "pesid": "rhel7-rhui-google-compute-engine", -- "entries": [ -- { ++ "pesid": "rhel10-rhui-microsoft-azure-sap-apps", + "entries": [ + { - "major_version": "7", - "repoid": "google-compute-engine", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "rhui-microsoft-azure-rhel10-sapapps", + "arch": "x86_64", - "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", ++ "channel": "eus", + "repo_type": "rpm", + "distro": "rhel", - "rhui": "google" -- } -- ] -- }, -- { ++ "rhui": "azure" + } + ] + }, + { - "pesid": "rhel7-rhui-microsoft-azure-sap-apps", -- "entries": [ -- { ++ "pesid": "rhel10-rhui-microsoft-sap-ha", + "entries": [ + { - "major_version": "7", - "repoid": "rhui-microsoft-azure-rhel7-base-sap-apps", -- "arch": "x86_64", ++ "major_version": "10", ++ "repoid": "rhui-microsoft-azure-rhel10-sap-ha", + "arch": "x86_64", - "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "azure" -- } -- ] -- }, -- { ++ "channel": "e4s", + "repo_type": "rpm", + "distro": "rhel", + "rhui": "azure" +@@ -3065,22 +1978,40 @@ + ] + }, + { - "pesid": "rhel7-rhui-custom-client-at-alibaba", -- "entries": [ -- { ++ "pesid": "rhel8-BaseOS", + "entries": [ + { - "major_version": "7", - "repoid": "rhui-custom-rhui_client_at_alibaba", -- "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", ++ "major_version": "8", ++ "repoid": "baseos", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "baseos", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "baseos", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "baseos", + "arch": "x86_64", + "channel": "ga", + "repo_type": "rpm", - "distro": "rhel", - "rhui": "alibaba" - } @@ -6192,151 +14649,162 @@ index c4ae9038..bdd8c4f6 100644 - { - "pesid": "rhel8-BaseOS", - "entries": [ -- { -- "major_version": "8", -- "repoid": "baseos", -- "arch": "aarch64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "centos" -- }, -- { -- "major_version": "8", -- "repoid": "baseos", -- "arch": "ppc64le", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "centos" -- }, -- { -- "major_version": "8", -- "repoid": "baseos", -- "arch": "s390x", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "centos" -- }, -- { -- "major_version": "8", -- "repoid": "baseos", -- "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "centos" -- }, -- { -- "major_version": "8", -- "repoid": "rhel-8-baseos-beta-rhui-rpms", -- "arch": "aarch64", -- "channel": "beta", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "aws" -- }, -- { -- "major_version": "8", -- "repoid": "rhel-8-baseos-beta-rhui-rpms", -- "arch": "x86_64", -- "channel": "beta", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "aws" -- }, -- { -- "major_version": "8", -- "repoid": "rhel-8-baseos-rhui-rpms", -- "arch": "aarch64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "aws" -- }, -- { -- "major_version": "8", -- "repoid": "rhel-8-baseos-rhui-rpms", -- "arch": "x86_64", -- "channel": "ga", -- "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "aws" -+ "distro": "rhel", -+ "rhui": "aws" - }, ++ "distro": "almalinux" ++ }, { "major_version": "8", -@@ -4551,66 +3250,211 @@ - "arch": "x86_64", - "channel": "ga", - "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "google" -+ "distro": "rhel", -+ "rhui": "google" + "repoid": "baseos", +@@ -3380,6 +2311,38 @@ + { + "pesid": "rhel8-AppStream", + "entries": [ ++ { ++ "major_version": "8", ++ "repoid": "appstream", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" + }, + { + "major_version": "8", -+ "repoid": "rhui-rhel-8-for-x86_64-highavailability-rhui-rpms", -+ "arch": "x86_64", ++ "repoid": "appstream", ++ "arch": "ppc64le", + "channel": "ga", + "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "alibaba" -+ } -+ ] -+ }, -+ { -+ "pesid": "rhel8-ansible-2", -+ "entries": [ ++ "distro": "almalinux" ++ }, + { + "major_version": "8", -+ "repoid": "ansible-2-for-rhel-8-x86_64-rhui-rpms", -+ "arch": "x86_64", ++ "repoid": "appstream", ++ "arch": "s390x", + "channel": "ga", + "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "azure" -+ } -+ ] -+ }, -+ { -+ "pesid": "rhel8-jbeap-7.4", -+ "entries": [ ++ "distro": "almalinux" ++ }, + { + "major_version": "8", -+ "repoid": "jb-eap-7.4-for-rhel-8-x86_64-rpms", ++ "repoid": "appstream", + "arch": "x86_64", + "channel": "ga", + "repo_type": "rpm", -+ "distro": "rhel" -+ } -+ ] -+ }, -+ { -+ "pesid": "rhel8-jbeap-8.0", -+ "entries": [ ++ "distro": "almalinux" ++ }, + { + "major_version": "8", + "repoid": "appstream", +@@ -3811,6 +2774,38 @@ + "repo_type": "rpm", + "distro": "rhel" + }, + { + "major_version": "8", -+ "repoid": "jb-eap-8.0-for-rhel-8-x86_64-rpms", -+ "arch": "x86_64", ++ "repoid": "powertools", ++ "arch": "aarch64", + "channel": "ga", + "repo_type": "rpm", -+ "distro": "rhel" -+ } -+ ] -+ }, -+ { -+ "pesid": "rhel8-jbeap-8.1", -+ "entries": [ ++ "distro": "almalinux" ++ }, + { + "major_version": "8", -+ "repoid": "jb-eap-8.1-for-rhel-8-x86_64-rpms", ++ "repoid": "powertools", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "powertools", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "powertools", + "arch": "x86_64", + "channel": "ga", + "repo_type": "rpm", -+ "distro": "rhel" -+ } -+ ] -+ }, ++ "distro": "almalinux" ++ }, + { + "major_version": "8", + "repoid": "powertools", +@@ -4055,6 +3050,14 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "8", ++ "repoid": "rt", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "8", + "repoid": "rt", +@@ -4068,6 +3071,14 @@ + { + "pesid": "rhel8-NFV", + "entries": [ ++ { ++ "major_version": "8", ++ "repoid": "nfv", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "8", + "repoid": "nfv", +@@ -4348,6 +3359,38 @@ + { + "pesid": "rhel8-HighAvailability", + "entries": [ ++ { ++ "major_version": "8", ++ "repoid": "ha", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "ha", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "ha", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "ha", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "8", + "repoid": "ha", +@@ -4618,6 +3661,151 @@ + } + ] + }, + { + "pesid": "rhel8-satellite-6.16", + "entries": [ @@ -6439,83 +14907,334 @@ index c4ae9038..bdd8c4f6 100644 + "channel": "eus", + "repo_type": "rpm", + "distro": "rhel" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "satellite-client-6-for-rhel-8-s390x-rpms", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "satellite-client-6-for-rhel-8-x86_64-aus-rpms", ++ "arch": "x86_64", ++ "channel": "aus", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "satellite-client-6-for-rhel-8-x86_64-e4s-rpms", ++ "arch": "x86_64", ++ "channel": "e4s", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "satellite-client-6-for-rhel-8-x86_64-eus-rpms", ++ "arch": "x86_64", ++ "channel": "eus", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "8", ++ "repoid": "satellite-client-6-for-rhel-8-x86_64-rpms", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ } ++ ] ++ }, + { + "pesid": "rhel8-rhui-client-config-server-8", + "entries": [ +@@ -4872,6 +4060,38 @@ + { + "pesid": "rhel9-BaseOS", + "entries": [ ++ { ++ "major_version": "9", ++ "repoid": "baseos", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "baseos", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "baseos", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "baseos", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "9", + "repoid": "baseos", +@@ -5153,6 +4373,38 @@ + { + "pesid": "rhel9-AppStream", + "entries": [ ++ { ++ "major_version": "9", ++ "repoid": "appstream", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "appstream", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "appstream", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "appstream", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "9", + "repoid": "appstream", +@@ -5557,6 +4809,38 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "9", ++ "repoid": "crb", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "crb", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "crb", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "crb", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "9", + "repoid": "crb", +@@ -5791,6 +5075,22 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "9", ++ "repoid": "rhel-9-for-ppc64le-rt-rpms", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "rhel-9-for-s390x-rt-rpms", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, + { + "major_version": "9", + "repoid": "rhel-9-for-x86_64-rt-beta-rpms", +@@ -5815,6 +5115,14 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "9", ++ "repoid": "rt", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "9", + "repoid": "rt", +@@ -5828,6 +5136,14 @@ + { + "pesid": "rhel9-NFV", + "entries": [ ++ { ++ "major_version": "9", ++ "repoid": "nfv", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, + { + "major_version": "9", + "repoid": "nfv", +@@ -5854,8 +5170,24 @@ }, { - "major_version": "8", -- "repoid": "rhui-rhel-8-for-x86_64-highavailability-rhui-rpms", -- "arch": "x86_64", -+ "repoid": "satellite-client-6-for-rhel-8-s390x-rpms", + "major_version": "9", +- "repoid": "rhel-9-for-aarch64-nfv-rpms", +- "arch": "aarch64", ++ "repoid": "rhel-9-for-aarch64-nfv-rpms", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "rhel-9-for-ppc64le-nfv-rpms", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "rhel-9-for-s390x-nfv-rpms", + "arch": "s390x", "channel": "ga", "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "alibaba" -- } -- ] -- }, -- { -- "pesid": "rhel8-ansible-2", -- "entries": [ + "distro": "rhel" +@@ -5889,6 +5221,14 @@ + { + "pesid": "rhel9-SAP-NetWeaver", + "entries": [ ++ { ++ "major_version": "9", ++ "repoid": "rhel-9-for-aarch64-sap-netweaver-rpms", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", + "distro": "rhel" + }, { - "major_version": "8", -- "repoid": "ansible-2-for-rhel-8-x86_64-rhui-rpms", -+ "repoid": "satellite-client-6-for-rhel-8-x86_64-aus-rpms", - "arch": "x86_64", -- "channel": "ga", -+ "channel": "aus", - "repo_type": "rpm", -- "distro": "rhel", -- "rhui": "azure" -- } -- ] -- }, -- { -- "pesid": "rhel8-jbeap-7.4", -- "entries": [ + "major_version": "9", + "repoid": "rhel-9-for-ppc64le-sap-netweaver-beta-rpms", +@@ -6026,6 +5366,14 @@ + { + "pesid": "rhel9-SAP-Solutions", + "entries": [ ++ { ++ "major_version": "9", ++ "repoid": "rhel-9-for-aarch64-sap-solutions-rpms", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", + "distro": "rhel" + }, { - "major_version": "8", -- "repoid": "jb-eap-7.4-for-rhel-8-x86_64-rpms", -+ "repoid": "satellite-client-6-for-rhel-8-x86_64-e4s-rpms", - "arch": "x86_64", -- "channel": "ga", -+ "channel": "e4s", + "major_version": "9", + "repoid": "rhel-9-for-ppc64le-sap-solutions-e4s-rpms", +@@ -6042,6 +5390,14 @@ "repo_type": "rpm", "distro": "rhel" -- } -- ] -- }, -- { -- "pesid": "rhel8-jbeap-8.0", -- "entries": [ + }, ++ { ++ "major_version": "9", ++ "repoid": "rhel-9-for-s390x-sap-solutions-rpms", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel" + }, { - "major_version": "8", -- "repoid": "jb-eap-8.0-for-rhel-8-x86_64-rpms", -+ "repoid": "satellite-client-6-for-rhel-8-x86_64-eus-rpms", - "arch": "x86_64", -- "channel": "ga", -+ "channel": "eus", - "repo_type": "rpm", - "distro": "rhel" -- } -- ] -- }, -- { -- "pesid": "rhel8-jbeap-8.1", -- "entries": [ + "major_version": "9", + "repoid": "rhel-9-for-x86_64-sap-solutions-e4s-rhui-rpms", +@@ -6090,6 +5446,38 @@ + { + "pesid": "rhel9-HighAvailability", + "entries": [ ++ { ++ "major_version": "9", ++ "repoid": "highavailability", ++ "arch": "aarch64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "highavailability", ++ "arch": "ppc64le", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "highavailability", ++ "arch": "s390x", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" ++ }, ++ { ++ "major_version": "9", ++ "repoid": "highavailability", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "almalinux" + }, { - "major_version": "8", -- "repoid": "jb-eap-8.1-for-rhel-8-x86_64-rpms", -+ "repoid": "satellite-client-6-for-rhel-8-x86_64-rpms", - "arch": "x86_64", - "channel": "ga", - "repo_type": "rpm", -@@ -6305,6 +5149,190 @@ + "major_version": "9", + "repoid": "highavailability", +@@ -6305,6 +5693,190 @@ } ] }, @@ -6706,7 +15425,7 @@ index c4ae9038..bdd8c4f6 100644 { "pesid": "rhel9-rhui-client-config-server-9", "entries": [ -@@ -6434,45 +5462,6 @@ +@@ -6434,45 +6006,6 @@ "rhui": "alibaba" } ] @@ -6846,15 +15565,1869 @@ index 00000000..52f5af9d + api.produce(ActiveVendorList(data=list(active_vendors))) + else: + self.log.info("No active vendors found, vendor list not generated") +diff --git a/repos/system_upgrade/common/actors/checknvme/actor.py b/repos/system_upgrade/common/actors/checknvme/actor.py +new file mode 100644 +index 00000000..dc82c4ad +--- /dev/null ++++ b/repos/system_upgrade/common/actors/checknvme/actor.py +@@ -0,0 +1,54 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import checknvme ++from leapp.models import ( ++ KernelCmdline, ++ LiveModeConfig, ++ NVMEInfo, ++ StorageInfo, ++ TargetKernelCmdlineArgTasks, ++ TargetUserSpacePreupgradeTasks, ++ TargetUserSpaceUpgradeTasks, ++ UpgradeInitramfsTasks, ++ UpgradeKernelCmdlineArgTasks ++) ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class CheckNVME(Actor): ++ """ ++ Check if NVMe devices are used and possibly register additional actions. ++ ++ Check whether the system uses NVMe devices. These can be connected using ++ different transport technologies, e.g., PCIe, TCP, FC, etc. Transports ++ handled by the current implementation: ++ * PCIe (no special actions are required) ++ * Fibre Channel (FC) ++ ++ When NVMe-FC devices are detected, the following actions are taken: ++ * dracut, dracut-network, nvme-cli, and some others packages are installed into initramfs ++ * /etc/nvme is copied into target userspace ++ * the nvmf dracut module is included into upgrade initramfs ++ * rd.nvmf.discover=fc,auto is added to the upgrade boot entry ++ * nvme_core.multipath is added to the upgrade and target boot entry ++ ++ Conditions causing the upgrade to be inhibited: ++ * detecting a NVMe device using a transport technology different than PCIe or FC ++ that is used in /etc/fstab ++ * missing /etc/nvme/hostnqn or /etc/nvme/hostid when NVMe-FC device is present ++ * source system is RHEL 9+ and it has disabled native multipath ++ """ ++ name = 'check_nvme' ++ consumes = (LiveModeConfig, KernelCmdline, NVMEInfo, StorageInfo) ++ produces = ( ++ Report, ++ TargetKernelCmdlineArgTasks, ++ TargetUserSpacePreupgradeTasks, ++ TargetUserSpaceUpgradeTasks, ++ UpgradeInitramfsTasks, ++ UpgradeKernelCmdlineArgTasks ++ ) ++ tags = (ChecksPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ checknvme.process() +diff --git a/repos/system_upgrade/common/actors/checknvme/libraries/checknvme.py b/repos/system_upgrade/common/actors/checknvme/libraries/checknvme.py +new file mode 100644 +index 00000000..cce11f43 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/checknvme/libraries/checknvme.py +@@ -0,0 +1,352 @@ ++import os ++from collections import defaultdict ++from typing import List ++ ++from leapp import reporting ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.common.config.version import get_source_major_version ++from leapp.libraries.stdlib import api ++from leapp.models import ( ++ CopyFile, ++ DracutModule, ++ KernelCmdline, ++ KernelCmdlineArg, ++ LiveModeConfig, ++ NVMEDevice, ++ NVMEInfo, ++ StorageInfo, ++ TargetKernelCmdlineArgTasks, ++ TargetUserSpacePreupgradeTasks, ++ TargetUserSpaceUpgradeTasks, ++ UpgradeInitramfsTasks, ++ UpgradeKernelCmdlineArgTasks ++) ++ ++FMT_LIST_SEPARATOR = '\n - ' ++FABRICS_TRANSPORT_TYPES = ['fc', 'tcp', 'rdma'] ++BROKEN_TRANSPORT_TYPES = ['tcp', 'rdma'] ++SAFE_TRANSPORT_TYPES = ['pcie', 'fc'] ++RQ_RPMS_CONTAINER = [ ++ 'iproute', ++ 'jq', ++ 'nvme-cli', ++ 'sed', ++] ++ ++# We need this packages early (when setting up container) as we will be modifying some ++# of their files ++EARLY_CONTAINER_RPMS = [ ++ 'dracut', ++ 'dracut-network', # Adds dracut-nvmf module ++] ++ ++ ++class NVMEDeviceCollection: ++ def __init__(self): ++ self.device_by_transport = defaultdict(list) ++ ++ def add_device(self, device: NVMEDevice): ++ self.device_by_transport[device.transport].append(device) ++ ++ def add_devices(self, devices: List[NVMEDevice]): ++ for device in devices: ++ self.add_device(device) ++ ++ def get_devices_by_transport(self, transport: str) -> List[NVMEDevice]: ++ return self.device_by_transport[transport] ++ ++ @property ++ def handled_transport_types(self) -> List[str]: ++ return SAFE_TRANSPORT_TYPES ++ ++ @property ++ def unhandled_devices(self) -> List[NVMEDevice]: ++ unhandled_devices = [] ++ for transport, devices in self.device_by_transport.items(): ++ if transport not in self.handled_transport_types: ++ unhandled_devices.extend(devices) ++ return unhandled_devices ++ ++ @property ++ def fabrics_devices(self) -> List[NVMEDevice]: ++ fabrics_devices = [] ++ for transport in FABRICS_TRANSPORT_TYPES: ++ fabrics_devices.extend(self.device_by_transport[transport]) ++ ++ return fabrics_devices ++ ++ ++def _format_list(data, sep=FMT_LIST_SEPARATOR, callback_sort=sorted, limit=0): ++ # NOTE(pstodulk): Teaser O:-> https://issues.redhat.com/browse/RHEL-126447 ++ ++ def identity(values): ++ return values ++ ++ if callback_sort is None: ++ callback_sort = identity ++ res = ['{}{}'.format(sep, item) for item in callback_sort(data)] ++ if limit: ++ return ''.join(res[:limit]) ++ return ''.join(res) ++ ++ ++def is_livemode_enabled() -> bool: ++ livemode_config = next(api.consume(LiveModeConfig), None) ++ if livemode_config and livemode_config.is_enabled: ++ return True ++ return False ++ ++ ++def get_current_cmdline_arg_value(arg_name: str): ++ cmdline = next(api.consume(KernelCmdline), None) ++ ++ if not cmdline: ++ raise StopActorExecutionError( ++ 'Failed to obtain message with information about current kernel cmdline' ++ ) ++ ++ for arg in cmdline.parameters: ++ if arg.key == arg_name: ++ return arg.value ++ ++ return None ++ ++ ++def _report_native_multipath_required(): ++ """Report that NVMe native multipath must be enabled on RHEL 9 before the upgrade.""" ++ reporting.create_report([ ++ reporting.Title('NVMe native multipath must be enabled on the target system'), ++ reporting.Summary( ++ 'The system is booted with "nvme_core.multipath=N" kernel command line argument, ' ++ 'disabling native multipath for NVMe devices. However, native multipath ' ++ 'is required to be used for NVMe over Fabrics (NVMeoF) on the target system. ' ++ 'Regarding that it is required to update the system setup to use ' ++ 'the native multipath before the in-place upgrade.' ++ ), ++ reporting.Remediation(hint=( ++ 'Enable native multipath for NVMe devices following the official ' ++ 'documentation and reboot your system - see the attached link.' ++ )), ++ reporting.ExternalLink( ++ url='https://red.ht/rhel-9-enabling-multipathing-on-nvme-devices', ++ title='Enabling native multipathing on NVMe devices.' ++ ), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([reporting.Groups.INHIBITOR, reporting.Groups.FILESYSTEM]), ++ ]) ++ ++ ++def _report_system_should_migrate_to_native_multipath(): ++ """ ++ Report that since RHEL 9, native NVMe multipath is the recommended multipath solution for NVMe. ++ """ ++ reporting.create_report([ ++ reporting.Title('Native NVMe multipath is recommended on the target system.'), ++ reporting.Summary( ++ 'In the case that the system is using dm-multipath on NVMe devices, ' ++ 'it is recommended to use the native NVMe multipath instead. ' ++ 'We recommend to update the system configuration after the in-place ' ++ 'upgrade following the official documentation - see the attached link.' ++ ), ++ reporting.ExternalLink( ++ url='https://red.ht/rhel-9-enabling-multipathing-on-nvme-devices', ++ title='Enabling native multipathing on NVMe devices.' ++ ), ++ reporting.Severity(reporting.Severity.INFO), ++ reporting.Groups([reporting.Groups.FILESYSTEM, reporting.Groups.POST]), ++ ]) ++ ++ ++def _report_kernel_cmdline_might_be_modified_unnecessarily(): ++ """ ++ Report that we introduced nvme_core.multipath=N, which might not be necessary. ++ ++ We introduce nvme_core.multipath=N (unconditionally) during 8>9 upgrade. However, ++ the introduction of the argument might not be always necessary, but we currently lack ++ an implementation that would precisely identify when the argument is truly needed. ++ """ ++ reporting.create_report([ ++ reporting.Title('Native NVMe multipath will be disabled on the target system.'), ++ reporting.Summary( ++ 'To ensure system\'s storage layout remains consistent during the upgrade, native ' ++ 'NVMe multipath will be disabled by adding nvme_core.multipath=N to the default boot entry. ' ++ 'In the case that the system does not use multipath, the nvme_core.multipath=N should be manually ' ++ 'removed from the target system\'s boot entry after the upgrade.' ++ ), ++ reporting.ExternalLink( ++ url='https://red.ht/rhel-9-enabling-multipathing-on-nvme-devices', ++ title='Enabling native multipathing on NVMe devices.' ++ ), ++ reporting.Severity(reporting.Severity.INFO), ++ reporting.Groups([reporting.Groups.FILESYSTEM, reporting.Groups.POST]), ++ ]) ++ ++ ++def _tasks_copy_files_into_container(nvme_device_collection: NVMEDeviceCollection): ++ """ ++ Tasks needed to modify target userspace container and the upgrade initramfs. ++ """ ++ # NOTE: prepared for future extension, as it's possible that we will need ++ # to copy more files when starting to look at NVMe-(RDMA|TCP) ++ copy_files = [] ++ ++ if nvme_device_collection.fabrics_devices: ++ # /etc/nvme/ is required only in case of NVMe-oF (PCIe drives are safe) ++ copy_files.append(CopyFile(src='/etc/nvme/')) ++ ++ api.produce(TargetUserSpaceUpgradeTasks( ++ copy_files=copy_files, ++ install_rpms=RQ_RPMS_CONTAINER) ++ ) ++ ++ ++def _tasks_for_kernel_cmdline(nvme_device_collection: NVMEDeviceCollection): ++ upgrade_cmdline_args = [] ++ target_cmdline_args = [] ++ ++ if not is_livemode_enabled(): ++ upgrade_cmdline_args.append(KernelCmdlineArg(key='rd.nvmf.discover', value='fc,auto')) ++ ++ # The nvme_core.multipath argument is used to disable native multipath for NVMeoF devices. ++ nvme_core_mpath_arg_val = get_current_cmdline_arg_value('nvme_core.multipath') ++ ++ # FIXME(pstodulk): handle multi-controller NVMe-PCIe drives WITH multipath used by, e.g., Intel SSD DC P4500. ++ # Essentially, we always append nvme_core.multipath=N to the kernel command line during an 8>9 upgrade. This also ++ # includes basics setups where a simple NVMe drive is attached over PCIe without any multipath capabilities (think ++ # of an ordinary laptops). When the user attempts to later perform a 9>10 upgrade, an inhibitor will be raised with ++ # instructions to remove nvme_core.multipath=N introduced by us during the previous upgrade, which might be ++ # confusing as they might never even heard of multipath. Right now, we just emit a report for the user to remove ++ # nvme_core.multipath=N from the boot entry if multipath is not used. We should improve this behaviour in the ++ # future so that we can precisely target when to introduce the argument. ++ ++ if get_source_major_version() == '8': ++ # NOTE: it's expected kind of that for NVMeoF users always use multipath ++ ++ # If the system is already booted with nvme_core.multipath=?, do not change it ++ # The value will be copied from the default boot entry. ++ # On the other, on 8>9 we want to always add this as there native multipath was unsupported ++ # on RHEL 8, therefore, we should not need it (hence the value N). ++ if not nvme_core_mpath_arg_val: ++ upgrade_cmdline_args.append(KernelCmdlineArg(key='nvme_core.multipath', value='N')) ++ target_cmdline_args.append(KernelCmdlineArg(key='nvme_core.multipath', value='N')) ++ ++ if nvme_core_mpath_arg_val != 'Y': ++ # Print the report only if NVMeoF is detected and ++ _report_system_should_migrate_to_native_multipath() ++ _report_kernel_cmdline_might_be_modified_unnecessarily() ++ ++ if get_source_major_version() == '9': ++ # NOTE(pstodulk): Check this always, does not matter whether we detect ++ # NVMeoF or whether just PCIe is used. In any case, we will require user ++ # to fix it. ++ if nvme_core_mpath_arg_val == 'N': ++ _report_native_multipath_required() ++ return ++ ++ api.produce(UpgradeKernelCmdlineArgTasks(to_add=upgrade_cmdline_args)) ++ api.produce(TargetKernelCmdlineArgTasks(to_add=target_cmdline_args)) ++ ++ ++def register_upgrade_tasks(nvme_device_collection: NVMEDeviceCollection): ++ """ ++ Register tasks that should happen during IPU to handle NVMe devices ++ successfully. ++ ++ Args: ++ nvme_fc_devices (list): List of NVMe-FC devices ++ """ ++ _tasks_copy_files_into_container(nvme_device_collection) ++ _tasks_for_kernel_cmdline(nvme_device_collection) ++ ++ api.produce(TargetUserSpacePreupgradeTasks(install_rpms=EARLY_CONTAINER_RPMS)) ++ api.produce(UpgradeInitramfsTasks(include_dracut_modules=[DracutModule(name='nvmf')])) ++ ++ ++def report_missing_configs_for_fabrics_devices(nvme_info: NVMEInfo, ++ nvme_device_collection: NVMEDeviceCollection, ++ max_devices_in_report: int = 3) -> bool: ++ missing_configs = [] ++ if not nvme_info.hostid: ++ missing_configs.append('/etc/nvme/hostid') ++ if not nvme_info.hostnqn: ++ missing_configs.append('/etc/nvme/hostnqn') ++ ++ # NOTE(pstodulk): hostid and hostnqn are mandatory for NVMe-oF devices. ++ # That means practically FC, RDMA, TCP. Let's inform user the upgrade ++ # is blocked and they must configure the system properly to be able to ++ # upgrade ++ if not nvme_device_collection.fabrics_devices or not missing_configs: ++ return # We either have no fabrics devices or we have both hostid and hostnqn ++ ++ files_str = ', '.join(missing_configs) if missing_configs else 'required configuration files' ++ ++ device_names = [dev.name for dev in nvme_device_collection.fabrics_devices[:max_devices_in_report]] ++ if len(nvme_device_collection.fabrics_devices) > max_devices_in_report: ++ device_names.append('...') ++ device_list_str = ', '.join(device_names) ++ ++ reporting.create_report([ ++ reporting.Title('Missing NVMe configuration files required for the upgrade'), ++ reporting.Summary( ++ 'The system has NVMe-oF devices detected ({}), but {} are missing. ' ++ 'Both /etc/nvme/hostid and /etc/nvme/hostnqn must be present and configured for NVMe-oF usage. ' ++ 'Upgrade cannot continue until these files are provided.'.format(device_list_str, files_str) ++ ), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([reporting.Groups.INHIBITOR, reporting.Groups.FILESYSTEM]), ++ reporting.Remediation( ++ hint='Ensure the files /etc/nvme/hostid and /etc/nvme/hostnqn are present and properly configured.' ++ ), ++ ]) ++ ++ ++def get_devices_present_in_fstab() -> List[str]: ++ storage_info = next(api.consume(StorageInfo), None) ++ ++ if not storage_info: ++ raise StopActorExecutionError('Failed to obtain message with information about fstab entries') ++ ++ # Call realpath to get the *canonical* path to the device (user might use disk UUIDs, etc. in fstab) ++ return {os.path.realpath(entry.fs_spec) for entry in storage_info.fstab} ++ ++ ++def check_unhandled_devices_present_in_fstab(nvme_device_collection: NVMEDeviceCollection) -> bool: ++ """Check if any unhandled NVMe devices are present in fstab. ++ ++ Args: ++ nvme_device_collection: NVMEDeviceCollection instance ++ ++ Returns: ++ True if any unhandled NVMe devices are present in fstab, False otherwise ++ """ ++ unhandled_dev_nodes = {os.path.join('/dev', device.name) for device in nvme_device_collection.unhandled_devices} ++ fstab_listed_dev_nodes = set(get_devices_present_in_fstab()) ++ ++ required_unhandled_dev_nodes = unhandled_dev_nodes.intersection(fstab_listed_dev_nodes) ++ if required_unhandled_dev_nodes: ++ summary = ( ++ 'The system has NVMe devices with a transport type that is currently ' ++ 'not handled during the upgrade process present in fstab. Problematic devices: {0}' ++ ).format(_format_list(required_unhandled_dev_nodes)) ++ ++ reporting.create_report([ ++ reporting.Title('NVMe devices with unhandled transport type present in fstab'), ++ reporting.Summary(summary), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([reporting.Groups.INHIBITOR, reporting.Groups.FILESYSTEM]), ++ ]) ++ return True ++ return False ++ ++ ++def process(): ++ nvmeinfo = next(api.consume(NVMEInfo), None) ++ if not nvmeinfo or not nvmeinfo.devices: ++ return # Nothing to do ++ ++ nvme_device_collection = NVMEDeviceCollection() ++ nvme_device_collection.add_devices(nvmeinfo.devices) ++ ++ check_unhandled_devices_present_in_fstab(nvme_device_collection) ++ report_missing_configs_for_fabrics_devices(nvmeinfo, nvme_device_collection) ++ register_upgrade_tasks(nvme_device_collection) +diff --git a/repos/system_upgrade/common/actors/checknvme/tests/test_checknvme.py b/repos/system_upgrade/common/actors/checknvme/tests/test_checknvme.py +new file mode 100644 +index 00000000..7f18e7b4 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/checknvme/tests/test_checknvme.py +@@ -0,0 +1,431 @@ ++import os ++ ++from leapp import reporting ++from leapp.libraries.actor import checknvme ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import ( ++ FstabEntry, ++ KernelCmdline, ++ NVMEDevice, ++ NVMEInfo, ++ StorageInfo, ++ TargetKernelCmdlineArgTasks, ++ TargetUserSpacePreupgradeTasks, ++ TargetUserSpaceUpgradeTasks, ++ UpgradeInitramfsTasks, ++ UpgradeKernelCmdlineArgTasks ++) ++from leapp.utils.report import is_inhibitor ++ ++ ++def _make_storage_info(fstab_entries=None): ++ """Helper to create StorageInfo with fstab entries.""" ++ if fstab_entries is None: ++ fstab_entries = [] ++ return StorageInfo(fstab=fstab_entries) ++ ++ ++def test_no_nvme_devices(monkeypatch): ++ """Test when no NVMe devices are present.""" ++ msgs = [KernelCmdline(parameters=[])] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ ++ checknvme.process() ++ ++ # No messages should be produced when no NVMe devices are present ++ assert api.produce.called == 0 ++ ++ ++def test_nvme_pcie_devices_only(monkeypatch): ++ """Test with only NVMe PCIe devices (no FC devices).""" ++ nvme_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='pcie' ++ ) ++ nvme_info = NVMEInfo( ++ devices=[nvme_device], ++ hostid='test-hostid', ++ hostnqn='test-hostnqn' ++ ) ++ ++ msgs = [KernelCmdline(parameters=[]), nvme_info, _make_storage_info()] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(checknvme, '_report_system_should_migrate_to_native_multipath', lambda: None) ++ monkeypatch.setattr(checknvme, '_report_kernel_cmdline_might_be_modified_unnecessarily', lambda: None) ++ ++ checknvme.process() ++ ++ def _get_produced_msg(msg_type): ++ """Get a single produced message of the given type.""" ++ for msg in api.produce.model_instances: ++ # We cannot use isinstance due to problems with inheritance ++ if type(msg) is msg_type: # pylint: disable=unidiomatic-typecheck ++ return msg ++ return None ++ ++ # Check TargetUserSpaceUpgradeTasks - no copy_files for PCIe-only ++ userspace_tasks = _get_produced_msg(TargetUserSpaceUpgradeTasks) ++ assert userspace_tasks.copy_files == [] ++ assert set(userspace_tasks.install_rpms) == {'iproute', 'jq', 'nvme-cli', 'sed'} ++ ++ # Check TargetUserSpacePreupgradeTasks ++ preupgrade_tasks = _get_produced_msg(TargetUserSpacePreupgradeTasks) ++ assert set(preupgrade_tasks.install_rpms) == {'dracut', 'dracut-network'} ++ ++ # Check UpgradeInitramfsTasks ++ initramfs_tasks = _get_produced_msg(UpgradeInitramfsTasks) ++ assert len(initramfs_tasks.include_dracut_modules) == 1 ++ assert initramfs_tasks.include_dracut_modules[0].name == 'nvmf' ++ ++ # Check UpgradeKernelCmdlineArgTasks ++ upgrade_cmdline_tasks = _get_produced_msg(UpgradeKernelCmdlineArgTasks) ++ upgrade_cmdline_args = {(arg.key, arg.value) for arg in upgrade_cmdline_tasks.to_add} ++ assert ('rd.nvmf.discover', 'fc,auto') in upgrade_cmdline_args ++ ++ # Check TargetKernelCmdlineArgTasks ++ target_cmdline_tasks = _get_produced_msg(TargetKernelCmdlineArgTasks) ++ # For PCIe-only, no nvme_core.multipath arg is added (no fabrics devices) ++ target_cmdline_args = {(arg.key, arg.value) for arg in target_cmdline_tasks.to_add} ++ assert target_cmdline_args == set() or ('nvme_core.multipath', 'N') in target_cmdline_args ++ ++ ++def test_nvme_fc_devices_present(monkeypatch): ++ """Test with NVMe-FC devices present.""" ++ nvme_fc_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='fc' ++ ) ++ nvme_info = NVMEInfo( ++ devices=[nvme_fc_device], ++ hostid='test-hostid', ++ hostnqn='test-hostnqn' ++ ) ++ ++ msgs = [KernelCmdline(parameters=[]), nvme_info, _make_storage_info()] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(checknvme, '_report_system_should_migrate_to_native_multipath', lambda: None) ++ monkeypatch.setattr(checknvme, '_report_kernel_cmdline_might_be_modified_unnecessarily', lambda: None) ++ ++ checknvme.process() ++ ++ assert api.produce.called == 5 ++ ++ produced_msgs = api.produce.model_instances ++ assert any(isinstance(msg, TargetUserSpacePreupgradeTasks) for msg in produced_msgs) ++ assert any(isinstance(msg, TargetUserSpaceUpgradeTasks) for msg in produced_msgs) ++ assert any(isinstance(msg, UpgradeInitramfsTasks) for msg in produced_msgs) ++ ++ # Check that UpgradeKernelCmdlineArgTasks was produced with correct argument ++ kernel_cmdline_msgs = [msg for msg in produced_msgs if isinstance(msg, UpgradeKernelCmdlineArgTasks)] ++ assert len(kernel_cmdline_msgs) == 1 ++ ++ cmdline_args = {(c_arg.key, c_arg.value) for c_arg in kernel_cmdline_msgs[0].to_add} ++ expected_cmdline_args = { ++ ('rd.nvmf.discover', 'fc,auto'), ++ ('nvme_core.multipath', 'N') ++ } ++ assert expected_cmdline_args == cmdline_args ++ ++ ++def test_mixed_nvme_devices(monkeypatch): ++ """Test with mixed NVMe devices (PCIe and FC).""" ++ nvme_pcie_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='pcie' ++ ) ++ nvme_fc_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme1', ++ name='nvme1', ++ transport='fc' ++ ) ++ nvme_info = NVMEInfo( ++ devices=[nvme_pcie_device, nvme_fc_device], ++ hostid='test-hostid', ++ hostnqn='test-hostnqn' ++ ) ++ ++ msgs = [KernelCmdline(parameters=[]), nvme_info, _make_storage_info()] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(checknvme, '_report_system_should_migrate_to_native_multipath', lambda: None) ++ monkeypatch.setattr(checknvme, '_report_kernel_cmdline_might_be_modified_unnecessarily', lambda: None) ++ ++ checknvme.process() ++ ++ assert api.produce.called == 5 ++ ++ produced_msgs = api.produce.model_instances ++ ++ # Check that UpgradeKernelCmdlineArgTasks was produced ++ kernel_cmdline_msgs = [msg for msg in produced_msgs if isinstance(msg, UpgradeKernelCmdlineArgTasks)] ++ assert len(kernel_cmdline_msgs) == 1 ++ ++ cmdline_args = {(c_arg.key, c_arg.value) for c_arg in kernel_cmdline_msgs[0].to_add} ++ expected_cmdline_args = { ++ ('rd.nvmf.discover', 'fc,auto'), ++ ('nvme_core.multipath', 'N') ++ } ++ assert expected_cmdline_args == cmdline_args ++ ++ ++def test_multiple_nvme_fc_devices(monkeypatch): ++ """Test with multiple NVMe-FC devices.""" ++ nvme_fc_device1 = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='fc' ++ ) ++ nvme_fc_device2 = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme1', ++ name='nvme1', ++ transport='fc' ++ ) ++ nvme_info = NVMEInfo( ++ devices=[nvme_fc_device1, nvme_fc_device2], ++ hostid='test-hostid', ++ hostnqn='test-hostnqn' ++ ) ++ ++ msgs = [KernelCmdline(parameters=[]), nvme_info, _make_storage_info()] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(checknvme, '_report_system_should_migrate_to_native_multipath', lambda: None) ++ monkeypatch.setattr(checknvme, '_report_kernel_cmdline_might_be_modified_unnecessarily', lambda: None) ++ ++ checknvme.process() ++ ++ # Should still produce only one UpgradeKernelCmdlineArgTasks message ++ kernel_cmdline_msgs = [msg for msg in api.produce.model_instances ++ if isinstance(msg, UpgradeKernelCmdlineArgTasks)] ++ assert len(kernel_cmdline_msgs) == 1 ++ ++ # Should still have only two kernel arguments ++ assert len(kernel_cmdline_msgs[0].to_add) == 2 ++ ++ ++def test_nvme_missing_hostid_hostnqn_creates_inhibitor(monkeypatch): ++ """Test that missing hostid/hostnqn creates an inhibitor report for NVMe-oF devices.""" ++ nvme_fc_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='fc' ++ ) ++ # Missing hostid and hostnqn ++ nvme_info = NVMEInfo( ++ devices=[nvme_fc_device], ++ hostid=None, ++ hostnqn=None ++ ) ++ ++ msgs = [KernelCmdline(parameters=[]), nvme_info, _make_storage_info()] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) ++ monkeypatch.setattr(checknvme, '_report_system_should_migrate_to_native_multipath', lambda: None) ++ monkeypatch.setattr(checknvme, '_report_kernel_cmdline_might_be_modified_unnecessarily', lambda: None) ++ ++ checknvme.process() ++ ++ # Should create an inhibitor report for missing configs ++ assert reporting.create_report.called == 1 ++ assert is_inhibitor(reporting.create_report.report_fields) ++ ++ ++def test_nvme_device_collection_categorization(): ++ """Test NVMEDeviceCollection categorizes devices correctly.""" ++ nvme_pcie_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='pcie' ++ ) ++ nvme_fc_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme1', ++ name='nvme1', ++ transport='fc' ++ ) ++ nvme_tcp_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme2', ++ name='nvme2', ++ transport='tcp' ++ ) ++ ++ collection = checknvme.NVMEDeviceCollection() ++ collection.add_devices([nvme_pcie_device, nvme_fc_device, nvme_tcp_device]) ++ ++ assert nvme_pcie_device in collection.get_devices_by_transport('pcie') ++ assert nvme_fc_device in collection.get_devices_by_transport('fc') ++ assert nvme_tcp_device in collection.get_devices_by_transport('tcp') ++ ++ # FC and TCP are fabrics devices ++ assert nvme_fc_device in collection.fabrics_devices ++ assert nvme_tcp_device in collection.fabrics_devices ++ assert nvme_pcie_device not in collection.fabrics_devices ++ ++ # TCP is unhandled (not in SAFE_TRANSPORT_TYPES) ++ assert nvme_tcp_device in collection.unhandled_devices ++ assert nvme_pcie_device not in collection.unhandled_devices ++ assert nvme_fc_device not in collection.unhandled_devices ++ ++ ++def test_register_upgrade_tasks_without_fabrics_devices(monkeypatch): ++ """Test register_upgrade_tasks without fabrics devices.""" ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ ++ kernel_cmdline_tasks = KernelCmdline(parameters=[]) ++ msgs = [kernel_cmdline_tasks] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(checknvme, '_report_system_should_migrate_to_native_multipath', lambda: None) ++ monkeypatch.setattr(checknvme, '_report_kernel_cmdline_might_be_modified_unnecessarily', lambda: None) ++ ++ nvme_pcie_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='pcie' ++ ) ++ collection = checknvme.NVMEDeviceCollection() ++ collection.add_device(nvme_pcie_device) ++ ++ checknvme.register_upgrade_tasks(collection) ++ ++ produced_msgs = api.produce.model_instances ++ expected_msg_types = { ++ TargetUserSpaceUpgradeTasks, ++ TargetUserSpacePreupgradeTasks, ++ UpgradeInitramfsTasks, ++ UpgradeKernelCmdlineArgTasks, ++ TargetKernelCmdlineArgTasks, ++ } ++ assert set(type(msg) for msg in produced_msgs) == expected_msg_types ++ ++ ++def test_register_upgrade_tasks_with_fabrics_devices(monkeypatch): ++ """Test register_upgrade_tasks with fabrics devices.""" ++ nvme_fc_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='fc' ++ ) ++ collection = checknvme.NVMEDeviceCollection() ++ collection.add_device(nvme_fc_device) ++ ++ msgs = [KernelCmdline(parameters=[])] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(checknvme, '_report_system_should_migrate_to_native_multipath', lambda: None) ++ monkeypatch.setattr(checknvme, '_report_kernel_cmdline_might_be_modified_unnecessarily', lambda: None) ++ ++ checknvme.register_upgrade_tasks(collection) ++ ++ produced_msgs = api.produce.model_instances ++ expected_msg_types = { ++ TargetUserSpaceUpgradeTasks, ++ TargetUserSpacePreupgradeTasks, ++ UpgradeInitramfsTasks, ++ UpgradeKernelCmdlineArgTasks, ++ TargetKernelCmdlineArgTasks, ++ } ++ assert set(type(msg) for msg in produced_msgs) == expected_msg_types ++ ++ kernel_cmdline_msgs = [msg for msg in produced_msgs if isinstance(msg, UpgradeKernelCmdlineArgTasks)] ++ assert len(kernel_cmdline_msgs) == 1 ++ ++ cmdline_args = {(c_arg.key, c_arg.value) for c_arg in kernel_cmdline_msgs[0].to_add} ++ expected_cmdline_args = { ++ ('rd.nvmf.discover', 'fc,auto'), ++ ('nvme_core.multipath', 'N') ++ } ++ assert expected_cmdline_args == cmdline_args ++ ++ ++def test_check_unhandled_devices_not_in_fstab(monkeypatch): ++ """Test that no inhibitor is created when unhandled devices are not in fstab.""" ++ nvme_tcp_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='tcp' # tcp is unhandled ++ ) ++ collection = checknvme.NVMEDeviceCollection() ++ collection.add_device(nvme_tcp_device) ++ ++ # fstab contains a different device ++ fstab_entries = [ ++ FstabEntry(fs_spec='/dev/sda1', fs_file='/', fs_vfstype='ext4', ++ fs_mntops='defaults', fs_freq='1', fs_passno='1') ++ ] ++ storage_info = _make_storage_info(fstab_entries) ++ ++ msgs = [storage_info] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) ++ monkeypatch.setattr('os.path.realpath', lambda path: path) ++ ++ result = checknvme.check_unhandled_devices_present_in_fstab(collection) ++ ++ assert result is False ++ assert reporting.create_report.called == 0 ++ ++ ++def test_check_unhandled_devices_in_fstab_creates_inhibitor(monkeypatch): ++ """Test that an inhibitor is created when unhandled devices are in fstab.""" ++ nvme_tcp_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='tcp' # tcp is unhandled ++ ) ++ collection = checknvme.NVMEDeviceCollection() ++ collection.add_device(nvme_tcp_device) ++ ++ # fstab contains the unhandled device ++ fstab_entries = [ ++ FstabEntry(fs_spec='/dev/nvme0', fs_file='/', fs_vfstype='ext4', ++ fs_mntops='defaults', fs_freq='1', fs_passno='1') ++ ] ++ storage_info = _make_storage_info(fstab_entries) ++ ++ msgs = [storage_info] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) ++ monkeypatch.setattr(os.path, 'realpath', lambda path: path) ++ ++ result = checknvme.check_unhandled_devices_present_in_fstab(collection) ++ ++ assert result is True ++ assert reporting.create_report.called == 1 ++ assert is_inhibitor(reporting.create_report.report_fields) ++ ++ ++def test_check_unhandled_devices_handled_device_in_fstab_no_inhibitor(monkeypatch): ++ """Test that no inhibitor is created when only handled devices are in fstab.""" ++ nvme_pcie_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='pcie' # pcie is handled ++ ) ++ collection = checknvme.NVMEDeviceCollection() ++ collection.add_device(nvme_pcie_device) ++ ++ # fstab contains the handled device ++ fstab_entries = [ ++ FstabEntry(fs_spec='/dev/nvme0n1p1', fs_file='/', fs_vfstype='ext4', ++ fs_mntops='defaults', fs_freq='1', fs_passno='1') ++ ] ++ storage_info = _make_storage_info(fstab_entries) ++ ++ msgs = [storage_info] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) ++ monkeypatch.setattr('os.path.realpath', lambda path: path) ++ ++ result = checknvme.check_unhandled_devices_present_in_fstab(collection) ++ ++ assert result is False ++ assert reporting.create_report.called == 0 +diff --git a/repos/system_upgrade/common/actors/checkpersistentmounts/libraries/checkpersistentmounts.py b/repos/system_upgrade/common/actors/checkpersistentmounts/libraries/checkpersistentmounts.py +index 2a35f4c5..79b431bb 100644 +--- a/repos/system_upgrade/common/actors/checkpersistentmounts/libraries/checkpersistentmounts.py ++++ b/repos/system_upgrade/common/actors/checkpersistentmounts/libraries/checkpersistentmounts.py +@@ -31,7 +31,7 @@ def check_mount_is_persistent(storage_info, mountpoint): + """Check if mountpoint is mounted in persistent fashion""" + + mount_entry_exists = any(me.mount == mountpoint for me in storage_info.mount) +- fstab_entry_exists = any(fe.fs_file == mountpoint for fe in storage_info.fstab) ++ fstab_entry_exists = any(fe.fs_file.rstrip('/') == mountpoint for fe in storage_info.fstab) + + if mount_entry_exists and not fstab_entry_exists: + inhibit_upgrade_due_non_persistent_mount(mountpoint) +diff --git a/repos/system_upgrade/common/actors/checkpersistentmounts/tests/test_checkpersistentmounts.py b/repos/system_upgrade/common/actors/checkpersistentmounts/tests/test_checkpersistentmounts.py +index fd6b3da3..14ce4e97 100644 +--- a/repos/system_upgrade/common/actors/checkpersistentmounts/tests/test_checkpersistentmounts.py ++++ b/repos/system_upgrade/common/actors/checkpersistentmounts/tests/test_checkpersistentmounts.py +@@ -11,6 +11,9 @@ MOUNT_ENTRY = MountEntry(name='/dev/sdaX', tp='ext4', mount='/var/lib/leapp', op + FSTAB_ENTRY = FstabEntry(fs_spec='', fs_file='/var/lib/leapp', fs_vfstype='', + fs_mntops='defaults', fs_freq='0', fs_passno='0') + ++FSTAB_ENTRY_TRAIL_SLASH = FstabEntry(fs_spec='', fs_file='/var/lib/leapp/', fs_vfstype='', ++ fs_mntops='defaults', fs_freq='0', fs_passno='0') ++ + + @pytest.mark.parametrize( + ('storage_info', 'should_inhibit'), +@@ -27,6 +30,10 @@ FSTAB_ENTRY = FstabEntry(fs_spec='', fs_file='/var/lib/leapp', fs_vfstype='', + StorageInfo(mount=[MOUNT_ENTRY], fstab=[FSTAB_ENTRY]), + False + ), ++ ( ++ StorageInfo(mount=[MOUNT_ENTRY], fstab=[FSTAB_ENTRY_TRAIL_SLASH]), ++ False ++ ), + ] + ) + def test_var_lib_leapp_non_persistent_is_detected(monkeypatch, storage_info, should_inhibit): diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh -index 56a94b5d..46c5d9b6 100755 +index 56a94b5d..758e1dfa 100755 --- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh +++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh +@@ -282,7 +282,7 @@ do_upgrade() { + local dirname + dirname="$("$NEWROOT/bin/dirname" "$NEWROOT$LEAPP_FAILED_FLAG_FILE")" + [ -d "$dirname" ] || mkdir "$dirname" +- ++ + echo >&2 "Creating file $NEWROOT$LEAPP_FAILED_FLAG_FILE" + echo >&2 "Warning: Leapp upgrade failed and there is an issue blocking the upgrade." + echo >&2 "Please file a support case with /var/log/leapp/leapp-upgrade.log attached" @@ -390,4 +390,3 @@ getarg 'rd.break=leapp-logs' 'rd.upgrade.break=leapp-finish' && { sync mount -o "remount,$old_opts" "$NEWROOT" exit $result - +diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh +index 45f98148..3f656d63 100755 +--- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh ++++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh +@@ -104,6 +104,12 @@ install() { + # script to actually run the upgrader binary + inst_hook upgrade 50 "$_moddir/do-upgrade.sh" + ++ # The initqueue checkscript to ensure all requested devices are mounted. ++ # The initqueue is usually left when rootfs (eventually /usr) is mounted ++ # but we require in this case whole fstab mounted under /sysroot. Without ++ # the script, the initqueue is left too early. ++ inst_hook initqueue/finished 99 "$moddir/upgrade-mount-wait-check.sh" ++ + #NOTE: some clean up?.. ideally, everything should be inside the leapp* + #NOTE: current *.service is changed so in case we would like to use the + # hook, we will have to modify it back +diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/upgrade-mount-wait-check.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/upgrade-mount-wait-check.sh +new file mode 100755 +index 00000000..5e21fa12 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/upgrade-mount-wait-check.sh +@@ -0,0 +1,50 @@ ++#!/bin/bash ++ ++# shellcheck disable=SC1091 # The file must be always present to boot the system ++type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh ++ ++log_debug() { ++ # TODO(pstodulk): The arg is probably not needed ++ getarg 'rd.upgrade.debug' && echo >&2 "Upgrade Initqueue Debug: $1" ++} ++ ++ ++check_reqs_in_dir() { ++ log_debug "Check resources from: $1" ++ result=0 ++ # shellcheck disable=SC2045 # Iterating over ls should be fine (there should be no whitespaces) ++ for fname in $(ls -1 "$1"); do ++ # We grep for What=/dev explicitly to exclude bind mounting units ++ resource_path=$(grep "^What=/dev/" "$1/$fname" | cut -d "=" -f2-) ++ if [ -z "$resource_path" ]; then ++ # Grep found no match, meaning that the unit is mounting something different than a block device ++ continue ++ fi ++ ++ grep -E "^Options=.*bind.*" "$1/$fname" &>/dev/null ++ is_bindmount=$? ++ if [ $is_bindmount -eq 0 ]; then ++ # The unit contains Options=...,bind,..., or Options=...,rbind,... so it is a bind mount -> skip ++ continue ++ fi ++ ++ grep -E "^Options=.*nofail.*" "$1/$fname" &>/dev/null ++ is_nofail=$? ++ if [ $is_nofail -eq 0 ]; then ++ # The unit contains Options=...,nofail,... so it is a nofail mount -> skip ++ continue ++ fi ++ ++ if [ ! -e "$resource_path" ]; then ++ log_debug "Waiting for missing resource: '$resource_path'" ++ result=1 ++ fi ++ done ++ ++ return $result ++} ++ ++SYSTEMD_DIR="/usr/lib/systemd/system" ++LOCAL_FS_MOUNT_DIR="$SYSTEMD_DIR/local-fs.target.requires" ++ ++check_reqs_in_dir "$LOCAL_FS_MOUNT_DIR" +diff --git a/repos/system_upgrade/common/actors/convert/securebootinhibit/actor.py b/repos/system_upgrade/common/actors/convert/securebootinhibit/actor.py +new file mode 100644 +index 00000000..53f41e71 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/securebootinhibit/actor.py +@@ -0,0 +1,19 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import securebootinhibit ++from leapp.models import FirmwareFacts ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class SecureBootInhibit(Actor): ++ """ ++ Inhibit the conversion if SecureBoot is enabled. ++ """ ++ ++ name = 'secure_boot_inhibit' ++ consumes = (FirmwareFacts,) ++ produces = (Report,) ++ tags = (IPUWorkflowTag, ChecksPhaseTag) ++ ++ def process(self): ++ securebootinhibit.process() +diff --git a/repos/system_upgrade/common/actors/convert/securebootinhibit/libraries/securebootinhibit.py b/repos/system_upgrade/common/actors/convert/securebootinhibit/libraries/securebootinhibit.py +new file mode 100644 +index 00000000..5edb9fa2 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/securebootinhibit/libraries/securebootinhibit.py +@@ -0,0 +1,42 @@ ++from leapp import reporting ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.common.config import is_conversion ++from leapp.libraries.stdlib import api ++from leapp.models import FirmwareFacts ++ ++ ++def process(): ++ if not is_conversion(): ++ return ++ ++ ff = next(api.consume(FirmwareFacts), None) ++ if not ff: ++ raise StopActorExecutionError( ++ "Could not identify system firmware", ++ details={"details": "Actor did not receive FirmwareFacts message."}, ++ ) ++ ++ if ff.firmware == "efi" and ff.secureboot_enabled: ++ report = [ ++ reporting.Title( ++ "Detected enabled Secure Boot when trying to convert the system" ++ ), ++ reporting.Summary( ++ "Conversion to a different Linux distribution is not possible" ++ " when the Secure Boot is enabled. Artifacts of the target" ++ " Linux distribution are signed by keys that are not accepted" ++ " by the source Linux distribution." ++ ), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([reporting.Groups.INHIBITOR, reporting.Groups.BOOT]), ++ # TODO some link ++ reporting.Remediation( ++ hint="Disable Secure Boot to be able to convert the system to" ++ " a different Linux distribution. Then re-enable Secure Boot" ++ " again after the upgrade process is finished successfully." ++ " Check instructions for your current OS, or hypervisor in" ++ " case of virtual machines, for more information how to" ++ " disable Secure Boot." ++ ), ++ ] ++ reporting.create_report(report) +diff --git a/repos/system_upgrade/common/actors/convert/securebootinhibit/tests/test_securebootinhibit.py b/repos/system_upgrade/common/actors/convert/securebootinhibit/tests/test_securebootinhibit.py +new file mode 100644 +index 00000000..340e6b16 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/securebootinhibit/tests/test_securebootinhibit.py +@@ -0,0 +1,58 @@ ++import pytest ++ ++from leapp import reporting ++from leapp.libraries.actor import securebootinhibit ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked ++from leapp.libraries.stdlib import api ++from leapp.models import FirmwareFacts ++ ++ ++@pytest.mark.parametrize( ++ 'ff,is_conversion,should_inhibit', [ ++ # conversion, secureboot enabled = inhibit ++ ( ++ FirmwareFacts(firmware='efi', ppc64le_opal=None, secureboot_enabled=True), ++ True, ++ True ++ ), ++ ( ++ FirmwareFacts(firmware='efi', ppc64le_opal=None, secureboot_enabled=True), ++ False, ++ False ++ ), ++ # bios is ok ++ ( ++ FirmwareFacts(firmware='bios', ppc64le_opal=None, secureboot_enabled=False), ++ False, ++ False ++ ), ++ # bios is ok during conversion too ++ ( ++ FirmwareFacts(firmware='bios', ppc64le_opal=None, secureboot_enabled=False), ++ True, ++ False ++ ), ++ ( ++ FirmwareFacts(firmware='efi', ppc64le_opal=None, secureboot_enabled=False), ++ True, ++ False ++ ), ++ ( ++ FirmwareFacts(firmware='efi', ppc64le_opal=None, secureboot_enabled=False), ++ False, ++ False ++ ), ++ ] ++) ++def test_process(monkeypatch, ff, is_conversion, should_inhibit): ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[ff])) ++ monkeypatch.setattr(reporting, "create_report", create_report_mocked()) ++ monkeypatch.setattr(securebootinhibit, "is_conversion", lambda: is_conversion) ++ ++ securebootinhibit.process() ++ ++ if should_inhibit: ++ assert reporting.create_report.called == 1 ++ assert reporting.Groups.INHIBITOR in reporting.create_report.report_fields['groups'] ++ else: ++ assert not reporting.create_report.called +diff --git a/repos/system_upgrade/common/actors/convert/updateefi/actor.py b/repos/system_upgrade/common/actors/convert/updateefi/actor.py +new file mode 100644 +index 00000000..4c97ebd7 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/updateefi/actor.py +@@ -0,0 +1,25 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import updateefi ++from leapp.models import FirmwareFacts ++from leapp.reporting import Report ++from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag ++ ++ ++class UpdateEfiEntry(Actor): ++ """ ++ Update EFI directory and entry during conversion. ++ ++ During conversion, removes leftover source distro EFI directory on the ESP ++ (EFI System Partition) and it's EFI boot entry. It also adds a new boot ++ entry for the target distro. ++ ++ This actor does nothing when not converting. ++ """ ++ ++ name = "update_efi" ++ consumes = (FirmwareFacts,) ++ produces = (Report,) ++ tags = (ApplicationsPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ updateefi.process() +diff --git a/repos/system_upgrade/common/actors/convert/updateefi/libraries/updateefi.py b/repos/system_upgrade/common/actors/convert/updateefi/libraries/updateefi.py +new file mode 100644 +index 00000000..1f300125 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/updateefi/libraries/updateefi.py +@@ -0,0 +1,230 @@ ++import errno ++import os ++ ++from leapp import reporting ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.common import efi ++from leapp.libraries.common.config import architecture, get_source_distro_id, get_target_distro_id, is_conversion ++from leapp.libraries.common.distro import distro_id_to_pretty_name, get_distro_efidir_canon_path ++from leapp.libraries.stdlib import api ++ ++ ++def _get_target_efi_bin_path(): ++ # Sorted by priority. ++ # NOTE: The shim-x64 package providing the shimx64.efi binary can be removed when ++ # not using secure boot, grubx64.efi should always be present (provided by ++ # grub-efi-x64). ++ # WARN: However it is expected to have the shim installed on the system to comply ++ # with the official guidelines. ++ # ++ # TODO: There are usually 2 more shim* files which appear unused on a fresh system: ++ # - shim.efi - seems like it's the same as shimx64.efi ++ # - shim64-.efi - ??? ++ # What about them? ++ efibins_by_arch = { ++ architecture.ARCH_X86_64: ("shimx64.efi", "grubx64.efi"), ++ architecture.ARCH_ARM64: ("shimaa64.efi", "grubaa64.efi"), ++ } ++ ++ arch = api.current_actor().configuration.architecture ++ for filename in efibins_by_arch[arch]: ++ efi_dir = get_distro_efidir_canon_path(get_target_distro_id()) ++ canon_path = os.path.join(efi_dir, filename) ++ if os.path.exists(canon_path): ++ return efi.canonical_path_to_efi_format(canon_path) ++ ++ return None ++ ++ ++def _add_boot_entry_for_target(efibootinfo): ++ """ ++ Create a new UEFI bootloader entry for the target system. ++ ++ Return the newly created bootloader entry. ++ """ ++ efi_bin_path = _get_target_efi_bin_path() ++ if not efi_bin_path: ++ # this is a fatal error as at least one of the possible EFI binaries ++ # should be present ++ raise efi.EFIError("Unable to detect any UEFI binary file.") ++ ++ label = distro_id_to_pretty_name(get_target_distro_id()) ++ ++ existing_entry = efi.get_boot_entry(efibootinfo, label, efi_bin_path) ++ if existing_entry: ++ api.current_logger().debug( ++ "The '{}' UEFI bootloader entry is already present.".format(label) ++ ) ++ return existing_entry ++ ++ return efi.add_boot_entry(label, efi_bin_path) ++ ++ ++def _remove_boot_entry_for_source(efibootinfo): ++ efibootinfo_fresh = efi.EFIBootInfo() ++ source_entry = efibootinfo_fresh.entries.get(efibootinfo.current_bootnum, None) ++ ++ if not source_entry: ++ api.current_logger().debug( ++ "The currently booted source distro EFI boot entry has been already" ++ " removed since the target entry has been added, skipping removal." ++ ) ++ return ++ ++ original_source_entry = efibootinfo.entries[source_entry.boot_number] ++ ++ if source_entry != original_source_entry: ++ api.current_logger().debug( ++ "The boot entry with current bootnum has changed since the target" ++ " distro entry has been added, skipping removal." ++ ) ++ return ++ ++ efi.remove_boot_entry(source_entry.boot_number) ++ ++ ++def _try_remove_source_efi_dir(): ++ """ ++ Try to remove the source distro EFI directory ++ ++ The directory is not reported if it's not empty to preserve potential ++ custom files. In such a case a post upgrade report is produced informing ++ user to handle the leftover files. ++ """ ++ efi_dir_source = get_distro_efidir_canon_path(get_source_distro_id()) ++ if not os.path.exists(efi_dir_source): ++ api.current_logger().debug( ++ "Source distro EFI directory at {} does not exist, skipping removal.".format(efi_dir_source) ++ ) ++ return ++ ++ target_efi_dir = get_distro_efidir_canon_path(get_target_distro_id()) ++ if efi_dir_source == target_efi_dir: ++ api.current_logger().debug( ++ "Source and target distros use the same '{}' EFI directory.".format(efi_dir_source) ++ ) ++ return ++ ++ try: ++ os.rmdir(efi_dir_source) ++ api.current_logger().debug( ++ "Deleted source system EFI directory at {}".format(efi_dir_source) ++ ) ++ except FileNotFoundError: ++ api.current_logger().debug( ++ "Couldn't remove the source system EFI directory at {}: the directory no longer exists".format( ++ efi_dir_source ++ ) ++ ) ++ except OSError as e: ++ if e.errno == errno.ENOTEMPTY: ++ api.current_logger().debug( ++ "Didn't remove the source EFI directory {}, it does not exist".format( ++ efi_dir_source ++ ) ++ ) ++ summary = ( ++ "During the upgrade, the EFI binaries and grub configuration files" ++ f" were migrated from the source OS EFI directory {efi_dir_source}" ++ f" to the target OS EFI directory {target_efi_dir}." ++ f" Leftover files were detected in {target_efi_dir}, review them" ++ " and migrate them manually." ++ ) ++ reporting.create_report([ ++ reporting.Title("Review leftover files in the source OS EFI directory"), ++ reporting.Summary(summary), ++ reporting.Groups([ ++ reporting.Groups.BOOT, ++ reporting.Groups.POST, ++ ]), ++ reporting.Severity(reporting.Severity.LOW), ++ ]) ++ else: ++ api.current_logger().error( ++ "Failed to remove the source system EFI directory at {}: {}".format( ++ efi_dir_source, e ++ ) ++ ) ++ summary = ( ++ f"Removal of the source system EFI directory at {efi_dir_source} failed." ++ " Remove the directory manually if present." ++ ) ++ reporting.create_report([ ++ reporting.Title("Failed to remove source system EFI directory"), ++ reporting.Summary(summary), ++ reporting.Groups([ ++ reporting.Groups.BOOT, ++ reporting.Groups.FAILURE, ++ reporting.Groups.POST, ++ ]), ++ reporting.Severity(reporting.Severity.LOW), ++ ]) ++ ++ ++def _replace_boot_entries(): ++ try: ++ efibootinfo = efi.EFIBootInfo() ++ target_entry = _add_boot_entry_for_target(efibootinfo) ++ # NOTE: this isn't strictly necessary as UEFI should set the next entry ++ # to be the first in the BootOrder. This is a workaround to make sure ++ # the "efi_finalization_fix" actor doesn't attempt to set BootNext to ++ # the original entry which will be deleted below. ++ efi.set_bootnext(target_entry.boot_number) ++ except efi.EFIError as e: ++ raise StopActorExecutionError( ++ "Failed to add UEFI boot entry for the target system", ++ details={"details": str(e)}, ++ ) ++ ++ # NOTE: Some UEFI implementations, such as OVMF used in qemu, automatically ++ # add entries for EFI directories. Though the entry is named after the EFI ++ # directory (so "redhat" on RHEL). However if the UEFI doesn't add an entry ++ # after we fail to do so, it might render the OS "unbootable". ++ # Let's keep the source entry and directory if we can't add the target entry as a ++ # backup. ++ ++ _try_remove_source_efi_dir() ++ ++ try: ++ # doesn't matter if the removal of source EFI dir failed, we don't want ++ # the source entry, we have the new one for target ++ _remove_boot_entry_for_source(efibootinfo) ++ except efi.EFIError as e: ++ api.current_logger().error("Failed to remove source distro EFI boot entry: {}".format(e)) ++ ++ # This is low severity, some UEFIs will automatically remove an entry ++ # whose EFI binary no longer exists at least OVMF, used by qemu, does. ++ summary = ( ++ "Removal of the source system UEFI boot entry failed." ++ " Check UEFI boot entries and manually remove it if it's still present." ++ ) ++ reporting.create_report( ++ [ ++ reporting.Title("Failed to remove source system EFI boot entry"), ++ reporting.Summary(summary), ++ reporting.Groups( ++ [ ++ reporting.Groups.BOOT, ++ reporting.Groups.FAILURE, ++ reporting.Groups.POST, ++ ] ++ ), ++ reporting.Severity(reporting.Severity.LOW), ++ ] ++ ) ++ ++ ++def process(): ++ if not is_conversion(): ++ return ++ ++ if not architecture.matches_architecture(architecture.ARCH_X86_64, architecture.ARCH_ARM64): ++ return ++ ++ if not efi.is_efi(): ++ return ++ ++ # NOTE no need to check whether we have the efibootmgr binary, the ++ # efi_check_boot actor does ++ ++ _replace_boot_entries() +diff --git a/repos/system_upgrade/common/actors/convert/updateefi/tests/test_updateefi.py b/repos/system_upgrade/common/actors/convert/updateefi/tests/test_updateefi.py +new file mode 100644 +index 00000000..0ad31cc5 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/updateefi/tests/test_updateefi.py +@@ -0,0 +1,469 @@ ++import copy ++import errno ++import os ++import types ++from unittest import mock ++ ++import pytest ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import updateefi ++from leapp.libraries.common import efi ++from leapp.libraries.common.config import architecture ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked ++from leapp.libraries.stdlib import api ++ ++ ++@pytest.fixture ++def mock_logger(): ++ with mock.patch( ++ "leapp.libraries.stdlib.api.current_logger", new_callable=logger_mocked ++ ) as mock_logger: ++ yield mock_logger ++ ++ ++@pytest.fixture ++def mock_create_report(): ++ with mock.patch( ++ "leapp.reporting.create_report", new_callable=create_report_mocked ++ ) as mock_create_report: ++ yield mock_create_report ++ ++ ++@pytest.mark.parametrize( ++ "arch, exist, expect", ++ [ ++ (architecture.ARCH_X86_64, ["shimx64.efi", "grubx64.efi"], r"\EFI\redhat\shimx64.efi"), ++ (architecture.ARCH_X86_64, ["shimx64.efi"], r"\EFI\redhat\shimx64.efi"), ++ (architecture.ARCH_X86_64, ["grubx64.efi"], r"\EFI\redhat\grubx64.efi"), ++ (architecture.ARCH_X86_64, [], None), ++ ++ (architecture.ARCH_ARM64, ["shimaa64.efi", "grubaa64.efi"], r"\EFI\redhat\shimaa64.efi"), ++ (architecture.ARCH_ARM64, ["shimaa64.efi"], r"\EFI\redhat\shimaa64.efi"), ++ (architecture.ARCH_ARM64, ["grubaa64.efi"], r"\EFI\redhat\grubaa64.efi"), ++ (architecture.ARCH_ARM64, [], None), ++ ] ++) ++def test__get_target_efi_bin_path(monkeypatch, arch, exist, expect): ++ # distro is not important, just make it look like conversion ++ curr_actor = CurrentActorMocked(arch=arch, src_distro="centos", dst_distro="rhel") ++ monkeypatch.setattr(api, "current_actor", curr_actor) ++ ++ def mock_exists(path): ++ efidir = "/boot/efi/EFI/redhat" ++ return path in [os.path.join(efidir, p) for p in exist] ++ ++ monkeypatch.setattr(os.path, "exists", mock_exists) ++ ++ actual = updateefi._get_target_efi_bin_path() ++ assert actual == expect ++ ++ ++TEST_ADD_ENTRY_INPUTS = [ ++ ("Red Hat Enterprise Linux", r"\EFI\redhat\shimx64.efi"), ++ ("Red Hat Enterprise Linux", r"\EFI\redhat\grubx64.efi"), ++ ("Centos Stream", r"\EFI\centos\grubx64.efi"), ++] ++ ++ ++@pytest.mark.parametrize("label, efi_bin_path", TEST_ADD_ENTRY_INPUTS) ++@mock.patch("leapp.libraries.common.efi.get_boot_entry") ++@mock.patch("leapp.libraries.common.efi.add_boot_entry") ++def test__add_boot_entry_for_target( ++ mock_add_boot_entry, mock_get_boot_entry, monkeypatch, label, efi_bin_path ++): ++ # need to mock this but it's unused because distro_id_to_pretty_name is mocked ++ monkeypatch.setattr(api, "current_actor", CurrentActorMocked(dst_distro="whatever")) ++ monkeypatch.setattr(updateefi, "distro_id_to_pretty_name", lambda _distro: label) ++ monkeypatch.setattr(updateefi, "_get_target_efi_bin_path", lambda: efi_bin_path) ++ ++ mock_efibootinfo = mock.MagicMock(name="EFIBootInfo_instance") ++ entry = efi.EFIBootLoaderEntry("0003", label, True, efi_bin_path) ++ mock_get_boot_entry.return_value = None ++ mock_add_boot_entry.return_value = entry ++ ++ assert entry == updateefi._add_boot_entry_for_target(mock_efibootinfo) ++ ++ mock_get_boot_entry.assert_called_once_with(mock_efibootinfo, label, efi_bin_path) ++ mock_add_boot_entry.assert_called_once_with(label, efi_bin_path) ++ ++ ++@pytest.mark.parametrize("label, efi_bin_path", TEST_ADD_ENTRY_INPUTS) ++@mock.patch("leapp.libraries.common.efi.get_boot_entry") ++@mock.patch("leapp.libraries.common.efi.add_boot_entry") ++def test__add_boot_entry_for_target_already_exists( ++ mock_add_boot_entry, mock_get_boot_entry, monkeypatch, label, efi_bin_path ++): ++ # need to mock this but it's unused because distro_id_to_pretty_name is mocked ++ monkeypatch.setattr(api, "current_actor", CurrentActorMocked(dst_distro="whatever")) ++ monkeypatch.setattr(updateefi, "distro_id_to_pretty_name", lambda _distro: label) ++ monkeypatch.setattr(updateefi, "_get_target_efi_bin_path", lambda: efi_bin_path) ++ ++ mock_efibootinfo = mock.MagicMock(name="EFIBootInfo_instance") ++ entry = efi.EFIBootLoaderEntry("0003", label, True, efi_bin_path) ++ mock_get_boot_entry.return_value = entry ++ ++ out = updateefi._add_boot_entry_for_target(mock_efibootinfo) ++ ++ assert out == entry ++ mock_get_boot_entry.assert_called_once_with(mock_efibootinfo, label, efi_bin_path) ++ mock_add_boot_entry.assert_not_called() ++ ++ ++def test__add_boot_entry_for_target_no_efi_bin(monkeypatch): ++ monkeypatch.setattr(updateefi, "_get_target_efi_bin_path", lambda: None) ++ ++ with pytest.raises(efi.EFIError, match="Unable to detect any UEFI binary file."): ++ mock_efibootinfo = mock.MagicMock(name="EFIBootInfo_instance") ++ updateefi._add_boot_entry_for_target(mock_efibootinfo) ++ ++ ++class MockEFIBootInfo: ++ ++ def __init__(self, entries, current_bootnum=None): ++ # just to have some entries even when we don't need the entries ++ other_entry = efi.EFIBootLoaderEntry( ++ "0001", ++ "UEFI: Built-in EFI Shell", ++ True, ++ "VenMedia(5023b95c-db26-429b-a648-bd47664c8012)..BO", ++ ) ++ entries = entries + [other_entry] ++ ++ self.boot_order = tuple(entry.boot_number for entry in entries) ++ self.current_bootnum = current_bootnum or self.boot_order[0] ++ self.next_bootnum = None ++ self.entries = {entry.boot_number: entry for entry in entries} ++ ++ ++TEST_SOURCE_ENTRY = efi.EFIBootLoaderEntry( ++ "0002", "Centos Stream", True, r"File(\EFI\centos\shimx64.efi)" ++) ++TEST_TARGET_ENTRY = efi.EFIBootLoaderEntry( ++ "0003", "Red Hat Enterprise Linux", True, r"File(\EFI\redhat\shimx64.efi)" ++) ++ ++ ++@mock.patch("leapp.libraries.common.efi.remove_boot_entry") ++@mock.patch("leapp.libraries.common.efi.EFIBootInfo") ++def test__remove_boot_entry_for_source( ++ mock_efibootinfo, ++ mock_remove_boot_entry, ++): ++ efibootinfo = MockEFIBootInfo([TEST_SOURCE_ENTRY], current_bootnum="0002") ++ mock_efibootinfo.return_value = MockEFIBootInfo( ++ [TEST_TARGET_ENTRY, TEST_SOURCE_ENTRY], current_bootnum="0002" ++ ) ++ ++ updateefi._remove_boot_entry_for_source(efibootinfo) ++ ++ mock_efibootinfo.assert_called_once() ++ mock_remove_boot_entry.assert_called_once_with("0002") ++ ++ ++@mock.patch("leapp.libraries.common.efi.remove_boot_entry") ++@mock.patch("leapp.libraries.common.efi.EFIBootInfo") ++def test__remove_boot_entry_for_source_no_longer_exists( ++ mock_efibootinfo, mock_remove_boot_entry, mock_logger ++): ++ efibootinfo = MockEFIBootInfo([TEST_SOURCE_ENTRY], current_bootnum="0002") ++ mock_efibootinfo.return_value = MockEFIBootInfo( ++ [TEST_TARGET_ENTRY], current_bootnum="0002" ++ ) ++ ++ updateefi._remove_boot_entry_for_source(efibootinfo) ++ ++ msg = ( ++ "The currently booted source distro EFI boot entry has been already" ++ " removed since the target entry has been added, skipping removal." ++ ) ++ assert msg in mock_logger.dbgmsg ++ mock_efibootinfo.assert_called_once() ++ mock_remove_boot_entry.assert_not_called() ++ ++ ++@mock.patch("leapp.libraries.common.efi.remove_boot_entry") ++@mock.patch("leapp.libraries.common.efi.EFIBootInfo") ++def test__remove_boot_entry_for_source_has_changed( ++ mock_efibootinfo, mock_remove_boot_entry, mock_logger ++): ++ efibootinfo = MockEFIBootInfo([TEST_SOURCE_ENTRY], current_bootnum="0002") ++ modified_source_entry = copy.copy(TEST_SOURCE_ENTRY) ++ modified_source_entry.efi_bin_source = r"File(\EFI\centos\grubx64.efi)" ++ mock_efibootinfo.return_value = MockEFIBootInfo( ++ [TEST_TARGET_ENTRY, modified_source_entry], current_bootnum="0002" ++ ) ++ ++ updateefi._remove_boot_entry_for_source(efibootinfo) ++ ++ msg = ( ++ "The boot entry with current bootnum has changed since the target" ++ " distro entry has been added, skipping removal." ++ ) ++ assert msg in mock_logger.dbgmsg ++ mock_efibootinfo.assert_called_once() ++ mock_remove_boot_entry.assert_not_called() ++ ++ ++class TestRemoveSourceEFIDir: ++ SOURCE_EFIDIR = "/boot/efi/EFI/centos" ++ TARGET_EFIDIR = "/boot/efi/EFI/redhat" ++ ++ @pytest.fixture(autouse=True) ++ def mock_current_actor(self): # pylint:disable=no-self-use ++ with mock.patch("leapp.libraries.stdlib.api.current_actor") as mock_current_actor: ++ mock_current_actor.return_value = CurrentActorMocked( ++ src_distro="centos", dst_distro="redhat" ++ ) ++ yield ++ ++ @mock.patch("os.path.exists") ++ @mock.patch("leapp.libraries.actor.updateefi.get_distro_efidir_canon_path") ++ @mock.patch("os.rmdir") ++ def test_success( ++ self, mock_rmdir, mock_efidir_path, mock_exists, mock_logger ++ ): ++ mock_efidir_path.side_effect = [self.SOURCE_EFIDIR, self.TARGET_EFIDIR] ++ ++ updateefi._try_remove_source_efi_dir() ++ ++ mock_exists.assert_called_once_with(self.SOURCE_EFIDIR) ++ mock_rmdir.assert_called_once_with(self.SOURCE_EFIDIR) ++ msg = f"Deleted source system EFI directory at {self.SOURCE_EFIDIR}" ++ assert msg in mock_logger.dbgmsg ++ ++ @mock.patch("os.path.exists") ++ @mock.patch("leapp.libraries.actor.updateefi.get_distro_efidir_canon_path") ++ @mock.patch("os.rmdir") ++ def test__efi_dir_does_not_exist( ++ self, mock_rmdir, mock_efidir_path, mock_exists, mock_logger ++ ): ++ mock_efidir_path.return_value = self.SOURCE_EFIDIR ++ mock_exists.return_value = False ++ ++ updateefi._try_remove_source_efi_dir() ++ ++ mock_exists.assert_called_once_with(self.SOURCE_EFIDIR) ++ mock_rmdir.assert_not_called() ++ msg = f"Source distro EFI directory at {self.SOURCE_EFIDIR} does not exist, skipping removal." ++ assert msg in mock_logger.dbgmsg ++ ++ @mock.patch("os.path.exists") ++ @mock.patch("leapp.libraries.actor.updateefi.get_distro_efidir_canon_path") ++ @mock.patch("os.rmdir") ++ def test_source_efi_dir_same_as_target( ++ self, mock_rmdir, mock_efidir_path, mock_exists, mock_logger ++ ): ++ """ ++ Source and target dirs use the same directory ++ """ ++ mock_efidir_path.side_effect = [self.TARGET_EFIDIR, self.TARGET_EFIDIR] ++ mock_exists.return_value = True ++ ++ updateefi._try_remove_source_efi_dir() ++ ++ mock_exists.assert_called_once_with(self.TARGET_EFIDIR) ++ mock_rmdir.assert_not_called() ++ msg = f"Source and target distros use the same '{self.TARGET_EFIDIR}' EFI directory." ++ assert msg in mock_logger.dbgmsg ++ ++ @mock.patch("os.path.exists") ++ @mock.patch("leapp.libraries.actor.updateefi.get_distro_efidir_canon_path") ++ @mock.patch("os.rmdir") ++ def test_rmdir_fail( ++ self, mock_rmdir, mock_efidir_path, mock_exists, mock_logger, mock_create_report ++ ): ++ """ ++ Test removal failures ++ """ ++ mock_efidir_path.side_effect = [self.SOURCE_EFIDIR, self.TARGET_EFIDIR] ++ mock_rmdir.side_effect = OSError ++ ++ updateefi._try_remove_source_efi_dir() ++ ++ mock_exists.assert_called_once_with(self.SOURCE_EFIDIR) ++ mock_rmdir.assert_called_once_with(self.SOURCE_EFIDIR) ++ msg = f"Failed to remove the source system EFI directory at {self.SOURCE_EFIDIR}" ++ assert msg in mock_logger.errmsg[0] ++ assert mock_create_report.called == 1 ++ title = "Failed to remove source system EFI directory" ++ assert mock_create_report.report_fields["title"] == title ++ ++ @mock.patch("os.path.exists") ++ @mock.patch("leapp.libraries.actor.updateefi.get_distro_efidir_canon_path") ++ @mock.patch("os.rmdir") ++ def test_dir_no_longer_exists_failed_rmdir( ++ self, mock_rmdir, mock_efidir_path, mock_exists, mock_logger ++ ): ++ mock_efidir_path.side_effect = [self.SOURCE_EFIDIR, self.TARGET_EFIDIR] ++ mock_rmdir.side_effect = FileNotFoundError( ++ 2, "No such file or directory", self.SOURCE_EFIDIR ++ ) ++ ++ updateefi._try_remove_source_efi_dir() ++ ++ mock_exists.assert_called_once_with(self.SOURCE_EFIDIR) ++ mock_rmdir.assert_called_once_with(self.SOURCE_EFIDIR) ++ msg = ( ++ "Couldn't remove the source system EFI directory at" ++ f" {self.SOURCE_EFIDIR}: the directory no longer exists" ++ ) ++ assert msg in mock_logger.dbgmsg[0] ++ ++ @mock.patch("os.path.exists") ++ @mock.patch("leapp.libraries.actor.updateefi.get_distro_efidir_canon_path") ++ @mock.patch("os.rmdir") ++ def test_dir_not_empty( ++ self, mock_rmdir, mock_efidir_path, mock_exists, mock_logger, mock_create_report ++ ): ++ """ ++ Test that the directory is not removed if there are any leftover files ++ ++ The distro provided files in the efi dir are usually removed during the RPM ++ upgrade transaction (shim and grub own them). If there are any leftover ++ files, such as custom user files, the directory should be preserved and ++ report created. ++ """ ++ mock_efidir_path.side_effect = [self.SOURCE_EFIDIR, self.TARGET_EFIDIR] ++ mock_rmdir.side_effect = OSError( ++ errno.ENOTEMPTY, os.strerror(errno.ENOTEMPTY), self.SOURCE_EFIDIR ++ ) ++ ++ updateefi._try_remove_source_efi_dir() ++ ++ mock_rmdir.assert_called_once_with(self.SOURCE_EFIDIR) ++ mock_exists.assert_called_once_with(self.SOURCE_EFIDIR) ++ msg = "Didn't remove the source EFI directory {}, it does not exist".format( ++ self.SOURCE_EFIDIR ++ ) ++ assert msg in mock_logger.dbgmsg[0] ++ assert mock_create_report.called == 1 ++ title = "Review leftover files in the source OS EFI directory" ++ assert mock_create_report.report_fields["title"] == title ++ ++ ++@pytest.mark.parametrize( ++ "is_conversion, arch, is_efi, should_skip", ++ [ ++ # conversion, is efi ++ (True, architecture.ARCH_X86_64, True, False), ++ (True, architecture.ARCH_ARM64, True, False), ++ (True, architecture.ARCH_PPC64LE, True, True), ++ (True, architecture.ARCH_S390X, True, True), ++ # conversion, not efi ++ (True, architecture.ARCH_X86_64, False, True), ++ (True, architecture.ARCH_ARM64, False, True), ++ (True, architecture.ARCH_PPC64LE, False, True), ++ (True, architecture.ARCH_S390X, False, True), ++ # not conversion, is efi ++ (False, architecture.ARCH_X86_64, True, True), ++ (False, architecture.ARCH_ARM64, True, True), ++ (False, architecture.ARCH_PPC64LE, True, True), ++ (False, architecture.ARCH_S390X, True, True), ++ # not conversion, not efi ++ (False, architecture.ARCH_X86_64, False, True), ++ (False, architecture.ARCH_ARM64, False, True), ++ (False, architecture.ARCH_PPC64LE, False, True), ++ (False, architecture.ARCH_S390X, False, True), ++ ], ++) ++@mock.patch("leapp.libraries.actor.updateefi._replace_boot_entries") ++def test_process_skip( ++ mock_replace_boot_entries, monkeypatch, is_conversion, arch, is_efi, should_skip ++): ++ monkeypatch.setattr(api, "current_actor", CurrentActorMocked(arch=arch)) ++ monkeypatch.setattr(updateefi, "is_conversion", lambda: is_conversion) ++ monkeypatch.setattr(efi, "is_efi", lambda: is_efi) ++ ++ updateefi.process() ++ ++ if should_skip: ++ mock_replace_boot_entries.assert_not_called() ++ else: ++ mock_replace_boot_entries.assert_called_once() ++ ++ ++class TestReplaceBootEntries: ++ ++ @pytest.fixture ++ def mocks(self): # pylint:disable=no-self-use ++ UPDATE_EFI = 'leapp.libraries.actor.updateefi' ++ EFI_LIB = 'leapp.libraries.common.efi' ++ with mock.patch(f'{UPDATE_EFI}._try_remove_source_efi_dir') as remove_source_dir, \ ++ mock.patch(f'{UPDATE_EFI}._remove_boot_entry_for_source') as remove_source_entry, \ ++ mock.patch(f'{UPDATE_EFI}._add_boot_entry_for_target') as add_target_entry, \ ++ mock.patch(f'{EFI_LIB}.set_bootnext') as set_bootnext, \ ++ mock.patch(f'{EFI_LIB}.EFIBootInfo') as efibootinfo: ++ ++ # default for happy path ++ efibootinfo_obj = mock.MagicMock(name="EFIBootInfo_instance") ++ efibootinfo.return_value = efibootinfo_obj ++ ++ entry = mock.MagicMock(name="target_entry") ++ entry.boot_number = "0003" ++ add_target_entry.return_value = entry ++ ++ yield types.SimpleNamespace( ++ EFIBootInfo=efibootinfo, ++ set_bootnext=set_bootnext, ++ add_boot_entry_for_target=add_target_entry, ++ try_remove_source_efi_dir=remove_source_dir, ++ remove_boot_entry_for_source=remove_source_entry, ++ logger=mock_logger, ++ ) ++ ++ def test__fail_remove_source_entry( # pylint:disable=no-self-use ++ self, mocks, mock_logger, mock_create_report ++ ): ++ mocks.remove_boot_entry_for_source.side_effect = efi.EFIError ++ ++ updateefi._replace_boot_entries() ++ ++ msg = "Failed to remove source distro EFI boot entry" ++ assert msg in mock_logger.errmsg[0] ++ ++ assert mock_create_report.called == 1 ++ title = "Failed to remove source system EFI boot entry" ++ assert mock_create_report.report_fields["title"] == title ++ ++ @pytest.mark.parametrize( ++ "which_fail", ["EFIBootInfo", "add_target", "set_bootnext"] ++ ) ++ def test__fail_add_target_entry( # pylint:disable=no-self-use ++ self, mocks, mock_logger, mock_create_report, which_fail ++ ): ++ if which_fail == "EFIBootInfo": ++ mocks.EFIBootInfo.side_effect = efi.EFIError ++ elif which_fail == "add_target": ++ mocks.add_boot_entry_for_target.side_effect = efi.EFIError ++ elif which_fail == "set_bootnext": ++ mocks.set_bootnext.side_effect = efi.EFIError ++ ++ with pytest.raises(StopActorExecutionError): ++ updateefi._replace_boot_entries() ++ ++ mocks.try_remove_source_efi_dir.assert_not_called() ++ mocks.remove_boot_entry_for_source.assert_not_called() ++ assert not mock_create_report.called ++ ++ def test__replace_boot_entries_success( # pylint:disable=no-self-use ++ self, mocks, mock_logger ++ ): ++ """Test that operations are carried out in the right order""" ++ mgr = mock.MagicMock() ++ mgr.attach_mock(mocks.EFIBootInfo, "EFIBootInfo") ++ mgr.attach_mock(mocks.set_bootnext, "set_bootnext") ++ mgr.attach_mock(mocks.add_boot_entry_for_target, "add_target_entry") ++ mgr.attach_mock(mocks.remove_boot_entry_for_source, "remove_source_entry") ++ mgr.attach_mock(mocks.try_remove_source_efi_dir, "remove_source_efidir") ++ ++ updateefi._replace_boot_entries() ++ ++ expected_sequence = [ ++ mock.call.EFIBootInfo(), ++ mock.call.add_target_entry(efi.EFIBootInfo.return_value), ++ mock.call.set_bootnext(mocks.add_boot_entry_for_target.return_value.boot_number), ++ mock.call.remove_source_efidir(), ++ mock.call.remove_source_entry(efi.EFIBootInfo.return_value), ++ ] ++ assert mgr.mock_calls == expected_sequence diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py index 003f3fc5..9e7bbf4a 100644 --- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py @@ -7024,6 +17597,134 @@ index f42909f0..6383a56f 100644 + + if not has_grub_cfg: + run(['/sbin/grub2-mkconfig', '-o', grub_cfg_path]) +diff --git a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/actor.py b/repos/system_upgrade/common/actors/emit_net_naming_scheme/actor.py +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/actor.py +rename to repos/system_upgrade/common/actors/emit_net_naming_scheme/actor.py +diff --git a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/libraries/emit_net_naming.py b/repos/system_upgrade/common/actors/emit_net_naming_scheme/libraries/emit_net_naming.py +similarity index 79% +rename from repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/libraries/emit_net_naming.py +rename to repos/system_upgrade/common/actors/emit_net_naming_scheme/libraries/emit_net_naming.py +index bab62a56..7b112ff0 100644 +--- a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/libraries/emit_net_naming.py ++++ b/repos/system_upgrade/common/actors/emit_net_naming_scheme/libraries/emit_net_naming.py +@@ -1,5 +1,5 @@ + from leapp.exceptions import StopActorExecutionError +-from leapp.libraries.common.config import get_env, version ++from leapp.libraries.common.config import get_env, get_target_distro_id, version + from leapp.libraries.stdlib import api + from leapp.models import ( + KernelCmdline, +@@ -10,7 +10,10 @@ from leapp.models import ( + UpgradeKernelCmdlineArgTasks + ) + +-NET_NAMING_SYSATTRS_RPM_NAME = 'rhel-net-naming-sysattrs' ++NET_NAMING_SYSATTRS_RPM_NAME = { ++ '9': 'rhel-net-naming-sysattrs', ++ '10': 'net-naming-sysattrs', ++} + + + def is_net_scheme_compatible_with_current_cmdline(): +@@ -45,15 +48,21 @@ def is_net_scheme_compatible_with_current_cmdline(): + + def emit_msgs_to_use_net_naming_schemes(): + is_feature_enabled = get_env('LEAPP_DISABLE_NET_NAMING_SCHEMES', '0') != '1' +- is_upgrade_8to9 = version.get_target_major_version() == '9' +- is_net_naming_enabled_and_permitted = is_feature_enabled and is_upgrade_8to9 +- if not is_net_naming_enabled_and_permitted: ++ ++ is_net_naming_available = version.get_target_major_version() == "9" or ( ++ version.matches_target_version(">= 10.2") ++ # TODO the net-naming-sysattrs pkg is not yet available on CS10, remove ++ # this when it becomes ++ and not get_target_distro_id() == "centos" ++ ) ++ ++ if not (is_feature_enabled and is_net_naming_available): + return + + # The package should be installed regardless of whether we will modify the cmdline - + # if the cmdline already contains net.naming-scheme, then the package will be useful + # in both, the upgrade environment and on the target system. +- pkgs_to_install = [NET_NAMING_SYSATTRS_RPM_NAME] ++ pkgs_to_install = [NET_NAMING_SYSATTRS_RPM_NAME[version.get_target_major_version()]] + api.produce(TargetUserSpaceUpgradeTasks(install_rpms=pkgs_to_install)) + api.produce(RpmTransactionTasks(to_install=pkgs_to_install)) + +diff --git a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py b/repos/system_upgrade/common/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py +similarity index 74% +rename from repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py +rename to repos/system_upgrade/common/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py +index acf72241..9c1f91bb 100644 +--- a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py ++++ b/repos/system_upgrade/common/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py +@@ -50,12 +50,32 @@ def test_is_net_scheme_compatible_with_current_cmdline(monkeypatch, kernel_args, + (False, True) + ] + ) +-def test_emit_msgs_to_use_net_naming_schemes(monkeypatch, is_net_scheme_enabled, is_current_cmdline_compatible): +- envvar_value = '0' if is_net_scheme_enabled else '1' +- +- mocked_actor = CurrentActorMocked(src_ver='8.10', +- dst_ver='9.5', +- envars={'LEAPP_DISABLE_NET_NAMING_SCHEMES': envvar_value}) ++@pytest.mark.parametrize( ++ 'source_ver, target_ver', ++ [ ++ ('8.10', '9.5'), ++ ('8.10', '9.8'), ++ ('9.6', '10.0'), ++ ('9.8', '10.2'), ++ ] ++) ++@pytest.mark.parametrize('target_distro', ['rhel', 'centos']) ++def test_emit_msgs_to_use_net_naming_schemes( ++ monkeypatch, ++ is_net_scheme_enabled, ++ is_current_cmdline_compatible, ++ source_ver, ++ target_ver, ++ target_distro, ++): ++ mocked_actor = CurrentActorMocked( ++ src_ver=source_ver, ++ dst_ver=target_ver, ++ dst_distro=target_distro, ++ envars={ ++ "LEAPP_DISABLE_NET_NAMING_SCHEMES": "0" if is_net_scheme_enabled else "1" ++ }, ++ ) + monkeypatch.setattr(api, 'current_actor', mocked_actor) + + monkeypatch.setattr(api, 'produce', produce_mocked()) +@@ -71,13 +91,22 @@ def test_emit_msgs_to_use_net_naming_schemes(monkeypatch, is_net_scheme_enabled, + assert not next(msgs, None), 'More than one message of type {type} produced'.format(type=type) + return msg + ++ target_major = target_ver.split(".")[0] ++ pkg_name = emit_net_naming_lib.NET_NAMING_SYSATTRS_RPM_NAME[target_major] + produced_messages = api.produce.model_instances +- if is_net_scheme_enabled: ++ ++ if ( ++ is_net_scheme_enabled ++ # the package is available since 10.2 ++ and target_ver != "10.0" ++ # TODO not yet available in CS 10, remove this when it is ++ and not (target_distro == "centos" and target_major == "10") ++ ): + userspace_tasks = ensure_one_msg_of_type_produced(produced_messages, TargetUserSpaceUpgradeTasks) +- assert userspace_tasks.install_rpms == [emit_net_naming_lib.NET_NAMING_SYSATTRS_RPM_NAME] ++ assert userspace_tasks.install_rpms == [pkg_name] + + rpm_tasks = ensure_one_msg_of_type_produced(produced_messages, RpmTransactionTasks) +- assert rpm_tasks.to_install == [emit_net_naming_lib.NET_NAMING_SYSATTRS_RPM_NAME] ++ assert rpm_tasks.to_install == [pkg_name] + else: + assert not api.produce.called + return diff --git a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py b/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py index 582a5821..18f2c33f 100644 --- a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py @@ -7059,6 +17760,217 @@ index 582a5821..18f2c33f 100644 + to_reinstall=list(to_reinstall), modules_to_reset=list(modules_to_reset.values()), modules_to_enable=list(modules_to_enable.values()))) +diff --git a/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/actor.py b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/actor.py +new file mode 100644 +index 00000000..4a3cd85d +--- /dev/null ++++ b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/actor.py +@@ -0,0 +1,22 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import fix_nvmf_initqueue_rules as fix_nvmf_initqueue_rules_lib ++from leapp.models import LiveModeConfig, NVMEInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks ++from leapp.tags import InterimPreparationPhaseTag, IPUWorkflowTag ++ ++ ++class FixNvmfInitqueueRules(Actor): ++ """ ++ Replace nvmf dracut module's initqueue rules with a our own version. ++ ++ The original 95-nvmf-initqueue.rules file in the nvmf dracut module ++ calls initqueue, which might not be running when the udev event lands. ++ Therefore, we call `nvme connect-all` directly when when the udev event is triggered. ++ """ ++ ++ name = 'fix_nvmf_initqueue_rules' ++ consumes = (LiveModeConfig, NVMEInfo, TargetUserSpaceInfo) ++ produces = (UpgradeInitramfsTasks,) ++ tags = (IPUWorkflowTag, InterimPreparationPhaseTag) ++ ++ def process(self): ++ fix_nvmf_initqueue_rules_lib.replace_nvmf_initqueue_rules() +diff --git a/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/files/95-nvmf-initqueue.rules b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/files/95-nvmf-initqueue.rules +new file mode 100644 +index 00000000..52a77fef +--- /dev/null ++++ b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/files/95-nvmf-initqueue.rules +@@ -0,0 +1,7 @@ ++# ++# Original nvmf-initqueue rules called initqueue, which might not be running when the udev event lands. ++# Therefore, we call it directly. ++ ++ACTION=="change", SUBSYSTEM=="fc", ENV{FC_EVENT}=="nvmediscovery", \ ++ ENV{NVMEFC_HOST_TRADDR}=="*", ENV{NVMEFC_TRADDR}=="*", \ ++ RUN+="/usr/sbin/nvme connect-all --transport=fc --traddr=$env{NVMEFC_TRADDR} --host-traddr=$env{NVMEFC_HOST_TRADDR}" +diff --git a/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/libraries/fix_nvmf_initqueue_rules.py b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/libraries/fix_nvmf_initqueue_rules.py +new file mode 100644 +index 00000000..9fd74ea9 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/libraries/fix_nvmf_initqueue_rules.py +@@ -0,0 +1,66 @@ ++import os ++import shutil ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.stdlib import api ++from leapp.models import LiveModeConfig, NVMEInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks ++ ++NVMF_DRACUT_MODULE_DIR = '/usr/lib/dracut/modules.d/95nvmf' ++NVMF_INITQUEUE_RULES_FILENAME = '95-nvmf-initqueue.rules' ++NVMF_INITQUEUE_RULES_PATH = os.path.join(NVMF_DRACUT_MODULE_DIR, NVMF_INITQUEUE_RULES_FILENAME) ++ ++ ++def _get_rules_file_path(): ++ """ ++ Get the path to the fixed 95-nvmf-initqueue.rules file bundled with this actor. ++ """ ++ return api.get_actor_file_path(NVMF_INITQUEUE_RULES_FILENAME) ++ ++ ++def is_livemode_enabled() -> bool: ++ livemode_config = next(api.consume(LiveModeConfig), None) ++ if livemode_config and livemode_config.is_enabled: ++ return True ++ return False ++ ++ ++def replace_nvmf_initqueue_rules(): ++ """ ++ Replace the nvmf dracut module's initqueue rules in the target userspace. ++ """ ++ nvme_info = next(api.consume(NVMEInfo), None) ++ if not nvme_info or not nvme_info.devices: ++ api.current_logger().debug('No NVMe devices detected, skipping nvmf initqueue rules replacement.') ++ return ++ ++ if is_livemode_enabled(): ++ api.current_logger().debug('LiveMode is enabled. Modifying initqueue stop condition is not required.') ++ return ++ ++ userspace_info = next(api.consume(TargetUserSpaceInfo), None) ++ source_rules_path = _get_rules_file_path() ++ ++ target_rules_path = os.path.join(userspace_info.path, NVMF_INITQUEUE_RULES_PATH.lstrip('/')) ++ target_dir = os.path.dirname(target_rules_path) ++ ++ # Check if the nvmf dracut module directory exists in the target userspace ++ if not os.path.isdir(target_dir): ++ api.current_logger().debug( ++ 'The nvmf dracut module directory {} does not exist in target userspace. ' ++ 'Skipping rules replacement.'.format(target_dir) ++ ) ++ return ++ ++ api.current_logger().info( ++ 'Replacing {} in target userspace with fixed version.'.format(NVMF_INITQUEUE_RULES_PATH) ++ ) ++ ++ try: ++ shutil.copy2(source_rules_path, target_rules_path) ++ api.current_logger().debug( ++ 'Successfully copied {} to {}'.format(source_rules_path, target_rules_path) ++ ) ++ except (IOError, OSError) as e: ++ raise StopActorExecutionError('Failed to copy nvmf initqueue rules to target userspace: {}'.format(e)) ++ ++ api.produce(UpgradeInitramfsTasks()) # To enforce ordering of actors +diff --git a/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/tests/test_fix_nvmf_initqueue_rules.py b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/tests/test_fix_nvmf_initqueue_rules.py +new file mode 100644 +index 00000000..93bc0285 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/initramfs/fix_nvmf_initqueue_rules/tests/test_fix_nvmf_initqueue_rules.py +@@ -0,0 +1,92 @@ ++import os ++import tempfile ++ ++from leapp.libraries.actor import fix_nvmf_initqueue_rules ++from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import LiveModeConfig, NVMEDevice, NVMEInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks ++ ++ ++def test_replace_nvmf_initqueue_rules_no_nvme_devices(monkeypatch): ++ """Test that replacement is skipped when no NVMe devices are detected.""" ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ ++ fix_nvmf_initqueue_rules.replace_nvmf_initqueue_rules() ++ ++ assert any('No NVMe devices detected' in msg for msg in api.current_logger.dbgmsg) ++ ++ ++def test_replace_nvmf_initqueue_rules_livemode_enabled(monkeypatch): ++ """Test that replacement is skipped when no LiveMode is enabled.""" ++ livemode_info = LiveModeConfig( ++ is_enabled=True, ++ squashfs_fullpath='' ++ ) ++ ++ nvme_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='fc' ++ ) ++ nvme_info = NVMEInfo(devices=[nvme_device], hostid='test-hostid', hostnqn='test-hostnqn') ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[livemode_info, nvme_info])) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ ++ fix_nvmf_initqueue_rules.replace_nvmf_initqueue_rules() ++ ++ assert any('LiveMode is enabled.' in msg for msg in api.current_logger.dbgmsg) ++ ++ ++def test_replace_nvmf_initqueue_rules_empty_nvme_devices(monkeypatch): ++ """Test that replacement is skipped when NVMEInfo has no devices.""" ++ nvme_info = NVMEInfo(devices=[], hostid=None, hostnqn=None) ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[nvme_info])) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ ++ fix_nvmf_initqueue_rules.replace_nvmf_initqueue_rules() ++ ++ assert any('No NVMe devices detected' in msg for msg in api.current_logger.dbgmsg) ++ ++ ++def test_replace_nvmf_initqueue_rules_success(monkeypatch): ++ """Test successful replacement of nvmf initqueue rules.""" ++ with tempfile.TemporaryDirectory(prefix='leapp_test_') as tmpdir: ++ nvmf_dir = os.path.join(tmpdir, 'usr/lib/dracut/modules.d/95nvmf') ++ os.makedirs(nvmf_dir) ++ ++ target_rules_path = os.path.join(nvmf_dir, '95-nvmf-initqueue.rules') ++ with open(target_rules_path, 'w') as f: ++ f.write('# original rules') ++ ++ source_file = os.path.join(tmpdir, 'source_rules') ++ with open(source_file, 'w') as f: ++ f.write('# fixed rules content') ++ ++ nvme_device = NVMEDevice( ++ sys_class_path='/sys/class/nvme/nvme0', ++ name='nvme0', ++ transport='fc' ++ ) ++ nvme_info = NVMEInfo(devices=[nvme_device], hostid='test-hostid', hostnqn='test-hostnqn') ++ userspace_info = TargetUserSpaceInfo(path=tmpdir, scratch='/tmp/scratch', mounts='/tmp/mounts') ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[nvme_info, userspace_info])) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(api, 'get_actor_file_path', lambda x: source_file) ++ ++ fix_nvmf_initqueue_rules.replace_nvmf_initqueue_rules() ++ ++ # Verify the file was replaced ++ with open(target_rules_path) as f: ++ content = f.read() ++ ++ assert content == '# fixed rules content' ++ ++ # Verify UpgradeInitramfsTasks was produced ++ assert api.produce.called == 1 ++ produced_msg = api.produce.model_instances[0] ++ assert isinstance(produced_msg, UpgradeInitramfsTasks) diff --git a/repos/system_upgrade/common/actors/livemode/emit_livemode_userspace_requirements/libraries/emit_livemode_userspace_requirements.py b/repos/system_upgrade/common/actors/livemode/emit_livemode_userspace_requirements/libraries/emit_livemode_userspace_requirements.py index 4ecf682b..80d38cb0 100644 --- a/repos/system_upgrade/common/actors/livemode/emit_livemode_userspace_requirements/libraries/emit_livemode_userspace_requirements.py @@ -7130,6 +18042,96 @@ index e24aa366..6eb71fee 100644 EnablementTestCase(env_vars={'LEAPP_UNSUPPORTED': '1'}, arch=architecture.ARCH_ARM64, pkgs=tuple(), result=EnablementResult.RAISE), +diff --git a/repos/system_upgrade/common/actors/livemode/removeliveimage/libraries/remove_live_image.py b/repos/system_upgrade/common/actors/livemode/removeliveimage/libraries/remove_live_image.py +index 5bb7e40f..a3718dcf 100644 +--- a/repos/system_upgrade/common/actors/livemode/removeliveimage/libraries/remove_live_image.py ++++ b/repos/system_upgrade/common/actors/livemode/removeliveimage/libraries/remove_live_image.py +@@ -21,5 +21,11 @@ def remove_live_image(): + + try: + os.unlink(artifacts.squashfs_path) ++ except FileNotFoundError: ++ api.current_logger().debug( ++ 'The %s file does not exist. Most likely it has been removed before. Usually happens with "leapp rerun".', ++ artifacts.squashfs_path ++ ) ++ return + except OSError as error: +- api.current_logger().warning('Failed to remove %s with error: %s', artifacts.squashfs, error) ++ api.current_logger().warning('Failed to remove %s with error: %s', artifacts.squashfs_path, error) +diff --git a/repos/system_upgrade/common/actors/livemode/removeliveimage/tests/test_remove_live_image.py b/repos/system_upgrade/common/actors/livemode/removeliveimage/tests/test_remove_live_image.py +index 4d6aa821..21a5fb93 100644 +--- a/repos/system_upgrade/common/actors/livemode/removeliveimage/tests/test_remove_live_image.py ++++ b/repos/system_upgrade/common/actors/livemode/removeliveimage/tests/test_remove_live_image.py +@@ -1,10 +1,11 @@ ++import errno + import functools + import os + + import pytest + + from leapp.libraries.actor import remove_live_image as remove_live_image_lib +-from leapp.libraries.common.testutils import CurrentActorMocked ++from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked + from leapp.libraries.stdlib import api + from leapp.models import LiveModeArtifacts, LiveModeConfig + +@@ -22,23 +23,51 @@ _LiveModeConfig = functools.partial(LiveModeConfig, squashfs_fullpath='configure + ) + ) + def test_remove_live_image(monkeypatch, livemode_config, squashfs_path, should_unlink_be_called): +- """ Test whether live-mode image (as found in LiveModeArtifacts) is removed. """ +- ++ """ ++ Test whether live-mode image (as found in LiveModeArtifacts) is removed. ++ """ + messages = [] + if livemode_config: + messages.append(livemode_config) + if squashfs_path: + messages.append(LiveModeArtifacts(squashfs_path=squashfs_path)) + +- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=messages)) +- + def unlink_mock(path): + if should_unlink_be_called: + assert path == squashfs_path + return + assert False # If we should not call unlink and we call it then fail the test ++ + monkeypatch.setattr(os, 'unlink', unlink_mock) ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=messages)) ++ ++ remove_live_image_lib.remove_live_image() ++ ++ ++@pytest.mark.parametrize('do_file_exists', (True, False)) ++def test_remove_live_image_oserror(monkeypatch, do_file_exists): ++ """ ++ Test that errors are properly handled when trying to unlink the file. ++ """ ++ messages = [ ++ _LiveModeConfig(is_enabled=True), ++ LiveModeArtifacts(squashfs_path='/var/lib/leapp/upgrade.img') ++ ] ++ ++ def unlink_mock(dummyPath): ++ if do_file_exists: ++ raise OSError('OSError happened :)') ++ raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), '/squashfs') + + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=messages)) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ monkeypatch.setattr(os, 'unlink', unlink_mock) + + remove_live_image_lib.remove_live_image() ++ ++ if do_file_exists: ++ assert api.current_logger.warnmsg ++ assert not api.current_logger.dbgmsg ++ else: ++ assert not api.current_logger.warnmsg ++ assert api.current_logger.dbgmsg diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py index 32e4527b..1e595e9a 100644 --- a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py @@ -7745,6 +18747,366 @@ index 772b33e6..95f664ad 100644 + + assert 'Failed to parse custom repository definition' in str(exc_info.value) + assert scancustomrepofile.CUSTOM_REPO_PATH in exc_info.value.details['hint'] +diff --git a/repos/system_upgrade/common/actors/scangrubdevice/actor.py b/repos/system_upgrade/common/actors/scangrubdevice/actor.py +index cb6be7ea..e6f9bf8a 100644 +--- a/repos/system_upgrade/common/actors/scangrubdevice/actor.py ++++ b/repos/system_upgrade/common/actors/scangrubdevice/actor.py +@@ -1,6 +1,5 @@ + from leapp.actors import Actor +-from leapp.libraries.common import grub +-from leapp.libraries.common.config import architecture ++from leapp.libraries.actor import scangrubdevice + from leapp.models import GrubInfo + from leapp.tags import FactsPhaseTag, IPUWorkflowTag + +@@ -16,10 +15,4 @@ class ScanGrubDeviceName(Actor): + tags = (FactsPhaseTag, IPUWorkflowTag) + + def process(self): +- if architecture.matches_architecture(architecture.ARCH_S390X): +- return +- +- devices = grub.get_grub_devices() +- grub_info = GrubInfo(orig_devices=devices) +- grub_info.orig_device_name = devices[0] if len(devices) == 1 else None +- self.produce(grub_info) ++ scangrubdevice.process() +diff --git a/repos/system_upgrade/common/actors/scangrubdevice/libraries/scangrubdevice.py b/repos/system_upgrade/common/actors/scangrubdevice/libraries/scangrubdevice.py +new file mode 100644 +index 00000000..608c67e5 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scangrubdevice/libraries/scangrubdevice.py +@@ -0,0 +1,22 @@ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.common import grub ++from leapp.libraries.common.config import architecture ++from leapp.libraries.stdlib import api ++from leapp.models import GrubInfo ++ ++ ++def process(): ++ if architecture.matches_architecture(architecture.ARCH_S390X): ++ return ++ ++ try: ++ devices = grub.get_grub_devices() ++ except grub.GRUBDeviceError as err: ++ raise StopActorExecutionError( ++ message='Cannot detect GRUB devices', ++ details={'details': str(err)} ++ ) ++ ++ grub_info = GrubInfo(orig_devices=devices) ++ grub_info.orig_device_name = devices[0] if len(devices) == 1 else None ++ api.produce(grub_info) +diff --git a/repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py b/repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py +index 0114d717..50c5ce8d 100644 +--- a/repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py ++++ b/repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py +@@ -1,35 +1,68 @@ ++import pytest ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import scangrubdevice + from leapp.libraries.common import grub +-from leapp.libraries.common.config import mock_configs ++from leapp.libraries.common.config import architecture ++from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked ++from leapp.libraries.stdlib import api + from leapp.models import GrubInfo + + +-def _get_grub_devices_mocked(): +- return ['/dev/vda', '/dev/vdb'] +- ++def test_process_one_dev(monkeypatch): ++ def _get_grub_devices_mocked(): ++ return ['/dev/vda'] + +-def test_actor_scan_grub_device(current_actor_context, monkeypatch): + monkeypatch.setattr(grub, 'get_grub_devices', _get_grub_devices_mocked) +- current_actor_context.run(config_model=mock_configs.CONFIG) +- info = current_actor_context.consume(GrubInfo) +- assert info and info[0].orig_devices == ['/dev/vda', '/dev/vdb'] +- assert len(info) == 1, 'Expected just one GrubInfo message' +- assert not info[0].orig_device_name ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) + ++ scangrubdevice.process() + +-def test_actor_scan_grub_device_one(current_actor_context, monkeypatch): ++ assert api.produce.called == 1 ++ assert len(api.produce.model_instances) == 1 ++ grubinfo = api.produce.model_instances[0] ++ assert isinstance(grubinfo, GrubInfo) ++ assert grubinfo.orig_devices == ['/dev/vda'] ++ assert grubinfo.orig_device_name == '/dev/vda' + ++ ++def test_process_multiple_devs(monkeypatch): + def _get_grub_devices_mocked(): +- return ['/dev/vda'] ++ return ['/dev/vda', '/dev/vdb'] + + monkeypatch.setattr(grub, 'get_grub_devices', _get_grub_devices_mocked) +- current_actor_context.run(config_model=mock_configs.CONFIG) +- info = current_actor_context.consume(GrubInfo) +- assert info and info[0].orig_devices == ['/dev/vda'] +- assert len(info) == 1, 'Expected just one GrubInfo message' +- assert info[0].orig_device_name == '/dev/vda' ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ ++ scangrubdevice.process() ++ ++ assert api.produce.called == 1 ++ assert len(api.produce.model_instances) == 1 ++ grubinfo = api.produce.model_instances[0] ++ assert isinstance(grubinfo, GrubInfo) ++ assert grubinfo.orig_devices == ['/dev/vda', '/dev/vdb'] ++ assert grubinfo.orig_device_name is None + + +-def test_actor_scan_grub_device_s390x(current_actor_context, monkeypatch): ++def test_process_no_produce_on_s390x(monkeypatch): ++ monkeypatch.setattr( ++ api, "current_actor", CurrentActorMocked(arch=architecture.ARCH_S390X) ++ ) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ ++ scangrubdevice.process() ++ ++ assert api.produce.called == 0 ++ ++ ++def test_process_fail_to_get_grubdevs(monkeypatch): ++ ++ def _get_grub_devices_mocked(): ++ raise grub.GRUBDeviceError() ++ + monkeypatch.setattr(grub, 'get_grub_devices', _get_grub_devices_mocked) +- current_actor_context.run(config_model=mock_configs.CONFIG_S390X) +- assert not current_actor_context.consume(GrubInfo) ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) ++ ++ with pytest.raises(StopActorExecutionError, match='Cannot detect GRUB devices'): ++ scangrubdevice.process() +diff --git a/repos/system_upgrade/common/actors/scannvme/actor.py b/repos/system_upgrade/common/actors/scannvme/actor.py +new file mode 100644 +index 00000000..a4f7aefe +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scannvme/actor.py +@@ -0,0 +1,25 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import scannvme ++from leapp.models import NVMEInfo ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++ ++class ScanNVMe(Actor): ++ """ ++ Detect existing NVMe devices. ++ ++ The detection is performed by checking content under /sys/class/nvme/ ++ directory where all NVMe devices should be listed. Additional information ++ is collected from the present files under each specific device. ++ ++ Namely the NVMe transport type and the device name is collected at this ++ moment. ++ """ ++ ++ name = 'scan_nvme' ++ consumes = () ++ produces = (NVMEInfo,) ++ tags = (FactsPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ scannvme.process() +diff --git a/repos/system_upgrade/common/actors/scannvme/libraries/scannvme.py b/repos/system_upgrade/common/actors/scannvme/libraries/scannvme.py +new file mode 100644 +index 00000000..ef77171d +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scannvme/libraries/scannvme.py +@@ -0,0 +1,88 @@ ++import os ++ ++from leapp.libraries.common.utils import read_file ++from leapp.libraries.stdlib import api ++from leapp.models import NVMEDevice, NVMEInfo ++ ++NVME_CLASS_DIR = '/sys/class/nvme' ++NVME_CONF_DIR = '/etc/nvme' ++NVME_CONF_HOSTID = '/etc/nvme/hostid' ++NVME_CONF_HOSTNQN = '/etc/nvme/hostnqn' ++ ++ ++class NVMEMissingTransport(Exception): ++ def __init__(self, message): ++ super().__init__(message) ++ self.message = message ++ ++ ++def _get_transport_type(device_path): ++ tpath = os.path.join(device_path, 'transport') ++ if not os.path.exists(tpath): ++ raise NVMEMissingTransport(f'The {tpath} file is missing.') ++ ++ transport = read_file(tpath).strip() ++ if not transport: ++ raise NVMEMissingTransport('The transport type is not defined.') ++ ++ return transport ++ ++ ++def scan_device(device_name): ++ device_path = os.path.join(NVME_CLASS_DIR, device_name) ++ if not os.path.isdir(device_path): ++ api.current_logger().warning( ++ 'Cannot scan NVMe device: Following path is not dir: {0}'.format(device_path) ++ ) ++ return None ++ ++ try: ++ transport = _get_transport_type(device_path) ++ except NVMEMissingTransport as e: ++ # unexpected; seatbelt - skipping tests ++ api.current_logger().warning( ++ 'Skipping {0} NVMe device: Cannot detect transport type: {1}'.format(device_name, e.message) ++ ) ++ return None ++ ++ return NVMEDevice( ++ sys_class_path=device_path, ++ name=device_name, ++ transport=transport ++ ) ++ ++ ++def get_hostid(fpath=NVME_CONF_HOSTID): ++ if not os.path.exists(fpath): ++ api.current_logger().debug('NVMe hostid config file is missing.') ++ return None ++ return read_file(fpath).strip() ++ ++ ++def get_hostnqn(fpath=NVME_CONF_HOSTNQN): ++ if not os.path.exists(fpath): ++ api.current_logger().debug('NVMe hostnqn config file is missing.') ++ return None ++ return read_file(fpath).strip() ++ ++ ++def process(): ++ if not os.path.isdir(NVME_CLASS_DIR): ++ api.current_logger().debug( ++ 'NVMe is not active: {0} does not exist.'.format(NVME_CLASS_DIR) ++ ) ++ return ++ ++ devices = [scan_device(device_name) for device_name in os.listdir(NVME_CLASS_DIR)] ++ # drop possible None values from the list ++ devices = [dev for dev in devices if dev is not None] ++ if not devices: ++ # NOTE(pstodulk): This could be suspicious possibly. ++ api.current_logger().warning('No NVMe device detected but NVMe seems active.') ++ return ++ ++ api.produce(NVMEInfo( ++ devices=devices, ++ hostnqn=get_hostnqn(), ++ hostid=get_hostid(), ++ )) +diff --git a/repos/system_upgrade/common/actors/scannvme/tests/test_scannvme.py b/repos/system_upgrade/common/actors/scannvme/tests/test_scannvme.py +new file mode 100644 +index 00000000..97b3980b +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scannvme/tests/test_scannvme.py +@@ -0,0 +1,84 @@ ++import pytest ++ ++from leapp.libraries.actor import scannvme ++from leapp.models import NVMEDevice ++ ++ ++def test_get_transport_type_file_missing(monkeypatch): ++ """Test that NVMEMissingTransport is raised when transport file does not exist.""" ++ monkeypatch.setattr('os.path.join', lambda *args: '/sys/class/nvme/nvme0/transport') ++ monkeypatch.setattr('os.path.exists', lambda path: False) ++ ++ with pytest.raises(scannvme.NVMEMissingTransport): ++ scannvme._get_transport_type('/sys/class/nvme/nvme0') ++ ++ ++def test_get_transport_type_file_empty(monkeypatch): ++ """Test that NVMEMissingTransport is raised when transport file is empty.""" ++ monkeypatch.setattr('os.path.join', lambda *args: '/sys/class/nvme/nvme0/transport') ++ monkeypatch.setattr('os.path.exists', lambda path: True) ++ monkeypatch.setattr( ++ 'leapp.libraries.actor.scannvme.read_file', ++ lambda path: ' \n' ++ ) ++ ++ with pytest.raises(scannvme.NVMEMissingTransport): ++ scannvme._get_transport_type('/sys/class/nvme/nvme0') ++ ++ ++@pytest.mark.parametrize('transport_value', ['pcie', 'tcp', 'rdma', 'fc', 'loop']) ++def test_get_transport_type_valid(monkeypatch, transport_value): ++ """Test that transport type is correctly read from the file.""" ++ monkeypatch.setattr('os.path.join', lambda *args: '/sys/class/nvme/nvme0/transport') ++ monkeypatch.setattr('os.path.exists', lambda path: True) ++ monkeypatch.setattr(scannvme, 'read_file', lambda path: transport_value + '\n') ++ ++ result = scannvme._get_transport_type('/sys/class/nvme/nvme0') ++ assert result == transport_value ++ ++ ++def test_scan_device_transport_detection_fails(monkeypatch): ++ """Test that None is returned when transport detection fails.""" ++ monkeypatch.setattr('os.path.join', lambda *args: '/'.join(args)) ++ monkeypatch.setattr('os.path.isdir', lambda path: True) ++ monkeypatch.setattr('os.path.exists', lambda path: False) ++ ++ result = scannvme.scan_device('nvme0') ++ ++ assert result is None ++ ++ ++@pytest.mark.parametrize('device_name,transport', [ ++ ('nvme0', 'pcie'), ++ ('nvme1', 'tcp'), ++ ('nvme2', 'rdma'), ++]) ++def test_scan_device_successful(monkeypatch, device_name, transport): ++ """Test that NVMEDevice is returned for a valid device.""" ++ expected_device_path = '/sys/class/nvme/{}'.format(device_name) ++ expected_transport_path = '{}/transport'.format(expected_device_path) ++ ++ def mock_isdir(path): ++ assert path == expected_device_path ++ return True ++ ++ def mock_exists(path): ++ assert path == expected_transport_path ++ return True ++ ++ def mock_read_file(path): ++ assert path == expected_transport_path ++ return transport + '\n' ++ ++ monkeypatch.setattr('os.path.join', lambda *args: '/'.join(args)) ++ monkeypatch.setattr('os.path.isdir', mock_isdir) ++ monkeypatch.setattr('os.path.exists', mock_exists) ++ monkeypatch.setattr(scannvme, 'read_file', mock_read_file) ++ ++ result = scannvme.scan_device(device_name) ++ ++ assert result is not None ++ assert isinstance(result, NVMEDevice) ++ assert result.name == device_name ++ assert result.transport == transport ++ assert result.sys_class_path == expected_device_path diff --git a/repos/system_upgrade/common/actors/scanvendorrepofiles/actor.py b/repos/system_upgrade/common/actors/scanvendorrepofiles/actor.py new file mode 100644 index 00000000..a5e481cb @@ -8235,7 +19597,7 @@ index 59b12c87..85d4a09e 100644 def process(self): self.produce(systemfacts.get_sysctls_status()) diff --git a/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py b/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py -index f16cea1d..b14e2a09 100644 +index f16cea1d..ba7bdb82 100644 --- a/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py +++ b/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py @@ -221,14 +221,13 @@ def get_repositories_status(): @@ -8259,11 +19621,220 @@ index f16cea1d..b14e2a09 100644 def get_selinux_status(): +@@ -295,12 +294,35 @@ def get_firewalls_status(): + ) + + ++def _get_secure_boot_state(): ++ try: ++ stdout = run(['mokutil', '--sb-state'])['stdout'] ++ return 'enabled' in stdout ++ except CalledProcessError as e: ++ if "doesn't support Secure Boot" in e.stderr: ++ return None ++ ++ raise StopActorExecutionError('Failed to determine SecureBoot state: {}'.format(e)) ++ except OSError as e: ++ # shim depends on mokutil, if it's not installed assume SecureBoot is disabled ++ api.current_logger().debug( ++ 'Failed to execute mokutil, assuming SecureBoot is disabled: {}'.format(e) ++ ) ++ return False ++ ++ + def get_firmware(): + firmware = 'efi' if os.path.isdir('/sys/firmware/efi') else 'bios' ++ ++ ppc64le_opal = None + if architecture.matches_architecture(architecture.ARCH_PPC64LE): +- ppc64le_opal = bool(os.path.isdir('/sys/firmware/opal/')) +- return FirmwareFacts(firmware=firmware, ppc64le_opal=ppc64le_opal) +- return FirmwareFacts(firmware=firmware) ++ ppc64le_opal = os.path.isdir('/sys/firmware/opal/') ++ ++ is_secureboot = None ++ if firmware == 'efi': ++ is_secureboot = _get_secure_boot_state() ++ ++ return FirmwareFacts(firmware=firmware, ppc64le_opal=ppc64le_opal, secureboot_enabled=is_secureboot) + + + @aslist +diff --git a/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts.py b/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts.py +index 16405b15..22ee7b7b 100644 +--- a/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts.py ++++ b/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts.py +@@ -1,20 +1,27 @@ + import grp ++import os + import pwd ++from unittest import mock + + import pytest + + from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import systemfacts + from leapp.libraries.actor.systemfacts import ( ++ _get_secure_boot_state, + _get_system_groups, + _get_system_users, + anyendswith, + anyhasprefix, + aslist, ++ get_firmware, + get_repositories_status + ) + from leapp.libraries.common import repofileutils +-from leapp.libraries.common.testutils import logger_mocked +-from leapp.libraries.stdlib import api ++from leapp.libraries.common.config import architecture ++from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked ++from leapp.libraries.stdlib import api, CalledProcessError ++from leapp.models import FirmwareFacts + from leapp.snactor.fixture import current_actor_libraries + + +@@ -138,3 +145,114 @@ def test_failed_parsed_repofiles(monkeypatch): + + with pytest.raises(StopActorExecutionError): + get_repositories_status() ++ ++ ++@pytest.mark.parametrize('is_enabled', (True, False)) ++@mock.patch('leapp.libraries.actor.systemfacts.run') ++def test_get_secure_boot_state_ok(mocked_run: mock.MagicMock, is_enabled): ++ mocked_run.return_value = { ++ 'stdout': f'SecureBoot {"enabled" if is_enabled else "disabled"}' ++ } ++ ++ out = _get_secure_boot_state() ++ ++ assert out == is_enabled ++ mocked_run.assert_called_once_with(['mokutil', '--sb-state']) ++ ++ ++@mock.patch('leapp.libraries.actor.systemfacts.run') ++def test_get_secure_boot_state_no_mokutil(mocked_run: mock.MagicMock): ++ mocked_run.side_effect = OSError ++ ++ out = _get_secure_boot_state() ++ ++ assert out is False ++ mocked_run.assert_called_once_with(['mokutil', '--sb-state']) ++ ++ ++@mock.patch('leapp.libraries.actor.systemfacts.run') ++def test_get_secure_boot_state_not_supported(mocked_run: mock.MagicMock): ++ cmd = ['mokutil', '--sb-state'] ++ result = { ++ 'stderr': "This system doesn't support Secure Boot", ++ 'exit_code': 255, ++ } ++ mocked_run.side_effect = CalledProcessError( ++ "Command mokutil --sb-state failed with exit code 255.", ++ cmd, ++ result ++ ) ++ ++ out = _get_secure_boot_state() ++ ++ assert out is None ++ mocked_run.assert_called_once_with(cmd) ++ ++ ++@mock.patch('leapp.libraries.actor.systemfacts.run') ++def test_get_secure_boot_state_failed(mocked_run: mock.MagicMock): ++ cmd = ['mokutil', '--sb-state'] ++ result = { ++ 'stderr': 'EFI variables are not supported on this system', ++ 'exit_code': 1, ++ } ++ mocked_run.side_effect = CalledProcessError( ++ "Command mokutil --sb-state failed with exit code 1.", ++ cmd, ++ result ++ ) ++ ++ with pytest.raises( ++ StopActorExecutionError, ++ match='Failed to determine SecureBoot state' ++ ): ++ _get_secure_boot_state() ++ ++ mocked_run.assert_called_once_with(cmd) ++ ++ ++def _ff(firmware, ppc64le_opal, is_secureboot): ++ return FirmwareFacts( ++ firmware=firmware, ++ ppc64le_opal=ppc64le_opal, ++ secureboot_enabled=is_secureboot ++ ) ++ ++ ++@pytest.mark.parametrize( ++ "has_sys_efi, has_sys_opal, is_ppc, secboot_state, expect", ++ [ ++ # 1. Standard BIOS on x86 ++ (False, False, False, None, _ff("bios", None, None)), ++ # 2. EFI on x86 with Secure Boot Enabled ++ (True, False, False, True, _ff("efi", None, True)), ++ # 3. EFI on x86 with Secure Boot Disabled ++ (True, False, False, False, _ff("efi", None, False)), ++ # 4. PPC64LE with OPAL (No EFI) ++ (False, True, True, None, _ff("bios", True, None)), ++ # 5. PPC64LE without OPAL (No EFI) ++ (False, False, True, None, _ff("bios", False, None)), ++ # 6. EFI on PPC64LE with OPAL ++ (True, True, True, True, _ff("efi", True, True)), ++ ] ++) ++def test_get_firmware_logic( ++ has_sys_efi, has_sys_opal, is_ppc, secboot_state, expect ++): ++ with mock.patch('os.path.isdir') as mock_isdir, \ ++ mock.patch('leapp.libraries.stdlib.api.current_actor') as mock_curr_actor, \ ++ mock.patch('leapp.libraries.actor.systemfacts._get_secure_boot_state') as mock_get_sb_state: ++ ++ mock_isdir.side_effect = lambda path: { ++ '/sys/firmware/efi': has_sys_efi, ++ '/sys/firmware/opal/': has_sys_opal ++ }.get(path, False) ++ ++ mock_curr_actor.return_value = CurrentActorMocked( ++ arch=architecture.ARCH_PPC64LE if is_ppc else architecture.ARCH_X86_64 ++ ) ++ mock_get_sb_state.return_value = secboot_state ++ ++ result = get_firmware() ++ ++ assert result == expect diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -index c825c731..a428aa98 100644 +index c825c731..55dec131 100644 --- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py +++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -@@ -155,9 +155,10 @@ def _import_gpg_keys(context, install_root_dir, target_major_version): +@@ -7,8 +7,18 @@ from leapp import reporting + from leapp.exceptions import StopActorExecution, StopActorExecutionError + from leapp.libraries.actor import constants + from leapp.libraries.common import distro, dnfplugin, mounting, overlaygen, repofileutils, rhsm, utils +-from leapp.libraries.common.config import get_env, get_product_type, get_source_distro_id, get_target_distro_id +-from leapp.libraries.common.config.version import get_target_major_version, get_target_version ++from leapp.libraries.common.config import ( ++ get_env, ++ get_product_type, ++ get_source_distro_id, ++ get_target_distro_id, ++ is_conversion ++) ++from leapp.libraries.common.config.version import ( ++ get_source_major_version, ++ get_target_major_version, ++ get_target_version ++) + from leapp.libraries.common.gpg import get_path_to_gpg_certs, is_nogpgcheck_set + from leapp.libraries.stdlib import api, CalledProcessError, config, run + from leapp.models import RequiredTargetUserspacePackages # deprecated +@@ -155,9 +165,10 @@ def _import_gpg_keys(context, install_root_dir, target_major_version): # installation of initial packages try: # Import also any other keys provided by the customer in the same directory @@ -8277,7 +19848,7 @@ index c825c731..a428aa98 100644 except CalledProcessError as exc: raise StopActorExecutionError( message=( -@@ -660,6 +661,7 @@ def _prep_repository_access(context, target_userspace): +@@ -660,6 +671,7 @@ def _prep_repository_access(context, target_userspace): run(["chroot", target_userspace, "/bin/bash", "-c", "su - -c update-ca-trust"]) if not rhsm.skip_rhsm(): @@ -8285,7 +19856,7 @@ index c825c731..a428aa98 100644 run(['rm', '-rf', os.path.join(target_etc, 'rhsm')]) context.copytree_from('/etc/rhsm', os.path.join(target_etc, 'rhsm')) -@@ -820,7 +822,15 @@ def _inhibit_on_duplicate_repos(repofiles): +@@ -820,7 +832,15 @@ def _inhibit_on_duplicate_repos(repofiles): def _get_all_available_repoids(context): @@ -8302,7 +19873,40 @@ index c825c731..a428aa98 100644 # TODO: this is not good solution, but keep it as it is now # Issue: #486 if rhsm.skip_rhsm(): -@@ -1302,7 +1312,15 @@ def setup_target_rhui_access_if_needed(context, indata): +@@ -973,14 +993,29 @@ def _get_distro_available_repoids(context, indata): + provider has itw own rpm). + On other: Repositories are provided in specific repofiles (e.g. centos.repo + and centos-addons.repo on CS) ++ Exception: On CS8->CS9 and AL8->AL9 there are no distro-provided ++ repoids as the repofile layout and urls are different ++ Conversions: Only custom repos - no distro repoids (all distros) + + :return: A set of repoids provided by distribution + :rtype: set[str] + """ + distro_repoids = distro.get_target_distro_repoids(context) +- distro_id = get_target_distro_id() +- rhel_and_rhsm = distro_id == 'rhel' and not rhsm.skip_rhsm() +- if distro_id != 'rhel' or rhel_and_rhsm: ++ target_distro = get_target_distro_id() ++ rhel_and_rhsm = target_distro == 'rhel' and not rhsm.skip_rhsm() ++ is_source_cs8 = ( ++ get_source_distro_id() == "centos" and get_source_major_version() == '8' ++ ) ++ is_source_almalinux8 = ( ++ get_source_distro_id() == "almalinux" and get_source_major_version() == '8' ++ ) ++ ++ if ( ++ not is_conversion() # conversions only work with custom repos ++ and not is_source_cs8 # there are no distro_repoids on CS8->CS9 ++ and not is_source_almalinux8 # there are no distro_repoids on AL8->AL9 ++ and (target_distro != "rhel" or rhel_and_rhsm) ++ ): + _inhibit_if_no_base_repos(distro_repoids) + + if indata and indata.rhui_info: +@@ -1302,7 +1337,15 @@ def setup_target_rhui_access_if_needed(context, indata): copied_repofiles = [copy.src for copy in copy_tasks if copy.src.endswith('.repo')] copied_repoids = set() for repofile in copied_repofiles: @@ -8319,7 +19923,7 @@ index c825c731..a428aa98 100644 copied_repoids.update(entry.repoid for entry in repofile_contents.data) cmd += ['--disablerepo', '*'] -@@ -1403,7 +1421,17 @@ def perform(): +@@ -1403,7 +1446,17 @@ def perform(): target_repoids = _gather_target_repositories(context, indata, prod_cert_path) _create_target_userspace(context, indata, indata.packages, indata.files, target_repoids) # TODO: this is tmp solution as proper one needs significant refactoring @@ -8338,6 +19942,50 @@ index c825c731..a428aa98 100644 api.produce(TMPTargetRepositoriesFacts(repositories=target_repo_facts)) # ## TODO ends here api.produce(UsedTargetRepositories( +diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py b/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py +index d783843c..f4ce390f 100644 +--- a/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py ++++ b/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py +@@ -1216,17 +1216,23 @@ def test__get_distro_available_repoids_norhsm_norhui(monkeypatch): + assert repoids == set() + + ++@pytest.mark.parametrize("src_distro", ["rhel", "centos", "almalinux"]) + @pytest.mark.parametrize( +- "distro_id,skip_rhsm", [("rhel", False), ("centos", True), ("almalinux", True)] ++ "dst_distro, skip_rhsm", [("rhel", False), ("centos", True), ("almalinux", True)] + ) ++@pytest.mark.parametrize("src_ver, dst_ver", [("9.6", "10.2"), ("8.10", "9.6")]) + def test__get_distro_available_repoids_nobaserepos_inhibit( +- monkeypatch, distro_id, skip_rhsm ++ monkeypatch, src_distro, dst_distro, skip_rhsm, src_ver, dst_ver + ): + """ + Test that get_distro_available repoids reports and raises if there are no base repos. + """ + monkeypatch.setattr( +- userspacegen.api, "current_actor", CurrentActorMocked(dst_distro=distro_id) ++ userspacegen.api, ++ "current_actor", ++ CurrentActorMocked( ++ src_distro=src_distro, dst_distro=dst_distro, src_ver=src_ver, dst_ver=dst_ver ++ ), + ) + monkeypatch.setattr(userspacegen.api.current_actor(), 'produce', produce_mocked()) + monkeypatch.setattr(reporting, "create_report", create_report_mocked()) +@@ -1235,6 +1241,12 @@ def test__get_distro_available_repoids_nobaserepos_inhibit( + monkeypatch.setattr(distro, 'get_target_distro_repoids', lambda ctx: []) + + indata = testInData(_PACKAGES_MSGS, None, None, _XFS_MSG, _STORAGEINFO_MSG, None) ++ ++ if src_distro in ("centos", "almalinux") and src_ver == "8.10" or src_distro != dst_distro: ++ # should not raise on CS 8to9, AL 8to9, and when converting ++ userspacegen._get_distro_available_repoids(None, indata) ++ return ++ + with pytest.raises(StopActorExecution): + # NOTE: context is not used without rhsm, for simplicity setting to None + userspacegen._get_distro_available_repoids(None, indata) diff --git a/repos/system_upgrade/common/actors/trustedgpgkeysscanner/libraries/trustedgpgkeys.py b/repos/system_upgrade/common/actors/trustedgpgkeysscanner/libraries/trustedgpgkeys.py index 6377f767..4c5420f6 100644 --- a/repos/system_upgrade/common/actors/trustedgpgkeysscanner/libraries/trustedgpgkeys.py @@ -8364,6 +20012,44 @@ index 6377f767..4c5420f6 100644 return pubkeys +diff --git a/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py b/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py +index 6a116db4..cc9bf280 100644 +--- a/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py ++++ b/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py +@@ -1,4 +1,5 @@ + from leapp import reporting ++from leapp.exceptions import StopActorExecution + from leapp.libraries.common import grub + from leapp.libraries.common.config import architecture + from leapp.libraries.stdlib import api, CalledProcessError, config, run +@@ -61,7 +62,11 @@ def process(): + return + ff = next(api.consume(FirmwareFacts), None) + if ff and ff.firmware == 'bios': +- grub_devs = grub.get_grub_devices() ++ try: ++ grub_devs = grub.get_grub_devices() ++ except grub.GRUBDeviceError as err: ++ api.current_logger().warning('Failed to detect GRUB devices: %s', err) ++ raise StopActorExecution() + if grub_devs: + update_grub_core(grub_devs) + else: +diff --git a/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py b/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py +index 93816103..2262e326 100644 +--- a/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py ++++ b/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py +@@ -107,9 +107,7 @@ def test_update_grub_nogrub_system_ibmz(monkeypatch): + + def test_update_grub_nogrub_system(monkeypatch): + def get_grub_devices_mocked(): +- # this is not very well documented, but the grub.get_grub_devices function raises a StopActorExecution on error +- # (whether that's caused by determining root partition or determining the block device a given partition is on +- raise StopActorExecution() ++ raise grub.GRUBDeviceError() + + monkeypatch.setattr(grub, 'get_grub_devices', get_grub_devices_mocked) + monkeypatch.setattr(reporting, 'create_report', testutils.create_report_mocked()) diff --git a/repos/system_upgrade/common/actors/vendorreposignaturescanner/actor.py b/repos/system_upgrade/common/actors/vendorreposignaturescanner/actor.py new file mode 100644 index 00000000..dbf86974 @@ -8779,11 +20465,82 @@ index 63910fe0..4e8b380d 100644 self.base.distro_sync() if self.opts.tid[0] == 'check': +diff --git a/repos/system_upgrade/common/files/upgrade_paths.json b/repos/system_upgrade/common/files/upgrade_paths.json +index 39fbe6c1..ca0fe590 100644 +--- a/repos/system_upgrade/common/files/upgrade_paths.json ++++ b/repos/system_upgrade/common/files/upgrade_paths.json +@@ -2,19 +2,18 @@ + "rhel": { + "default": { + "7.9": ["8.10"], +- "8.10": ["9.4", "9.6", "9.7", "9.8"], ++ "8.10": ["9.6", "9.8"], + "9.6": ["10.0"], +- "9.7": ["10.1"], + "9.8": ["10.2"], + "7": ["8.10"], +- "8": ["9.4", "9.6"], ++ "8": ["9.6", "9.8"], + "9": ["10.0"] + }, + "saphana": { + "7.9": ["8.10"], + "7": ["8.10"], +- "8.10": ["9.6", "9.4"], +- "8": ["9.6", "9.4"], ++ "8.10": ["9.8", "9.6"], ++ "8": ["9.8", "9.6"], + "9.6": ["10.0"], + "9.8": ["10.2"], + "9": ["10.0"] +diff --git a/repos/system_upgrade/common/libraries/config/__init__.py b/repos/system_upgrade/common/libraries/config/__init__.py +index 396c524a..8a2b4e35 100644 +--- a/repos/system_upgrade/common/libraries/config/__init__.py ++++ b/repos/system_upgrade/common/libraries/config/__init__.py +@@ -141,3 +141,19 @@ def get_target_distro_id(): + :rtype: str + """ + return api.current_actor().configuration.distro.target ++ ++ ++def is_conversion(): ++ """ ++ Return whether a conversion is happening during the upgrade. ++ ++ Conversions in means that a target distro different from source distro was ++ specified. ++ ++ This is a wrapper which compares source and target distro IDs. This can also ++ be helpful for testing. ++ ++ :return: True if converting False otherwise ++ :rtype: bool ++ """ ++ return get_source_distro_id() != get_target_distro_id() +diff --git a/repos/system_upgrade/common/libraries/config/version.py b/repos/system_upgrade/common/libraries/config/version.py +index c9bc3fb2..5c1e30c6 100644 +--- a/repos/system_upgrade/common/libraries/config/version.py ++++ b/repos/system_upgrade/common/libraries/config/version.py +@@ -16,7 +16,7 @@ OP_MAP = { + + _SUPPORTED_VERSIONS = { + '8': {'rhel': ['8.10'], 'rhel-saphana': ['8.10']}, +- '9': {'rhel': ['9.6'], 'rhel-saphana': ['9.6']}, ++ '9': {'rhel': ['9.6', '9.8'], 'rhel-saphana': ['9.6', '9.8']}, + } + + diff --git a/repos/system_upgrade/common/libraries/distro.py b/repos/system_upgrade/common/libraries/distro.py -index 04e553ac..fbf5a1b5 100644 +index 04e553ac..734d152b 100644 --- a/repos/system_upgrade/common/libraries/distro.py +++ b/repos/system_upgrade/common/libraries/distro.py -@@ -7,6 +7,7 @@ from leapp.libraries.common.config import get_target_distro_id +@@ -2,11 +2,12 @@ import json + import os + + from leapp.exceptions import StopActorExecutionError +-from leapp.libraries.common import repofileutils, rhsm ++from leapp.libraries.common import efi, repofileutils, rhsm + from leapp.libraries.common.config import get_target_distro_id from leapp.libraries.common.config.architecture import ARCH_ACCEPTED, ARCH_X86_64 from leapp.libraries.common.config.version import get_target_major_version from leapp.libraries.stdlib import api @@ -8812,15 +20569,7 @@ index 04e553ac..fbf5a1b5 100644 # distro -> major_version -> repofile -> tuple of architectures where it's present _DISTRO_REPOFILES_MAP = { -@@ -68,6 +76,7 @@ _DISTRO_REPOFILES_MAP = { - '/etc/yum.repos.d/almalinux.repo': ARCH_ACCEPTED, - }, - '9': { -+ '/etc/yum.repos.d/almalinux.repo': ARCH_ACCEPTED, - '/etc/yum.repos.d/almalinux-appstream.repo': ARCH_ACCEPTED, - '/etc/yum.repos.d/almalinux-baseos.repo': ARCH_ACCEPTED, - '/etc/yum.repos.d/almalinux-crb.repo': ARCH_ACCEPTED, -@@ -177,7 +186,18 @@ def get_distro_repoids(context, distro, major_version, arch): +@@ -177,7 +185,18 @@ def get_distro_repoids(context, distro, major_version, arch): # TODO: very similar thing should happens for all other repofiles in container return rhsm.get_available_repo_ids(context) @@ -8840,8 +20589,44 @@ index 04e553ac..fbf5a1b5 100644 distro_repofiles = _get_distro_repofiles(distro, major_version, arch) if not distro_repofiles: # TODO: a different way of signaling an error would be preferred (e.g. returning None), +@@ -208,3 +227,35 @@ def get_distro_repoids(context, distro, major_version, arch): + distro_repoids.extend([repo.repoid for repo in rfile.data]) + + return sorted(distro_repoids) ++ ++ ++def distro_id_to_pretty_name(distro_id): ++ """ ++ Get pretty name for the given distro id. ++ ++ The pretty name is what is found in the NAME field of /etc/os-release. ++ """ ++ return { ++ "rhel": "Red Hat Enterprise Linux", ++ "centos": "CentOS Stream", ++ "almalinux": "AlmaLinux", ++ }[distro_id] ++ ++ ++def get_distro_efidir_canon_path(distro_id): ++ """ ++ Get canonical path to the distro EFI directory in the EFI mountpoint. ++ ++ NOTE: The path might be incorrect for distros not properly enabled for IPU, ++ when enabling new distros in the codebase, make sure the path is correct. ++ """ ++ if distro_id == "rhel": ++ return os.path.join(efi.EFI_MOUNTPOINT, "EFI", "redhat") ++ ++ if distro_id == "almalinux": ++ return os.path.join(efi.EFI_MOUNTPOINT, "EFI", "almalinux") ++ ++ if distro_id == "centos": ++ return os.path.join(efi.EFI_MOUNTPOINT, "EFI", "centos") ++ ++ return os.path.join(efi.EFI_MOUNTPOINT, "EFI", distro_id) diff --git a/repos/system_upgrade/common/libraries/dnfplugin.py b/repos/system_upgrade/common/libraries/dnfplugin.py -index 7e1fd497..9e2ba376 100644 +index 7e1fd497..a42af5ca 100644 --- a/repos/system_upgrade/common/libraries/dnfplugin.py +++ b/repos/system_upgrade/common/libraries/dnfplugin.py @@ -89,6 +89,7 @@ def build_plugin_data(target_repoids, debug, test, tasks, on_aws): @@ -8852,6 +20637,455 @@ index 7e1fd497..9e2ba376 100644 'modules_to_enable': sorted(['{}:{}'.format(m.name, m.stream) for m in tasks.modules_to_enable]), }, 'dnf_conf': { +@@ -270,26 +271,24 @@ def _transaction(context, stage, target_repoids, tasks, plugin_info, xfs_info, + # allow handling new RHEL 9 syscalls by systemd-nspawn + env = {'SYSTEMD_SECCOMP': '0'} + +- # We need to reset modules twice, once before we check, and the second time before we actually perform +- # the upgrade. Not more often as the modules will be reset already. +- if stage in ('check', 'upgrade') and tasks.modules_to_reset: +- # We shall only reset modules that are not going to be enabled +- # This will make sure it is so +- modules_to_reset = {(module.name, module.stream) for module in tasks.modules_to_reset} +- modules_to_enable = {(module.name, module.stream) for module in tasks.modules_to_enable} +- module_reset_list = [module[0] for module in modules_to_reset - modules_to_enable] +- # Perform module reset +- cmd = ['/usr/bin/dnf', 'module', 'reset', '--enabled', ] + module_reset_list +- cmd += ['--disablerepo', '*', '-y', '--installroot', '/installroot'] +- try: +- context.call( +- cmd=cmd_prefix + cmd + common_params, +- callback_raw=utils.logging_handler, +- env=env +- ) +- except (CalledProcessError, OSError): +- api.current_logger().debug('Failed to reset modules via dnf with an error. Ignoring.', +- exc_info=True) ++ if tasks.modules_to_reset: ++ # We shall only reset modules that are not going to be enabled ++ # This will make sure it is so ++ modules_to_reset = {(module.name, module.stream) for module in tasks.modules_to_reset} ++ modules_to_enable = {(module.name, module.stream) for module in tasks.modules_to_enable} ++ module_reset_list = [module[0] for module in modules_to_reset - modules_to_enable] ++ # Perform module reset ++ cmd = ['/usr/bin/dnf', 'module', 'reset', '--enabled', ] + module_reset_list ++ cmd += ['--disablerepo', '*', '-y', '--installroot', '/installroot'] ++ try: ++ context.call( ++ cmd=cmd_prefix + cmd + common_params, ++ callback_raw=utils.logging_handler, ++ env=env ++ ) ++ except (CalledProcessError, OSError): ++ api.current_logger().debug('Failed to reset modules via dnf with an error. Ignoring.', ++ exc_info=True) + + cmd = [ + '/usr/bin/dnf', +diff --git a/repos/system_upgrade/common/libraries/efi.py b/repos/system_upgrade/common/libraries/efi.py +new file mode 100644 +index 00000000..c30d67e0 +--- /dev/null ++++ b/repos/system_upgrade/common/libraries/efi.py +@@ -0,0 +1,362 @@ ++import os ++import re ++ ++from leapp.libraries.common.partitions import ( ++ _get_partition_for_dir, ++ blk_dev_from_partition, ++ get_partition_number, ++ StorageScanError ++) ++from leapp.libraries.stdlib import api, CalledProcessError, run ++ ++EFI_MOUNTPOINT = '/boot/efi/' ++"""The path to the required mountpoint for ESP.""" ++ ++ ++class EFIError(Exception): ++ """ ++ Exception raised when EFI operation failed. ++ """ ++ ++ ++def canonical_path_to_efi_format(canonical_path): ++ r""" ++ Transform the canonical path to the UEFI format. ++ ++ e.g. /boot/efi/EFI/redhat/shimx64.efi -> \EFI\redhat\shimx64.efi ++ (just single backslash; so the string needs to be put into apostrophes ++ when used for /usr/sbin/efibootmgr cmd) ++ ++ The path has to start with /boot/efi otherwise the path is invalid for UEFI. ++ """ ++ ++ # We want to keep the last "/" of the EFI_MOUNTPOINT ++ return canonical_path.replace(EFI_MOUNTPOINT[:-1], "").replace("/", "\\") ++ ++ ++class EFIBootLoaderEntry: ++ """ ++ Representation of an UEFI boot loader entry. ++ """ ++ ++ def __init__(self, boot_number, label, active, efi_bin_source): ++ self.boot_number = boot_number ++ """Expected string, e.g. '0001'. """ ++ ++ self.label = label ++ """Label of the UEFI entry. E.g. 'Redhat'""" ++ ++ self.active = active ++ """True when the UEFI entry is active (asterisk is present next to the boot number)""" ++ ++ self.efi_bin_source = efi_bin_source ++ """Source of the UEFI binary. ++ ++ It could contain various values, e.g.: ++ FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(462caa21-7614-4503-836e-8ab6f4662331) ++ HD(1,GPT,28c77f6b-3cd0-4b22-985f-c99903835d79,0x800,0x12c000)/File(\\EFI\\redhat\\shimx64.efi) ++ PciRoot(0x0)/Pci(0x2,0x3)/Pci(0x0,0x0)N.....YM....R,Y. ++ """ ++ ++ def __eq__(self, other): ++ return all( ++ [ ++ self.boot_number == other.boot_number, ++ self.label == other.label, ++ self.active == other.active, ++ self.efi_bin_source == other.efi_bin_source, ++ ] ++ ) ++ ++ def __ne__(self, other): ++ return not self.__eq__(other) ++ ++ def __repr__(self): ++ return 'EFIBootLoaderEntry({boot_number}, {label}, {active}, {efi_bin_source})'.format( ++ boot_number=repr(self.boot_number), ++ label=repr(self.label), ++ active=repr(self.active), ++ efi_bin_source=repr(self.efi_bin_source) ++ ) ++ ++ def is_referring_to_file(self): ++ """Return True when the boot source is a file. ++ ++ Some sources could refer e.g. to PXE boot. Return true if the source ++ refers to a file ("ends with /File(...path...)") ++ ++ Does not matter whether the file exists or not. ++ """ ++ return '/File(\\' in self.efi_bin_source ++ ++ @staticmethod ++ def _efi_path_to_canonical(efi_path): ++ return os.path.join(EFI_MOUNTPOINT, efi_path.replace("\\", "/").lstrip("/")) ++ ++ def get_canonical_path(self): ++ """Return expected canonical path for the referred UEFI bin or None. ++ ++ Return None in case the entry is not referring to any UEFI bin ++ (e.g. when it refers to a PXE boot). ++ """ ++ if not self.is_referring_to_file(): ++ return None ++ match = re.search(r'/File\((?P\\.*)\)$', self.efi_bin_source) ++ return EFIBootLoaderEntry._efi_path_to_canonical(match.groups('path')[0]) ++ ++ ++class EFIBootInfo: ++ """ ++ Data about the current UEFI boot configuration. ++ ++ :raises EFIError: when unable to obtain info about the UEFI configuration, ++ BIOS is detected or ESP is not mounted where expected. ++ """ ++ ++ def __init__(self): ++ if not is_efi(): ++ raise EFIError('Unable to collect data about UEFI on a BIOS system.') ++ try: ++ result = run(['/usr/sbin/efibootmgr', '-v']) ++ except CalledProcessError: ++ raise EFIError('Unable to get information about UEFI boot entries.') ++ ++ bootmgr_output = result['stdout'] ++ ++ self.current_bootnum = None ++ """The boot number (str) of the current boot.""" ++ self.next_bootnum = None ++ """The boot number (str) of the next boot.""" ++ self.boot_order = tuple() ++ """The tuple of the UEFI boot loader entries in the boot order.""" ++ self.entries = {} ++ """The UEFI boot loader entries {'boot_number': EFIBootLoaderEntry}""" ++ ++ self._parse_efi_boot_entries(bootmgr_output) ++ self._parse_current_bootnum(bootmgr_output) ++ self._parse_next_bootnum(bootmgr_output) ++ self._parse_boot_order(bootmgr_output) ++ self._print_loaded_info() ++ ++ def _parse_efi_boot_entries(self, bootmgr_output): ++ """ ++ Return dict of UEFI boot loader entries: {"": EFIBootLoader} ++ """ ++ ++ self.entries = {} ++ regexp_entry = re.compile( ++ r"^Boot(?P[a-zA-Z0-9]+)(?P\*?)\s*(?P