Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rhel9: systemd service to create mountpoints for filesystem customizations for edge images #399

Merged
merged 4 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Schutzfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"fedora-39": {
"dependencies": {
"osbuild": {
"commit": "b42e1afddc37dae3199e039f2a3d3cb10f01485c"
"commit": "6b4bb850a77feade79d839d9ea075e29ca210ae9"
}
},
"repos": [
Expand Down
6 changes: 3 additions & 3 deletions pkg/distro/rhel9/distro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions pkg/distro/rhel9/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
21 changes: 10 additions & 11 deletions pkg/distro/rhel9/imagetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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, ", "))
}
Expand Down Expand Up @@ -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, ", "))
}
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions pkg/image/ostree_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
50 changes: 50 additions & 0 deletions pkg/manifest/ostree_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
61 changes: 61 additions & 0 deletions pkg/osbuild/systemd_unit_create_stage.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
70 changes: 70 additions & 0 deletions pkg/osbuild/systemd_unit_create_stage_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
22 changes: 22 additions & 0 deletions test/config-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
}
Loading
Loading