From c15dc5386d80eb6c40ed296550a8c2ec2946533b Mon Sep 17 00:00:00 2001 From: Sayan Paul Date: Tue, 5 Dec 2023 17:54:14 +0530 Subject: [PATCH 1/4] blueprint/customization:filesystem customizations for edge images Filesystem customizations is enabled for edge-raw-image, edge-ami,edge-vsphere, edge-simplified-installer relevent testing config is added to run in CI Signed-off-by: Sayan Paul --- pkg/distro/rhel9/distro_test.go | 6 +-- pkg/distro/rhel9/imagetype.go | 21 ++++---- test/config-map.json | 22 ++++++++ ...e-filesystem-customizations-installer.json | 54 +++++++++++++++++++ .../ostree-filesystem-customizations.json | 53 ++++++++++++++++++ 5 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 test/configs/ostree-filesystem-customizations-installer.json create mode 100644 test/configs/ostree-filesystem-customizations.json diff --git a/pkg/distro/rhel9/distro_test.go b/pkg/distro/rhel9/distro_test.go index 29686ef96e..da8496291b 100644 --- a/pkg/distro/rhel9/distro_test.go +++ b/pkg/distro/rhel9/distro_test.go @@ -672,7 +672,7 @@ func TestDistro_CustomFileSystemManifestError(t *testing.T) { imgType, _ := arch.GetImageType(imgTypeName) _, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0) if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { - assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types") + assert.EqualError(t, err, "Custom mountpoints are not supported for edge-container and edge-commit") } else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" || imgTypeName == "edge-vsphere" { continue } else { @@ -700,7 +700,7 @@ func TestDistro_TestRootMountPoint(t *testing.T) { imgType, _ := arch.GetImageType(imgTypeName) _, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0) if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { - assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types") + assert.EqualError(t, err, "Custom mountpoints are not supported for edge-container and edge-commit") } else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" || imgTypeName == "edge-vsphere" { continue } else { @@ -830,7 +830,7 @@ func TestDistro_CustomUsrPartitionNotLargeEnough(t *testing.T) { imgType, _ := arch.GetImageType(imgTypeName) _, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0) if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { - assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types") + assert.EqualError(t, err, "Custom mountpoints are not supported for edge-container and edge-commit") } else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" || imgTypeName == "edge-vsphere" { continue } else { diff --git a/pkg/distro/rhel9/imagetype.go b/pkg/distro/rhel9/imagetype.go index c1bb8ad76b..d0a7b2bfa3 100644 --- a/pkg/distro/rhel9/imagetype.go +++ b/pkg/distro/rhel9/imagetype.go @@ -167,12 +167,7 @@ func (t *imageType) getPartitionTable( partitioningMode := options.PartitioningMode if t.rpmOstree { // Edge supports only LVM, force it. - // Raw is not supported, return an error if it is requested // TODO Need a central location for logic like this - if partitioningMode == disk.RawPartitioningMode { - return nil, fmt.Errorf("partitioning mode raw not supported for %s on %s", t.Name(), t.arch.Name()) - } - partitioningMode = disk.LVMPartitioningMode } @@ -320,7 +315,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp } if t.name == "edge-simplified-installer" { - allowed := []string{"InstallationDevice", "FDO", "Ignition", "Kernel", "User", "Group", "FIPS"} + allowed := []string{"InstallationDevice", "FDO", "Ignition", "Kernel", "User", "Group", "FIPS", "Filesystem"} if err := customizations.CheckAllowed(allowed...); err != nil { return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", ")) } @@ -370,8 +365,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp if options.OSTree == nil || options.OSTree.URL == "" { return warnings, fmt.Errorf("%q images require specifying a URL from which to retrieve the OSTree commit", t.name) } - - allowed := []string{"Ignition", "Kernel", "User", "Group", "FIPS"} + allowed := []string{"Ignition", "Kernel", "User", "Group", "FIPS", "Filesystem"} if err := customizations.CheckAllowed(allowed...); err != nil { return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", ")) } @@ -398,9 +392,14 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp } mountpoints := customizations.GetFilesystems() - - if mountpoints != nil && t.rpmOstree { - return warnings, fmt.Errorf("Custom mountpoints are not supported for ostree types") + if mountpoints != nil && t.rpmOstree && (t.name == "edge-container" || t.name == "edge-commit") { + return warnings, fmt.Errorf("Custom mountpoints are not supported for edge-container and edge-commit") + } else if mountpoints != nil && t.rpmOstree && !(t.name == "edge-container" || t.name == "edge-commit") { + //customization allowed for edge-raw-image,edge-ami,edge-vsphere,edge-simplified-installer + err := blueprint.CheckMountpointsPolicy(mountpoints, policies.OstreeMountpointPolicies) + if err != nil { + return warnings, err + } } err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies) diff --git a/test/config-map.json b/test/config-map.json index 195087f364..83b3e12e19 100644 --- a/test/config-map.json +++ b/test/config-map.json @@ -234,5 +234,27 @@ "image-types": [ "image-installer" ] + }, + "./configs/ostree-filesystem-customizations-installer.json": { + "image-types": [ + "edge-simplified-installer" + ], + "distros": [ + "rhel-9.2", + "rhel-9.3", + "rhel-9.4" + ] + }, + "./configs/ostree-filesystem-customizations.json": { + "image-types": [ + "edge-raw-image", + "edge-ami", + "edge-vsphere" + ], + "distros": [ + "rhel-9.2", + "rhel-9.3", + "rhel-9.4" + ] } } diff --git a/test/configs/ostree-filesystem-customizations-installer.json b/test/configs/ostree-filesystem-customizations-installer.json new file mode 100644 index 0000000000..379640d8bc --- /dev/null +++ b/test/configs/ostree-filesystem-customizations-installer.json @@ -0,0 +1,54 @@ +{ + "name": "ostree-filesystem-customizations-installer", + "ostree": { + "url": "http://example.com/repo" + }, + "blueprint": { + "customizations": { + "user": [ + { + "groups": [ + "wheel" + ], + "key": "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNebAh6SjpAn8wB53K4695cGnHGuCtl4RdaX3futZgJUultHyzeYHnzMO7d4++qnRL+Rworew62LKP560uvtncc= github.com/osbuild/images", + "name": "osbuild" + } + ], + "installation_device": "/dev/vda", + "filesystem": [ + { + "mountpoint": "/foo", + "minsize": "2147483648" + }, + { + "mountpoint": "/foo/bar", + "minsize": "2 GiB" + }, + { + "mountpoint": "/root", + "minsize": "1 GiB" + }, + { + "mountpoint": "/mnt", + "minsize": "3 GiB" + }, + { + "mountpoint": "/srv", + "minsize": "4 GiB" + }, + { + "mountpoint": "/opt", + "minsize": "1 GiB" + }, + { + "mountpoint": "/var/mydata", + "minsize": "1 GiB" + } + ] + } + }, + "depends": { + "image-type": "edge-container", + "config": "empty.json" + } +} diff --git a/test/configs/ostree-filesystem-customizations.json b/test/configs/ostree-filesystem-customizations.json new file mode 100644 index 0000000000..03ef336e22 --- /dev/null +++ b/test/configs/ostree-filesystem-customizations.json @@ -0,0 +1,53 @@ +{ + "name": "ostree-filesystem-customizations", + "ostree": { + "url": "http://example.com/repo" + }, + "blueprint": { + "customizations": { + "user": [ + { + "groups": [ + "wheel" + ], + "key": "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNebAh6SjpAn8wB53K4695cGnHGuCtl4RdaX3futZgJUultHyzeYHnzMO7d4++qnRL+Rworew62LKP560uvtncc= github.com/osbuild/images", + "name": "osbuild" + } + ], + "filesystem": [ + { + "mountpoint": "/foo", + "minsize": "2147483648" + }, + { + "mountpoint": "/foo/bar", + "minsize": "2 GiB" + }, + { + "mountpoint": "/root", + "minsize": "1 GiB" + }, + { + "mountpoint": "/mnt", + "minsize": "3 GiB" + }, + { + "mountpoint": "/srv", + "minsize": "4 GiB" + }, + { + "mountpoint": "/opt", + "minsize": "1 GiB" + }, + { + "mountpoint": "/var/mydata", + "minsize": "1 GiB" + } + ] + } + }, + "depends": { + "image-type": "edge-container", + "config": "empty.json" + } +} From 2ba0b641b2f5fda84bca1585d1564e631fefe4b5 Mon Sep 17 00:00:00 2001 From: Sayan Paul Date: Thu, 15 Feb 2024 21:06:20 +0530 Subject: [PATCH 2/4] stage/systemd-unit-create:add systemd-unit-create stage Create systemd unit using the system-unit-create stage This new stage adds the ability to create a systemd service unit file. It provides customization to choose the unit-type and unit-path, defaults to '/usr/lib/systemd/system/' Filename is validated using the same rules as specified by systemd.unit(5) and they must contain the '.service' suffix (other types of unit files are not supported). config accepts sections: `Unit`, `Service`, `Install` and each section accepts list of options that is in accordance to systemd.service documentation. Relavent testing is also added. Signed-off-by: Sayan Paul --- pkg/osbuild/systemd_unit_create_stage.go | 61 ++++++++++++++++ pkg/osbuild/systemd_unit_create_stage_test.go | 70 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 pkg/osbuild/systemd_unit_create_stage.go create mode 100644 pkg/osbuild/systemd_unit_create_stage_test.go diff --git a/pkg/osbuild/systemd_unit_create_stage.go b/pkg/osbuild/systemd_unit_create_stage.go new file mode 100644 index 0000000000..19eea44b80 --- /dev/null +++ b/pkg/osbuild/systemd_unit_create_stage.go @@ -0,0 +1,61 @@ +package osbuild + +type serviceType string +type unitPath string + +const ( + Simple serviceType = "simple" + Exec serviceType = "exec" + Forking serviceType = "forking" + Oneshot serviceType = "oneshot" + Dbus serviceType = "dbus" + Notify serviceType = "notify" + NotifyReloadservice serviceType = "notify-reload" + Idle serviceType = "idle" + Etc unitPath = "etc" + Usr unitPath = "usr" +) + +type Unit struct { + Description string `json:"Description,omitempty"` + DefaultDependencies bool `json:"DefaultDependencies,omitempty"` + ConditionPathExists []string `json:"ConditionPathExists,omitempty"` + ConditionPathIsDirectory []string `json:"ConditionPathIsDirectory,omitempty"` + Requires []string `json:"Requires,omitempty"` + Wants []string `json:"Wants,omitempty"` +} + +type Service struct { + Type serviceType `json:"Type,omitempty"` + RemainAfterExit bool `json:"RemainAfterExit,omitempty"` + ExecStartPre []string `json:"ExecStartPre,omitempty"` + ExecStopPost []string `json:"ExecStopPost,omitempty"` + ExecStart []string `json:"ExecStart,omitempty"` +} + +type Install struct { + RequiredBy []string `json:"RequiredBy,omitempty"` + WantedBy []string `json:"WantedBy,omitempty"` +} + +type SystemdServiceUnit struct { + Unit *Unit `json:"Unit"` + Service *Service `json:"Service"` + Install *Install `json:"Install"` +} + +type SystemdUnitCreateStageOptions struct { + Filename string `json:"filename"` + UnitType unitType `json:"unit-type,omitempty"` // unitType defined in ./systemd_unit_stage.go + UnitPath unitPath `json:"unit-path,omitempty"` + Config SystemdServiceUnit `json:"config"` +} + +func (SystemdUnitCreateStageOptions) isStageOptions() {} + +func NewSystemdUnitCreateStageOptions(options *SystemdUnitCreateStageOptions) *Stage { + return &Stage{ + Type: "org.osbuild.systemd.unit.create", + Options: options, + } +} diff --git a/pkg/osbuild/systemd_unit_create_stage_test.go b/pkg/osbuild/systemd_unit_create_stage_test.go new file mode 100644 index 0000000000..e50b0f5de4 --- /dev/null +++ b/pkg/osbuild/systemd_unit_create_stage_test.go @@ -0,0 +1,70 @@ +package osbuild + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func createSystemdUnit() SystemdServiceUnit { + + var unit = Unit{ + Description: "Create directory and files", + ConditionPathExists: []string{"!/etc/myfile"}, + ConditionPathIsDirectory: []string{"!/etc/mydir"}, + Requires: []string{"dbus.service", "libvirtd.service"}, + Wants: []string{"local-fs.target"}, + } + var service = Service{ + Type: Oneshot, + RemainAfterExit: true, + ExecStartPre: []string{"echo creating_files"}, + ExecStopPost: []string{"echo done_creating_files"}, + ExecStart: []string{"mkdir -p /etc/mydir", "touch /etc/myfiles"}, + } + + var install = Install{ + RequiredBy: []string{"multi-user.target", "boot-complete.target"}, + WantedBy: []string{"sshd.service"}, + } + + var systemdUnit = SystemdServiceUnit{ + Unit: &unit, + Service: &service, + Install: &install, + } + + return systemdUnit +} + +func TestNewSystemdUnitCreateStage(t *testing.T) { + systemdServiceConfig := createSystemdUnit() + var options = SystemdUnitCreateStageOptions{ + Filename: "create-dir-files", + Config: systemdServiceConfig, + } + expectedStage := &Stage{ + Type: "org.osbuild.systemd.unit.create", + Options: &options, + } + + actualStage := NewSystemdUnitCreateStageOptions(&options) + assert.Equal(t, expectedStage, actualStage) +} + +func TestNewSystemdUnitCreateStageInEtc(t *testing.T) { + systemdServiceConfig := createSystemdUnit() + var options = SystemdUnitCreateStageOptions{ + Filename: "create-dir-files", + Config: systemdServiceConfig, + UnitPath: Etc, + UnitType: Global, + } + expectedStage := &Stage{ + Type: "org.osbuild.systemd.unit.create", + Options: &options, + } + + actualStage := NewSystemdUnitCreateStageOptions(&options) + assert.Equal(t, expectedStage, actualStage) +} From 3efb44d0a81450d1986e5e064a1e7fcd66157739 Mon Sep 17 00:00:00 2001 From: Sayan Paul Date: Fri, 16 Feb 2024 14:18:31 +0530 Subject: [PATCH 3/4] pipeline/ostree-deployment:add custom-mountpount-create service Hooked up the create-unit-stage to add create-mountpoint.service to ostree deployment. Automatically add and enable osbuild-create-mountpoint.service whenever user adds filesystem customizationi, to ensure that the mountpoints are available post rpm-ostree upgrade. Signed-off-by: Sayan Paul --- pkg/distro/rhel9/images.go | 8 +++++ pkg/image/ostree_disk.go | 3 ++ pkg/manifest/ostree_deployment.go | 50 +++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/pkg/distro/rhel9/images.go b/pkg/distro/rhel9/images.go index 33632ce3b3..c51e14c9e0 100644 --- a/pkg/distro/rhel9/images.go +++ b/pkg/distro/rhel9/images.go @@ -490,6 +490,10 @@ func edgeRawImage(workload workload.Workload, img.Filename = t.Filename() img.Compression = t.compression + for _, fs := range customizations.GetFilesystems() { + img.CustomFilesystems = append(img.CustomFilesystems, fs.Mountpoint) + } + return img, nil } @@ -546,6 +550,10 @@ func edgeSimplifiedInstallerImage(workload workload.Workload, rawImg.Filename = t.Filename() + for _, fs := range customizations.GetFilesystems() { + rawImg.CustomFilesystems = append(rawImg.CustomFilesystems, fs.Mountpoint) + } + // 92+ only if kopts := customizations.GetKernel(); kopts != nil && kopts.Append != "" { rawImg.KernelOptionsAppend = append(rawImg.KernelOptionsAppend, kopts.Append) diff --git a/pkg/image/ostree_disk.go b/pkg/image/ostree_disk.go index 99ed5a4d00..2228d56477 100644 --- a/pkg/image/ostree_disk.go +++ b/pkg/image/ostree_disk.go @@ -57,6 +57,8 @@ type OSTreeDiskImage struct { // Container buildable tweaks the buildroot to be container friendly, // i.e. to not rely on an installed osbuild-selinux ContainerBuildable bool + + CustomFilesystems []string } func NewOSTreeDiskImageFromCommit(commit ostree.SourceSpec) *OSTreeDiskImage { @@ -107,6 +109,7 @@ func baseRawOstreeImage(img *OSTreeDiskImage, buildPipeline manifest.Build, opts osPipeline.IgnitionPlatform = img.IgnitionPlatform osPipeline.LockRoot = img.LockRoot osPipeline.UseBootupd = opts.useBootupd + osPipeline.CustomFileSystems = img.CustomFilesystems // other image types (e.g. live) pass the workload to the pipeline. if img.Workload != nil { diff --git a/pkg/manifest/ostree_deployment.go b/pkg/manifest/ostree_deployment.go index 3e3afb97f1..e871cb5079 100644 --- a/pkg/manifest/ostree_deployment.go +++ b/pkg/manifest/ostree_deployment.go @@ -74,6 +74,8 @@ type OSTreeDeployment struct { // Use bootupd instead of grub2 as the bootloader UseBootupd bool + + CustomFileSystems []string } // NewOSTreeCommitDeployment creates a pipeline for an ostree deployment from a @@ -353,6 +355,19 @@ func (p *OSTreeDeployment) serialize() osbuild.Pipeline { }, })) + // This will create a custom systemd unit that create + // mountpoints if its not present.This will safeguard + // any ostree deployment which has custom filesystem + // during ostree upgrade. + // issue # https://github.com/osbuild/images/issues/352 + if len(p.CustomFileSystems) != 0 { + serviceName := "osbuild-ostree-mountpoints.service" + stageOption := osbuild.NewSystemdUnitCreateStageOptions(createMountpointService(serviceName, p.CustomFileSystems)) + stageOption.MountOSTree(p.osName, ref, 0) + pipeline.AddStage(stageOption) + p.EnabledServices = append(p.EnabledServices, serviceName) + } + // We enable / disable services below using the systemd stage, but its effect // may be overridden by systemd which may reset enabled / disabled services on // firstboot (which happend on F37+). This behavior, if available, is triggered @@ -481,3 +496,38 @@ func (p *OSTreeDeployment) getInline() []string { return inlineData } + +// Creates systemd unit stage by ingesting the servicename and mount-points +func createMountpointService(serviceName string, mountpoints []string) *osbuild.SystemdUnitCreateStageOptions { + var conditionPathIsDirectory []string + for _, mountpoint := range mountpoints { + conditionPathIsDirectory = append(conditionPathIsDirectory, "|!"+mountpoint) + } + unit := osbuild.Unit{ + Description: "Ensure custom filesystem mountpoints exist", + DefaultDependencies: false, + ConditionPathIsDirectory: conditionPathIsDirectory, + } + service := osbuild.Service{ + Type: osbuild.Oneshot, + RemainAfterExit: true, + //compatibility with composefs, will require transient rootfs to be enabled too. + ExecStartPre: []string{"/bin/sh -c \"if [ -z \"$(grep -Uq composefs /run/ostree-booted)\" ]; then chattr -i /; fi\""}, + ExecStopPost: []string{"/bin/sh -c \"if [ -z \"$(grep -Uq composefs /run/ostree-booted)\" ]; then chattr +i /; fi\""}, + ExecStart: []string{"mkdir -p " + strings.Join(mountpoints[:], " ")}, + } + install := osbuild.Install{ + WantedBy: []string{"local-fs.target"}, + } + options := osbuild.SystemdUnitCreateStageOptions{ + Filename: serviceName, + UnitPath: osbuild.Etc, + UnitType: osbuild.System, + Config: osbuild.SystemdServiceUnit{ + Unit: &unit, + Service: &service, + Install: &install, + }, + } + return &options +} From 56c05f36b49432ef3f3384c4a162929e3436d552 Mon Sep 17 00:00:00 2001 From: Sayan Paul Date: Mon, 11 Mar 2024 16:59:29 +0530 Subject: [PATCH 4/4] schutzfile:update os build commit Signed-off-by: Sayan Paul --- Schutzfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Schutzfile b/Schutzfile index 1b0252ef20..cfcd55bb8f 100644 --- a/Schutzfile +++ b/Schutzfile @@ -8,7 +8,7 @@ "fedora-39": { "dependencies": { "osbuild": { - "commit": "b42e1afddc37dae3199e039f2a3d3cb10f01485c" + "commit": "6b4bb850a77feade79d839d9ea075e29ca210ae9" } }, "repos": [