diff --git a/service/lib/agama/storage/config_checkers/alias.rb b/service/lib/agama/storage/config_checkers/alias.rb index 5f76eca505..ae78b09ce5 100644 --- a/service/lib/agama/storage/config_checkers/alias.rb +++ b/service/lib/agama/storage/config_checkers/alias.rb @@ -72,7 +72,7 @@ def overused_alias_issue error( format(_("The device with alias '%s' is used by more than one device"), config.alias), - kind: :overused_alias + kind: IssueClasses::Config::ALIAS ) end @@ -98,7 +98,7 @@ def formatted_issue ), config.alias ), - kind: :formatted_with_user + kind: IssueClasses::Config::OVERUSED ) end @@ -122,7 +122,7 @@ def partitioned_issue ), config.alias ), - kind: :partitioned_with_user + kind: IssueClasses::Config::OVERUSED ) end end diff --git a/service/lib/agama/storage/config_checkers/base.rb b/service/lib/agama/storage/config_checkers/base.rb index e49faa7fb3..81a6a9afa9 100644 --- a/service/lib/agama/storage/config_checkers/base.rb +++ b/service/lib/agama/storage/config_checkers/base.rb @@ -20,6 +20,7 @@ # find current contact information at www.suse.com. require "agama/issue" +require "agama/storage/issue_classes" module Agama module Storage diff --git a/service/lib/agama/storage/config_checkers/boot.rb b/service/lib/agama/storage/config_checkers/boot.rb index 4d25a2fe71..98fc16047c 100644 --- a/service/lib/agama/storage/config_checkers/boot.rb +++ b/service/lib/agama/storage/config_checkers/boot.rb @@ -73,7 +73,7 @@ def missing_alias_issue error( _("The boot device cannot be automatically selected"), - kind: :no_root + kind: IssueClasses::Config::NO_ROOT ) end @@ -84,7 +84,7 @@ def invalid_alias_issue # TRANSLATORS: %s is replaced by a device alias (e.g., "boot"). error( format(_("There is no boot device with alias '%s'"), device_alias), - kind: :no_such_alias + kind: IssueClasses::Config::ALIAS ) end diff --git a/service/lib/agama/storage/config_checkers/encryption.rb b/service/lib/agama/storage/config_checkers/encryption.rb index d08e93e733..24b5bcb33e 100644 --- a/service/lib/agama/storage/config_checkers/encryption.rb +++ b/service/lib/agama/storage/config_checkers/encryption.rb @@ -64,7 +64,7 @@ def encryption # @see Base def error(message) - super(message, kind: :encryption) + super(message, kind: IssueClasses::Config::ENCRYPTION) end # @return [Issue, nil] diff --git a/service/lib/agama/storage/config_checkers/filesystem.rb b/service/lib/agama/storage/config_checkers/filesystem.rb index c6a94dab0a..d67d067fff 100644 --- a/service/lib/agama/storage/config_checkers/filesystem.rb +++ b/service/lib/agama/storage/config_checkers/filesystem.rb @@ -67,7 +67,7 @@ def filesystem # @see Base def error(message) - super(message, kind: :filesystem) + super(message, kind: IssueClasses::Config::FILESYSTEM) end # @return [Issue, nil] diff --git a/service/lib/agama/storage/config_checkers/filesystems.rb b/service/lib/agama/storage/config_checkers/filesystems.rb index d2d83ac6fb..e30da53f48 100644 --- a/service/lib/agama/storage/config_checkers/filesystems.rb +++ b/service/lib/agama/storage/config_checkers/filesystems.rb @@ -70,7 +70,7 @@ def missing_paths_issue ), missing_paths.join(", ") ), - kind: :required_filesystems + kind: IssueClasses::Config::REQUIRED_PATHS ) end diff --git a/service/lib/agama/storage/config_checkers/logical_volume.rb b/service/lib/agama/storage/config_checkers/logical_volume.rb index 85c3113b16..2136ff615a 100644 --- a/service/lib/agama/storage/config_checkers/logical_volume.rb +++ b/service/lib/agama/storage/config_checkers/logical_volume.rb @@ -80,7 +80,7 @@ def missing_thin_pool_issue error( # TRANSLATORS: %s is the replaced by a device alias (e.g., "pv1"). format(_("There is no LVM thin pool volume with alias '%s'"), config.used_pool), - kind: :no_such_alias + kind: IssueClasses::Config::ALIAS ) end end diff --git a/service/lib/agama/storage/config_checkers/md_raid.rb b/service/lib/agama/storage/config_checkers/md_raid.rb index f6841958c8..3e2bbcb6d8 100644 --- a/service/lib/agama/storage/config_checkers/md_raid.rb +++ b/service/lib/agama/storage/config_checkers/md_raid.rb @@ -96,7 +96,7 @@ def missing_device_issue(device_alias) error( # TRANSLATORS: %s is the replaced by a device alias (e.g., "md1"). format(_("There is no MD RAID member device with alias '%s'"), device_alias), - kind: :no_such_alias + kind: IssueClasses::Config::ALIAS ) end @@ -107,7 +107,7 @@ def level_issue return if config.level return unless config.create? - error(format(_("There is a MD RAID without level")), kind: :md_raid) + error(format(_("There is a MD RAID without level")), kind: IssueClasses::Config::MD_RAID) end # Issue if the MD RAID does not contain enough member devices. @@ -119,7 +119,7 @@ def devices_size_issue error( format(_("At least %s devices are required for %s"), config.min_devices, config.level), - kind: :md_raid + kind: IssueClasses::Config::MD_RAID ) end @@ -174,7 +174,7 @@ def deleted_reused_member_issue(member_config) member: member_config.found_device.name, md_raid: config.found_device.name ), - kind: :reused_md_member + kind: IssueClasses::Config::OVERUSED_MD_MEMBER ) end @@ -197,7 +197,7 @@ def resized_reused_member_issue(member_config) member: member_config.found_device.name, md_raid: config.found_device.name ), - kind: :reused_md_member + kind: IssueClasses::Config::OVERUSED_MD_MEMBER ) end @@ -220,7 +220,7 @@ def formatted_reused_member_issue(member_config) member: member_config.found_device.name, md_raid: config.found_device.name ), - kind: :reused_md_member + kind: IssueClasses::Config::OVERUSED_MD_MEMBER ) end @@ -243,7 +243,7 @@ def partitioned_reused_member_issue(member_config) member: member_config.found_device.name, md_raid: config.found_device.name ), - kind: :reused_md_member + kind: IssueClasses::Config::OVERUSED_MD_MEMBER ) end @@ -265,7 +265,7 @@ def target_reused_member_issue(member_config) member: member_config.found_device.name, md_raid: config.found_device.name ), - kind: :reused_md_member + kind: IssueClasses::Config::OVERUSED_MD_MEMBER ) end @@ -290,7 +290,7 @@ def parent_reused_member_issue(device) device: parent_config.found_device.name, md_raid: config.found_device.name ), - kind: :reused_md_member + kind: IssueClasses::Config::OVERUSED_MD_MEMBER ) end diff --git a/service/lib/agama/storage/config_checkers/search.rb b/service/lib/agama/storage/config_checkers/search.rb index c4faead5d2..045bc375d4 100644 --- a/service/lib/agama/storage/config_checkers/search.rb +++ b/service/lib/agama/storage/config_checkers/search.rb @@ -62,7 +62,7 @@ def search # @see Base def error(message) - super(message, kind: :search) + super(message, kind: IssueClasses::Config::SEARCH) end # @return [Issue, nil] diff --git a/service/lib/agama/storage/config_checkers/volume_group.rb b/service/lib/agama/storage/config_checkers/volume_group.rb index fe943a8af9..9a0746b921 100644 --- a/service/lib/agama/storage/config_checkers/volume_group.rb +++ b/service/lib/agama/storage/config_checkers/volume_group.rb @@ -73,7 +73,7 @@ def issues def name_issue return if config.name && !config.name.empty? - error(_("There is a volume group without name")) + error(_("There is a volume group without name"), kind: IssueClasses::Config::LVM) end # Issues from logical volumes. @@ -104,7 +104,7 @@ def missing_physical_volume_issue(pv_alias) error( # TRANSLATORS: %s is the replaced by a device alias (e.g., "pv1"). format(_("There is no LVM physical volume with alias '%s'"), pv_alias), - kind: :no_such_alias + kind: IssueClasses::Config::ALIAS ) end @@ -136,7 +136,7 @@ def incompatible_physical_volumes_devices_issue ), config.name ), - kind: :incompatible_pv_targets + kind: IssueClasses::Config::LVM ) end @@ -153,7 +153,7 @@ def missing_physical_volumes_device_issue(device_alias) _("There is no target device for LVM physical volumes with alias '%s'"), device_alias ), - kind: :no_such_alias + kind: IssueClasses::Config::ALIAS ) end diff --git a/service/lib/agama/storage/config_checkers/volume_groups.rb b/service/lib/agama/storage/config_checkers/volume_groups.rb index 61376cf554..aed97e2697 100644 --- a/service/lib/agama/storage/config_checkers/volume_groups.rb +++ b/service/lib/agama/storage/config_checkers/volume_groups.rb @@ -66,7 +66,7 @@ def overused_physical_volumes_devices_issues _("The device '%s' is used several times as target device for physical volumes"), device ), - kind: :vg_target_devices + kind: IssueClasses::Config::OVERUSED_PV_TARGET ) end end diff --git a/service/lib/agama/storage/issue_classes.rb b/service/lib/agama/storage/issue_classes.rb new file mode 100644 index 0000000000..efc2babbb9 --- /dev/null +++ b/service/lib/agama/storage/issue_classes.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# Copyright (c) [2025] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module Agama + module Storage + # Module to declare all the known issue classes from the storage scope + module IssueClasses + # Generic issue found when calculating the proposal + PROPOSAL = :proposal + + # Issue classes related to the configuration provided by the user + module Config + # Generic issue with encryption settings + ENCRYPTION = :configEncryption + + # Generic issue with filesystem settings + FILESYSTEM = :configFilesystem + + # Generic issue defining LVM (eg. no volume group name) + LVM = :configLvm + + # Generic issue defining Md RAIDs (eg. no level) + MD_RAID = :configMdRaid + + # No root filesystem was defined + NO_ROOT = :configNoRoot + + # Issue with aliases (eg. same alias defined twice or referencing a non-existent alias) + ALIAS = :configAlias + + # A mandatory separate filesystem is missing in the configuration + REQUIRED_PATHS = :configRequiredPaths + + # The device specified in a 'search' was not found + SEARCH = :configSearch + + # Generic issue when a single device is used for several incompatible purposes + # (eg. to be formatted and also to be an LVM physical volume) + OVERUSED = :configOverused + + # A device is used by several volume groups as a target for generating PVs + OVERUSED_PV_TARGET = :configOverusedPvTarget + + # A device is part of a RAID and also chosen for an incompatible purpose + OVERUSED_MD_MEMBER = :configOverusedMdMember + end + end + end +end diff --git a/service/lib/agama/storage/proposal.rb b/service/lib/agama/storage/proposal.rb index c052dd577b..bf159ba67c 100644 --- a/service/lib/agama/storage/proposal.rb +++ b/service/lib/agama/storage/proposal.rb @@ -27,6 +27,7 @@ require "agama/storage/config_solver" require "agama/storage/model_support_checker" require "agama/storage/proposal_strategies" +require "agama/storage/issue_classes" require "agama/storage/system" require "json" require "yast" @@ -302,7 +303,7 @@ def storage_manager def failed_issue Issue.new( _("Cannot calculate a valid storage setup with the current configuration"), - kind: :proposal + kind: IssueClasses::PROPOSAL ) end @@ -312,7 +313,7 @@ def failed_issue def exception_issue(error) Issue.new( _("A problem ocurred while calculating the storage setup"), - kind: :proposal, + kind: IssueClasses::PROPOSAL, details: error.message ) end diff --git a/service/test/agama/storage/config_checker_test.rb b/service/test/agama/storage/config_checker_test.rb index 14fd203055..7720e42694 100644 --- a/service/test/agama/storage/config_checker_test.rb +++ b/service/test/agama/storage/config_checker_test.rb @@ -53,7 +53,7 @@ it "includes the boot issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_root, + kind: Agama::Storage::IssueClasses::Config::NO_ROOT, description: "The boot device cannot be automatically selected" ) end @@ -74,7 +74,7 @@ it "includes the drive issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :search, + kind: Agama::Storage::IssueClasses::Config::SEARCH, description: "Mandatory device /dev/vda not found" ) end @@ -94,7 +94,7 @@ it "includes the partition issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :filesystem, + kind: Agama::Storage::IssueClasses::Config::FILESYSTEM, description: "Missing file system type for '/'" ) end @@ -110,7 +110,7 @@ it "includes the MD RAID issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_such_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /no MD RAID member device with alias 'disk1'/ ) end @@ -130,7 +130,7 @@ it "includes the partition issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :filesystem, + kind: Agama::Storage::IssueClasses::Config::FILESYSTEM, description: "Missing file system type for '/'" ) end @@ -165,7 +165,7 @@ it "includes the logical volume issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :filesystem, + kind: Agama::Storage::IssueClasses::Config::FILESYSTEM, description: "Missing file system type for '/'" ) end @@ -203,7 +203,7 @@ it "includes an issue for the missing mount path" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :required_filesystems, + kind: Agama::Storage::IssueClasses::Config::REQUIRED_PATHS, description: /file system for \/ is/ ) end @@ -211,7 +211,7 @@ it "does not include an issue for the present mount path" do issues = subject.issues expect(issues).to_not include an_object_having_attributes( - kind: :required_filesystems, + kind: Agama::Storage::IssueClasses::Config::REQUIRED_PATHS, description: /file system for swap/ ) end @@ -265,7 +265,7 @@ it "includes the expected issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :vg_target_devices, + kind: Agama::Storage::IssueClasses::Config::OVERUSED_PV_TARGET, description: /The device 'disk1' is used several times/ ) end diff --git a/service/test/agama/storage/config_checkers/alias_test.rb b/service/test/agama/storage/config_checkers/alias_test.rb index 9d93cc0d42..e3aaf226e1 100644 --- a/service/test/agama/storage/config_checkers/alias_test.rb +++ b/service/test/agama/storage/config_checkers/alias_test.rb @@ -26,7 +26,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :overused_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /alias '#{device_alias}' is used by more than one/ ) end @@ -145,7 +145,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :formatted_with_user, + kind: Agama::Storage::IssueClasses::Config::OVERUSED, description: /alias '#{device_alias}' cannot be formatted because it is used/ ) end @@ -220,7 +220,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :partitioned_with_user, + kind: Agama::Storage::IssueClasses::Config::OVERUSED, description: /alias '#{device_alias}' cannot be partitioned because it is used/ ) end diff --git a/service/test/agama/storage/config_checkers/boot_test.rb b/service/test/agama/storage/config_checkers/boot_test.rb index 673e4eef57..9d6d717f56 100644 --- a/service/test/agama/storage/config_checkers/boot_test.rb +++ b/service/test/agama/storage/config_checkers/boot_test.rb @@ -51,7 +51,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_such_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /There is no boot device with alias '.*'/ ) end @@ -67,7 +67,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_root, + kind: Agama::Storage::IssueClasses::Config::NO_ROOT, description: /The boot device cannot be automatically selected/ ) end diff --git a/service/test/agama/storage/config_checkers/encryption_test.rb b/service/test/agama/storage/config_checkers/encryption_test.rb index 7beb4151d2..bba8207844 100644 --- a/service/test/agama/storage/config_checkers/encryption_test.rb +++ b/service/test/agama/storage/config_checkers/encryption_test.rb @@ -54,7 +54,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /No passphrase/ ) end @@ -78,7 +78,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /'Pervasive Volume Encryption' is not available/ ) end @@ -102,7 +102,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /'TPM-Based Full Disk Encrytion' is not available/ ) end @@ -121,7 +121,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /'Encryption with Volatile Protected Key' is not a suitable/ ) end diff --git a/service/test/agama/storage/config_checkers/examples.rb b/service/test/agama/storage/config_checkers/examples.rb index 4e4cd96356..ac0d6798f4 100644 --- a/service/test/agama/storage/config_checkers/examples.rb +++ b/service/test/agama/storage/config_checkers/examples.rb @@ -31,7 +31,7 @@ it "includes the search issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :search, + kind: Agama::Storage::IssueClasses::Config::SEARCH, description: "Mandatory device /test not found" ) end @@ -47,7 +47,7 @@ it "includes the filesystem issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :filesystem, + kind: Agama::Storage::IssueClasses::Config::FILESYSTEM, description: "Missing file system type for '/'" ) end @@ -63,7 +63,7 @@ it "includes the encryption issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /No passphrase .*/ ) end @@ -85,7 +85,7 @@ it "includes the partition issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :search, + kind: Agama::Storage::IssueClasses::Config::SEARCH, description: "Mandatory partition not found" ) end @@ -106,7 +106,7 @@ it "includes the alias issues" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :overused_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /alias '#{device_alias}' is used by more than one/ ) end diff --git a/service/test/agama/storage/config_checkers/filesystem_test.rb b/service/test/agama/storage/config_checkers/filesystem_test.rb index e510cca31a..caa1f800df 100644 --- a/service/test/agama/storage/config_checkers/filesystem_test.rb +++ b/service/test/agama/storage/config_checkers/filesystem_test.rb @@ -53,7 +53,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :filesystem, + kind: Agama::Storage::IssueClasses::Config::FILESYSTEM, description: /type 'FAT' is not suitable for '\/'/ ) end @@ -95,7 +95,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :filesystem, + kind: Agama::Storage::IssueClasses::Config::FILESYSTEM, description: /Missing file system type for '\/'/ ) end diff --git a/service/test/agama/storage/config_checkers/logical_volume_test.rb b/service/test/agama/storage/config_checkers/logical_volume_test.rb index 72aa09ec1c..5f0a64e445 100644 --- a/service/test/agama/storage/config_checkers/logical_volume_test.rb +++ b/service/test/agama/storage/config_checkers/logical_volume_test.rb @@ -64,7 +64,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_such_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /no LVM thin pool/ ) end @@ -76,7 +76,7 @@ it "does not include an issue" do issues = subject.issues expect(issues).to_not include an_object_having_attributes( - kind: :no_such_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /no LVM thin pool/ ) end diff --git a/service/test/agama/storage/config_checkers/md_raid_test.rb b/service/test/agama/storage/config_checkers/md_raid_test.rb index 45b36b5d4b..8e90a5634a 100644 --- a/service/test/agama/storage/config_checkers/md_raid_test.rb +++ b/service/test/agama/storage/config_checkers/md_raid_test.rb @@ -76,7 +76,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :md_raid, + kind: Agama::Storage::IssueClasses::Config::MD_RAID, description: /MD RAID without level/ ) end @@ -92,7 +92,9 @@ it "does not include the issue" do issues = subject.issues - expect(issues).to_not include an_object_having_attributes(kind: :md_raid) + expect(issues).to_not include an_object_having_attributes( + kind: Agama::Storage::IssueClasses::Config::MD_RAID + ) end end end @@ -106,7 +108,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :md_raid, + kind: Agama::Storage::IssueClasses::Config::MD_RAID, description: "At least 2 devices are required for raid0" ) end @@ -118,7 +120,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_such_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /no MD RAID member device with alias 'disk2'/ ) end @@ -151,7 +153,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :reused_md_member, + kind: Agama::Storage::IssueClasses::Config::OVERUSED_MD_MEMBER, description: /.*vda.*cannot be formatted.*part of.*md0/ ) end @@ -169,7 +171,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :reused_md_member, + kind: Agama::Storage::IssueClasses::Config::OVERUSED_MD_MEMBER, description: /.*vda.*cannot be partitioned.*part of.*md0/ ) end @@ -187,7 +189,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :reused_md_member, + kind: Agama::Storage::IssueClasses::Config::OVERUSED_MD_MEMBER, description: /.*vda.*cannot be used.*part of.*md0/ ) end @@ -213,7 +215,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :reused_md_member, + kind: Agama::Storage::IssueClasses::Config::OVERUSED_MD_MEMBER, description: /.*vda1.*cannot be deleted.*part of.*md0/ ) end @@ -239,7 +241,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :reused_md_member, + kind: Agama::Storage::IssueClasses::Config::OVERUSED_MD_MEMBER, description: /.*vda1.*cannot be resized.*part of.*md0/ ) end @@ -261,7 +263,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :reused_md_member, + kind: Agama::Storage::IssueClasses::Config::OVERUSED_MD_MEMBER, description: /.*vda.*cannot be formatted.*part of.*md0/ ) end diff --git a/service/test/agama/storage/config_checkers/search_test.rb b/service/test/agama/storage/config_checkers/search_test.rb index 35ba40c7fe..a3303c2d86 100644 --- a/service/test/agama/storage/config_checkers/search_test.rb +++ b/service/test/agama/storage/config_checkers/search_test.rb @@ -52,7 +52,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :search, + kind: Agama::Storage::IssueClasses::Config::SEARCH, description: "Mandatory drive not found" ) end diff --git a/service/test/agama/storage/config_checkers/volume_group_test.rb b/service/test/agama/storage/config_checkers/volume_group_test.rb index 212c5292a8..c11b4146f0 100644 --- a/service/test/agama/storage/config_checkers/volume_group_test.rb +++ b/service/test/agama/storage/config_checkers/volume_group_test.rb @@ -64,7 +64,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_such_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /no LVM physical volume with alias 'pv1'/ ) end @@ -84,7 +84,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :no_such_alias, + kind: Agama::Storage::IssueClasses::Config::ALIAS, description: /no target device for LVM physical volumes with alias 'second-disk'/ ) end @@ -110,7 +110,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /No passphrase/ ) end @@ -134,7 +134,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /'Regular LUKS2' is not available/ ) end @@ -146,7 +146,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :encryption, + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION, description: /'Encryption with Volatile Random Key' is not a suitable method/ ) end @@ -163,7 +163,9 @@ it "does not include an encryption issue" do issues = subject.issues - expect(issues).to_not include an_object_having_attributes(kind: :encryption) + expect(issues).to_not include an_object_having_attributes( + kind: Agama::Storage::IssueClasses::Config::ENCRYPTION + ) end end end @@ -219,7 +221,7 @@ it "includes the expected issue" do issues = subject.issues expect(issues).to include an_object_having_attributes( - kind: :incompatible_pv_targets, + kind: Agama::Storage::IssueClasses::Config::LVM, description: /'system' is mixing reused devices and new devices/ ) end @@ -230,7 +232,9 @@ it "does not include an incompatible targets issue" do issues = subject.issues - expect(issues).to_not include an_object_having_attributes(kind: :incompatible_pv_targets) + expect(issues).to_not include an_object_having_attributes( + kind: Agama::Storage::IssueClasses::Config::LVM + ) end end @@ -239,7 +243,9 @@ it "does not include an incompatible targets issue" do issues = subject.issues - expect(issues).to_not include an_object_having_attributes(kind: :incompatible_pv_targets) + expect(issues).to_not include an_object_having_attributes( + kind: Agama::Storage::IssueClasses::Config::LVM + ) end end end diff --git a/web/src/api/issue.ts b/web/src/api/issue.ts index a504c79ce2..70c9378999 100644 --- a/web/src/api/issue.ts +++ b/web/src/api/issue.ts @@ -25,7 +25,7 @@ type Scope = "localization" | "product" | "software" | "storage" | "users" | "is type Issue = { scope: Scope; description: string; - kind: string; + class: string; details?: string; }; diff --git a/web/src/components/core/IssuesAlert.tsx b/web/src/components/core/IssuesAlert.tsx index ff46d6e4de..d1706beb06 100644 --- a/web/src/components/core/IssuesAlert.tsx +++ b/web/src/components/core/IssuesAlert.tsx @@ -39,7 +39,7 @@ export default function IssuesAlert({ issues }) { {issues.map((i: Issue, idx: number) => ( {i.description}{" "} - {i.kind === "solver" && ( + {i.class === "solver" && ( { // TRANSLATORS: Clickable link to show and resolve package dependency conflicts diff --git a/web/src/components/overview/StorageSection.tsx b/web/src/components/overview/StorageSection.tsx index ffb168ca33..1baa9daf6c 100644 --- a/web/src/components/overview/StorageSection.tsx +++ b/web/src/components/overview/StorageSection.tsx @@ -91,7 +91,7 @@ const ModelSummary = ({ model }: { model: model.Config }): React.ReactNode => { const NoModelSummary = (): React.ReactNode => { const availableDevices = useAvailableDevices(); - const systemErrors = useIssues(); + const systemErrors = useIssues("storage"); const hasDisks = !!availableDevices.length; const hasResult = !systemErrors.length; diff --git a/web/src/components/product/ProductRegistrationAlert.tsx b/web/src/components/product/ProductRegistrationAlert.tsx index 4d9ced1b23..9a40b7a95f 100644 --- a/web/src/components/product/ProductRegistrationAlert.tsx +++ b/web/src/components/product/ProductRegistrationAlert.tsx @@ -47,7 +47,7 @@ export default function ProductRegistrationAlert() { const product = useProduct(); // FIXME: what scope reports these issues with the new API? const issues = useIssues("product"); - const registrationRequired = issues?.find((i) => i.kind === "missing_registration"); + const registrationRequired = issues?.find((i) => i.class === "missing_registration"); // NOTE: it shouldn't be mounted in these paths, but let's prevent rendering // if so just in case. diff --git a/web/src/components/storage/FixableConfigInfo.tsx b/web/src/components/storage/FixableConfigInfo.tsx index ac70712533..df62fc0eef 100644 --- a/web/src/components/storage/FixableConfigInfo.tsx +++ b/web/src/components/storage/FixableConfigInfo.tsx @@ -23,7 +23,7 @@ import React from "react"; import { Alert, List, ListItem } from "@patternfly/react-core"; import { n_ } from "~/i18n"; -import { useIssues } from "~/hooks/api/issue"; +import { useConfigIssues } from "~/hooks/storage/issue"; const Description = ({ errors }) => { return ( @@ -40,7 +40,7 @@ const Description = ({ errors }) => { * */ export default function FixableConfigInfo() { - const configErrors = useIssues("storage"); + const configErrors = useConfigIssues(); if (!configErrors.length) return; diff --git a/web/src/components/storage/ProposalFailedInfo.tsx b/web/src/components/storage/ProposalFailedInfo.tsx index ca4d4acc9c..d1c7120f02 100644 --- a/web/src/components/storage/ProposalFailedInfo.tsx +++ b/web/src/components/storage/ProposalFailedInfo.tsx @@ -24,6 +24,7 @@ import React from "react"; import { Alert, Content } from "@patternfly/react-core"; import { useStorageModel } from "~/hooks/api/storage"; import { useIssues } from "~/hooks/api/issue"; +import { useConfigIssues } from "~/hooks/storage/issue"; import * as partitionUtils from "~/components/storage/utils/partition"; import { _, formatList } from "~/i18n"; import { sprintf } from "sprintf-js"; @@ -87,8 +88,11 @@ const Description = () => { * - The generated proposal contains no errors. */ export default function ProposalFailedInfo() { - const issues = useIssues("storage"); - if (issues.length === 0) return; + const configErrors = useConfigIssues(); + const errors = useIssues("storage"); + + if (configErrors.length !== 0) return; + if (errors.length === 0) return; return ( diff --git a/web/src/components/storage/ProposalPage.tsx b/web/src/components/storage/ProposalPage.tsx index b481a33986..e3817d5bf7 100644 --- a/web/src/components/storage/ProposalPage.tsx +++ b/web/src/components/storage/ProposalPage.tsx @@ -53,7 +53,7 @@ import ProposalResultSection from "./ProposalResultSection"; import ProposalTransactionalInfo from "./ProposalTransactionalInfo"; import UnsupportedModelInfo from "./UnsupportedModelInfo"; import { useAvailableDevices } from "~/hooks/api/system/storage"; -import { useIssues } from "~/hooks/api/issue"; +import { useConfigIssues } from "~/hooks/storage/issue"; import { useReset } from "~/hooks/api/config/storage"; import { useProposal } from "~/hooks/api/proposal/storage"; import { useStorageModel } from "~/hooks/api/storage"; @@ -68,7 +68,7 @@ import MenuButton from "../core/MenuButton"; import spacingStyles from "@patternfly/react-styles/css/utilities/Spacing/spacing"; function InvalidConfigEmptyState(): React.ReactNode { - const errors = useIssues("storage"); + const errors = useConfigIssues(); const reset = useReset(); return ( @@ -303,7 +303,7 @@ export default function ProposalPage(): React.ReactNode { const model = useStorageModel(); const availableDevices = useAvailableDevices(); const proposal = useProposal(); - const issues = useIssues("storage"); + const configErrors = useConfigIssues(); const progress = useProgress("storage"); const navigate = useNavigate(); const location = useLocation(); @@ -324,11 +324,13 @@ export default function ProposalPage(): React.ReactNode { } }, [resetNeeded, setUiState]); - const fixable = ["no_root", "required_filesystems", "vg_target_devices", "reused_md_member"]; - const unfixableErrors = issues.filter((e) => !fixable.includes(e.kind)); + const fixable = [ + "configNoRoot", "configRequiredPaths", "configOverusedPvTarget", "configOverusedMdMember" + ]; + const unfixableErrors = configErrors.filter((e) => !fixable.includes(e.class)); const isModelEditable = model && !unfixableErrors.length; const hasDevices = !!availableDevices.length; - const hasResult = proposal !== null; + const hasResult = !!proposal; const showSections = hasDevices && (isModelEditable || hasResult); if (resetNeeded) return; diff --git a/web/src/hooks/api/issue.ts b/web/src/hooks/api/issue.ts index 05715cc9f0..2e01a45e23 100644 --- a/web/src/hooks/api/issue.ts +++ b/web/src/hooks/api/issue.ts @@ -57,4 +57,4 @@ const useIssuesChanges = () => { }, [client, queryClient]); }; -export { useIssues, useIssuesChanges }; +export { issuesQuery, useIssues, useIssuesChanges }; diff --git a/web/src/hooks/storage/issue.ts b/web/src/hooks/storage/issue.ts new file mode 100644 index 0000000000..eb5664be8f --- /dev/null +++ b/web/src/hooks/storage/issue.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) [2025] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +import React from "react"; +import { useSuspenseQuery } from "@tanstack/react-query"; +import { issuesQuery } from "~/hooks/api/issue"; +import { Issue } from "~/api/issue"; + +function selectIssues(issues: Issue[]) { + return issues.filter((i: Issue) => i.scope === "storage" && i.class !== "proposal"); +} + +function useConfigIssues(): Issue[] { + const { data } = useSuspenseQuery({ + ...issuesQuery, + select: selectIssues + }); + return data; +} + +export { useConfigIssues };