diff --git a/rust/agama-lib/share/storage.model.schema.json b/rust/agama-lib/share/storage.model.schema.json index 6a6bfb6b8f..e1fcf25fda 100644 --- a/rust/agama-lib/share/storage.model.schema.json +++ b/rust/agama-lib/share/storage.model.schema.json @@ -100,7 +100,8 @@ "reuse": { "type": "boolean" }, "default": { "type": "boolean" }, "type": { "$ref": "#/$defs/filesystemType" }, - "snapshots": { "type": "boolean" } + "snapshots": { "type": "boolean" }, + "label": { "type": "string" } } }, "filesystemType": { diff --git a/rust/package/agama.changes b/rust/package/agama.changes index 9bb80876ec..49af30d0ca 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed Feb 26 06:52:52 UTC 2025 - José Iván López González + +- Extend storage model schema to support file system label (needed + for jsc#AGM-122 and bsc#1237165). + ------------------------------------------------------------------- Wed Feb 26 06:51:37 UTC 2025 - Imobach Gonzalez Sosa diff --git a/service/lib/agama/storage/config_conversions/from_model_conversions/filesystem.rb b/service/lib/agama/storage/config_conversions/from_model_conversions/filesystem.rb index 1f7f44636a..d20c533485 100644 --- a/service/lib/agama/storage/config_conversions/from_model_conversions/filesystem.rb +++ b/service/lib/agama/storage/config_conversions/from_model_conversions/filesystem.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright (c) [2024] SUSE LLC +# Copyright (c) [2024-2025] SUSE LLC # # All Rights Reserved. # @@ -43,7 +43,8 @@ def conversions { reuse: model_json.dig(:filesystem, :reuse), path: model_json[:mountPath], - type: convert_type + type: convert_type, + label: model_json.dig(:filesystem, :label) } end diff --git a/service/lib/agama/storage/config_conversions/to_model_conversions/filesystem.rb b/service/lib/agama/storage/config_conversions/to_model_conversions/filesystem.rb index f0d0c6cab4..2652d885b1 100644 --- a/service/lib/agama/storage/config_conversions/to_model_conversions/filesystem.rb +++ b/service/lib/agama/storage/config_conversions/to_model_conversions/filesystem.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright (c) [2024] SUSE LLC +# Copyright (c) [2024-2025] SUSE LLC # # All Rights Reserved. # @@ -41,7 +41,8 @@ def conversions reuse: config.reuse?, default: convert_default, type: convert_type, - snapshots: convert_snapshots + snapshots: convert_snapshots, + label: config.label } end diff --git a/service/package/rubygem-agama-yast.changes b/service/package/rubygem-agama-yast.changes index 11664bf358..da32a598b3 100644 --- a/service/package/rubygem-agama-yast.changes +++ b/service/package/rubygem-agama-yast.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed Feb 26 06:52:45 UTC 2025 - José Iván López González + +- Add file system label to the config model (needed for jsc#AGM-122 + and bsc#1237165). + ------------------------------------------------------------------- Wed Feb 26 06:51:36 UTC 2025 - Imobach Gonzalez Sosa diff --git a/service/test/agama/storage/config_conversions/from_model_test.rb b/service/test/agama/storage/config_conversions/from_model_test.rb index d6a0cfe58d..2810bb72f7 100644 --- a/service/test/agama/storage/config_conversions/from_model_test.rb +++ b/service/test/agama/storage/config_conversions/from_model_test.rb @@ -172,14 +172,23 @@ end shared_examples "with filesystem" do |config_proc| + let(:filesystem) do + { + reuse: reuse, + default: default, + type: type, + snapshots: true, + label: label + } + end + + let(:reuse) { false } + let(:default) { false } + let(:type) { nil } + let(:label) { "test" } + context "if the filesystem is default" do - let(:filesystem) do - { - default: true, - type: type, - snapshots: true - } - end + let(:default) { true } context "and the type is 'btrfs'" do let(:type) { "btrfs" } @@ -193,7 +202,7 @@ expect(filesystem.type.fs_type).to eq(Y2Storage::Filesystems::Type::BTRFS) expect(filesystem.type.btrfs).to be_a(Agama::Storage::Configs::Btrfs) expect(filesystem.type.btrfs.snapshots?).to eq(true) - expect(filesystem.label).to be_nil + expect(filesystem.label).to eq("test") expect(filesystem.path).to be_nil expect(filesystem.mount_by).to be_nil expect(filesystem.mkfs_options).to be_empty @@ -212,7 +221,7 @@ expect(filesystem.type.default?).to eq(true) expect(filesystem.type.fs_type).to eq(Y2Storage::Filesystems::Type::XFS) expect(filesystem.type.btrfs).to be_nil - expect(filesystem.label).to be_nil + expect(filesystem.label).to eq("test") expect(filesystem.path).to be_nil expect(filesystem.mount_by).to be_nil expect(filesystem.mkfs_options).to be_empty @@ -222,13 +231,7 @@ end context "if the filesystem is not default" do - let(:filesystem) do - { - default: false, - type: type, - snapshots: true - } - end + let(:default) { false } context "and the type is 'btrfs'" do let(:type) { "btrfs" } @@ -242,7 +245,7 @@ expect(filesystem.type.fs_type).to eq(Y2Storage::Filesystems::Type::BTRFS) expect(filesystem.type.btrfs).to be_a(Agama::Storage::Configs::Btrfs) expect(filesystem.type.btrfs.snapshots?).to eq(true) - expect(filesystem.label).to be_nil + expect(filesystem.label).to eq("test") expect(filesystem.path).to be_nil expect(filesystem.mount_by).to be_nil expect(filesystem.mkfs_options).to be_empty @@ -261,7 +264,7 @@ expect(filesystem.type.default?).to eq(false) expect(filesystem.type.fs_type).to eq(Y2Storage::Filesystems::Type::XFS) expect(filesystem.type.btrfs).to be_nil - expect(filesystem.label).to be_nil + expect(filesystem.label).to eq("test") expect(filesystem.path).to be_nil expect(filesystem.mount_by).to be_nil expect(filesystem.mkfs_options).to be_empty @@ -270,8 +273,27 @@ end end + context "if the filesystem specifies 'reuse'" do + let(:reuse) { true } + + it "sets #filesystem to the expected value" do + config = config_proc.call(subject.convert) + filesystem = config.filesystem + expect(filesystem).to be_a(Agama::Storage::Configs::Filesystem) + expect(filesystem.reuse?).to eq(true) + expect(filesystem.type.default?).to eq(false) + expect(filesystem.type.fs_type).to be_nil + expect(filesystem.type.btrfs).to be_nil + expect(filesystem.label).to eq("test") + expect(filesystem.path).to be_nil + expect(filesystem.mount_by).to be_nil + expect(filesystem.mkfs_options).to be_empty + expect(filesystem.mount_options).to be_empty + end + end + context "if the filesystem does not specify 'type'" do - let(:filesystem) { { default: false } } + let(:type) { nil } it "sets #filesystem to the expected value" do config = config_proc.call(subject.convert) @@ -281,7 +303,7 @@ expect(filesystem.type.default?).to eq(false) expect(filesystem.type.fs_type).to be_nil expect(filesystem.type.btrfs).to be_nil - expect(filesystem.label).to be_nil + expect(filesystem.label).to eq("test") expect(filesystem.path).to be_nil expect(filesystem.mount_by).to be_nil expect(filesystem.mkfs_options).to eq([]) @@ -289,22 +311,22 @@ end end - context "if the filesystem specifies 'reuse'" do - let(:filesystem) { { reuse: true } } + context "if the filesystem does not specify 'label'" do + let(:label) { nil } it "sets #filesystem to the expected value" do config = config_proc.call(subject.convert) filesystem = config.filesystem expect(filesystem).to be_a(Agama::Storage::Configs::Filesystem) - expect(filesystem.reuse?).to eq(true) - expect(filesystem.type.default?).to eq(true) + expect(filesystem.reuse?).to eq(false) + expect(filesystem.type.default?).to eq(false) expect(filesystem.type.fs_type).to be_nil expect(filesystem.type.btrfs).to be_nil expect(filesystem.label).to be_nil expect(filesystem.path).to be_nil expect(filesystem.mount_by).to be_nil - expect(filesystem.mkfs_options).to be_empty - expect(filesystem.mount_options).to be_empty + expect(filesystem.mkfs_options).to eq([]) + expect(filesystem.mount_options).to eq([]) end end end @@ -316,7 +338,8 @@ { default: false, type: "btrfs", - snapshots: true + snapshots: true, + label: "test" } end @@ -329,7 +352,7 @@ expect(filesystem.type.fs_type).to eq(Y2Storage::Filesystems::Type::BTRFS) expect(filesystem.type.btrfs).to be_a(Agama::Storage::Configs::Btrfs) expect(filesystem.type.btrfs.snapshots?).to eq(true) - expect(filesystem.label).to be_nil + expect(filesystem.label).to eq("test") expect(filesystem.path).to eq("/test") expect(filesystem.mount_by).to be_nil expect(filesystem.mkfs_options).to be_empty diff --git a/service/test/agama/storage/config_conversions/to_model_test.rb b/service/test/agama/storage/config_conversions/to_model_test.rb index adf2b45e9a..927f60dbc0 100644 --- a/service/test/agama/storage/config_conversions/to_model_test.rb +++ b/service/test/agama/storage/config_conversions/to_model_test.rb @@ -103,7 +103,8 @@ { reuse: true, default: false, - type: "xfs" + type: "xfs", + label: "test" } ) end diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 3458ad9460..9407928312 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Feb 27 11:21:45 UTC 2025 - José Iván López González + +- Allow setting the file system label (related to jsc#AGM-122 + and bsc#1237165). + ------------------------------------------------------------------- Thu Feb 27 10:21:45 UTC 2025 - José Iván López González diff --git a/web/src/api/storage/types/config-model.ts b/web/src/api/storage/types/config-model.ts index fff51257ca..8a109fb952 100644 --- a/web/src/api/storage/types/config-model.ts +++ b/web/src/api/storage/types/config-model.ts @@ -64,6 +64,7 @@ export interface Filesystem { default: boolean; type?: FilesystemType; snapshots?: boolean; + label?: string; } export interface Partition { name?: string; diff --git a/web/src/components/storage/PartitionPage.test.tsx b/web/src/components/storage/PartitionPage.test.tsx index ef8557f739..a9a940f9b4 100644 --- a/web/src/components/storage/PartitionPage.test.tsx +++ b/web/src/components/storage/PartitionPage.test.tsx @@ -158,13 +158,16 @@ describe("PartitionPage", () => { const size = screen.getByRole("button", { name: "Size" }); // File system and size fields disabled until valid mount point selected expect(filesystem).toBeDisabled(); + expect(screen.queryByRole("textbox", { name: "File system label" })).not.toBeInTheDocument(); expect(size).toBeDisabled(); + await user.click(mountPoint); const mountPointOptions = screen.getByRole("listbox", { name: "Suggested mount points" }); const homeMountPoint = within(mountPointOptions).getByRole("option", { name: "/home" }); await user.click(homeMountPoint); // Valid mount point selected, enable file system and size fields expect(filesystem).toBeEnabled(); + expect(screen.queryByRole("textbox", { name: "File system label" })).toBeInTheDocument(); expect(size).toBeEnabled(); // Display mount point options await user.click(mountPointMode); @@ -206,6 +209,7 @@ describe("PartitionPage", () => { await user.click(homeMountPoint); expect(mountPoint).toHaveValue("/home"); expect(filesystem).toBeEnabled(); + expect(screen.queryByRole("textbox", { name: "File system label" })).toBeInTheDocument(); expect(size).toBeEnabled(); const clearMountPointButton = screen.getByRole("button", { name: "Clear selected mount point", @@ -214,6 +218,7 @@ describe("PartitionPage", () => { expect(mountPoint).toHaveValue(""); // File system and size fields disabled until valid mount point selected expect(filesystem).toBeDisabled(); + expect(screen.queryByRole("textbox", { name: "File system label" })).not.toBeInTheDocument(); expect(size).toBeDisabled(); }); @@ -227,7 +232,11 @@ describe("PartitionPage", () => { min: gib(5), max: gib(15), }, - filesystem: { default: false, type: "xfs" }, + filesystem: { + default: false, + type: "xfs", + label: "HOME", + }, }); }); @@ -239,6 +248,8 @@ describe("PartitionPage", () => { within(targetButton).getByText(/As a new partition/); const filesystemButton = screen.getByRole("button", { name: "File system" }); within(filesystemButton).getByText("XFS"); + const label = screen.getByRole("textbox", { name: "File system label" }); + expect(label).toHaveValue("HOME"); const sizeOptionButton = screen.getByRole("button", { name: "Size" }); within(sizeOptionButton).getByText("Custom"); const minSizeInput = screen.getByRole("textbox", { name: "Minimum size value" }); diff --git a/web/src/components/storage/PartitionPage.tsx b/web/src/components/storage/PartitionPage.tsx index f61018be93..135bfe2705 100644 --- a/web/src/components/storage/PartitionPage.tsx +++ b/web/src/components/storage/PartitionPage.tsx @@ -80,6 +80,7 @@ type FormValue = { mountPoint: string; target: string; filesystem: string; + filesystemLabel: string; sizeOption: SizeOptionValue; minSize: string; maxSize: string; @@ -131,6 +132,7 @@ function toPartitionConfig(value: FormValue): configModel.Partition { default: false, type, snapshots: value.filesystem === BTRFS_SNAPSHOTS, + label: value.filesystemLabel, }; }; @@ -167,6 +169,8 @@ function toFormValue(partitionConfig: configModel.Partition): FormValue { return fsConfig.type; }; + const filesystemLabel = (): string => partitionConfig.filesystem?.label || NO_VALUE; + const sizeOption = (): SizeOptionValue => { const reusePartition = partitionConfig.name !== undefined; const sizeConfig = partitionConfig.size; @@ -182,6 +186,7 @@ function toFormValue(partitionConfig: configModel.Partition): FormValue { mountPoint: mountPoint(), target: target(), filesystem: filesystem(), + filesystemLabel: filesystemLabel(), sizeOption: sizeOption(), minSize: size(partitionConfig.size?.min), maxSize: size(partitionConfig.size?.max), @@ -385,6 +390,7 @@ function useSolvedModel(value: FormValue): configModel.Config | null { const initialPartitionConfig = useInitialPartitionConfig(); const partitionConfig = toPartitionConfig(value); partitionConfig.size = undefined; + if (partitionConfig.filesystem) partitionConfig.filesystem.label = undefined; let sparseModel: configModel.Config | undefined; @@ -552,7 +558,8 @@ function FilesystemOptionLabel({ value, target }: FilesystemOptionLabelProps): R const filesystem = partition?.filesystem?.type; if (value === NO_VALUE) return _("Waiting for a mount point"); // TRANSLATORS: %s is a filesystem type, like Btrfs - if (value === REUSE_FILESYSTEM) return sprintf(_("Current %s"), filesystem); + if (value === REUSE_FILESYSTEM && filesystem) + return sprintf(_("Current %s"), filesystemLabel(filesystem)); if (value === BTRFS_SNAPSHOTS) return _("Btrfs with snapshots"); return filesystemLabel(value); @@ -641,6 +648,25 @@ function FilesystemSelect({ ); } +type FilesystemLabelProps = { + id?: string; + value: string; + onChange: (v: string) => void; +}; + +function FilesystemLabel({ id, value, onChange }: FilesystemLabelProps): React.ReactNode { + const isValid = (v: string) => /^[\w-_.]*$/.test(v); + + return ( + isValid(v) && onChange(v)} + /> + ); +} + type SizeOptionLabelProps = { value: SizeOptionValue; mountPoint: string; @@ -1116,6 +1142,7 @@ export default function PartitionPage() { const [mountPoint, setMountPoint] = React.useState(NO_VALUE); const [target, setTarget] = React.useState(NEW_PARTITION); const [filesystem, setFilesystem] = React.useState(NO_VALUE); + const [filesystemLabel, setFilesystemLabel] = React.useState(NO_VALUE); const [sizeOption, setSizeOption] = React.useState(NO_VALUE); const [minSize, setMinSize] = React.useState(NO_VALUE); const [maxSize, setMaxSize] = React.useState(NO_VALUE); @@ -1125,7 +1152,7 @@ export default function PartitionPage() { const [autoRefreshSize, setAutoRefreshSize] = React.useState(false); const initialValue = useInitialFormValue(); - const value = { mountPoint, target, filesystem, sizeOption, minSize, maxSize }; + const value = { mountPoint, target, filesystem, filesystemLabel, sizeOption, minSize, maxSize }; const { errors, getVisibleError } = useErrors(value); const device = useDevice(); @@ -1138,6 +1165,7 @@ export default function PartitionPage() { setMountPoint(initialValue.mountPoint); setTarget(initialValue.target); setFilesystem(initialValue.filesystem); + setFilesystemLabel(initialValue.filesystemLabel); setSizeOption(initialValue.sizeOption); setMinSize(initialValue.minSize); setMaxSize(initialValue.maxSize); @@ -1147,6 +1175,7 @@ export default function PartitionPage() { setMountPoint, setTarget, setFilesystem, + setFilesystemLabel, setSizeOption, setMinSize, setMaxSize, @@ -1209,6 +1238,7 @@ export default function PartitionPage() { const isFormValid = errors.length === 0; const mountPointError = getVisibleError("mountPoint"); const usedMountPt = mountPointError ? NO_VALUE : mountPoint; + const showLabel = filesystem !== NO_VALUE && filesystem !== REUSE_FILESYSTEM; return ( @@ -1256,14 +1286,31 @@ export default function PartitionPage() { - - + + + + + + + + {showLabel && ( + + + + + + )} +