Skip to content
Closed
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 pkg/manifest/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func NewTestOSWithPlatform(pf *platform.Data) *OS {
}

func (p *OSTreeDeployment) AddStagesForAllFilesAndInlineData(pipeline *osbuild.Pipeline, files []*fsnode.File) {
p.addStagesForAllFilesAndInlineData(pipeline, files, "ostree/ref")
p.addStagesForAllFilesAndInlineData(pipeline, files)
}

func (p *Vagrant) GetMacAddress() string {
Expand Down
86 changes: 30 additions & 56 deletions pkg/manifest/ostree_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,11 @@ func (p *OSTreeDeployment) serialize() (osbuild.Pipeline, error) {
return osbuild.Pipeline{}, fmt.Errorf("no content source defined for ostree deployment")
}

configStage := osbuild.NewOSTreeConfigStage(
// Set up mounts for the pipeline. These are used by all stages in
// this pipeline except those added via AddStageDirect.
pipeline.SetMounts(*osbuild.NewOSTreeDeploymentMount("ostree-"+ref, p.osName, ref, 0))

pipeline.AddStage(osbuild.NewOSTreeConfigStage(
&osbuild.OSTreeConfigStageOptions{
Repo: repoPath,
Config: &osbuild.OSTreeConfig{
Expand All @@ -347,36 +351,30 @@ func (p *OSTreeDeployment) serialize() (osbuild.Pipeline, error) {
},
},
},
)
configStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(configStage)
))

fsCfgStages, err := filesystemConfigStages(p.PartitionTable, p.MountConfiguration)
if err != nil {
return osbuild.Pipeline{}, err
}
for _, stage := range fsCfgStages {
stage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(stage)
}
pipeline.AddStages(fsCfgStages...)

if len(p.Groups) > 0 {
grpStage := osbuild.GenGroupsStage(p.Groups)
grpStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(grpStage)
pipeline.AddStage(osbuild.GenGroupsStage(p.Groups))
}

if len(p.Users) > 0 {
usersStage, err := osbuild.GenUsersStage(p.Users, false)
if err != nil {
return osbuild.Pipeline{}, fmt.Errorf("password encryption failed")
}
usersStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(usersStage)
}

if p.IgnitionPlatform != "" {
pipeline.AddStage(osbuild.NewIgnitionStage(&osbuild.IgnitionStageOptions{
// The ignition stage writes outside the deployment, so it
// bypasses the pipeline mounts.
pipeline.AddStageDirect(osbuild.NewIgnitionStage(&osbuild.IgnitionStageOptions{
// This is a workaround to make the systemd believe it's firstboot when ignition runs on real firstboot.
// Right now, since we ship /etc/machine-id, systemd thinks it's not firstboot and ignition depends on it
// to run on the real firstboot to enable services from presets.
Expand All @@ -399,9 +397,7 @@ func (p *OSTreeDeployment) serialize() (osbuild.Pipeline, error) {
if err != nil {
return osbuild.Pipeline{}, err
}
stageOption := osbuild.NewSystemdUnitCreateStage(mps)
stageOption.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(stageOption)
pipeline.AddStage(osbuild.NewSystemdUnitCreateStage(mps))
p.EnabledServices = append(p.EnabledServices, serviceName)
}

Expand All @@ -412,9 +408,7 @@ func (p *OSTreeDeployment) serialize() (osbuild.Pipeline, error) {
// in the code based on distro version, we enable / disable services also by
// creating a preset file.
if len(p.EnabledServices) != 0 || len(p.DisabledServices) != 0 {
presetsStage := osbuild.GenServicesPresetStage(p.EnabledServices, p.DisabledServices)
presetsStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(presetsStage)
pipeline.AddStage(osbuild.GenServicesPresetStage(p.EnabledServices, p.DisabledServices))
}
}

Expand All @@ -428,35 +422,24 @@ func (p *OSTreeDeployment) serialize() (osbuild.Pipeline, error) {
}

if p.LockRootUser && !hasRoot {
rootLockStage := osbuild.NewUsersStage(osbuild.NewUsersStageOptionsForLockedRoot())
rootLockStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(rootLockStage)
pipeline.AddStage(osbuild.NewUsersStage(osbuild.NewUsersStageOptionsForLockedRoot()))
}

if p.Keyboard != "" {
options := &osbuild.KeymapStageOptions{
pipeline.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{
Keymap: p.Keyboard,
}
keymapStage := osbuild.NewKeymapStage(options)
keymapStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(keymapStage)
}))
}

if p.Locale != "" {
options := &osbuild.LocaleStageOptions{
pipeline.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{
Language: p.Locale,
}
localeStage := osbuild.NewLocaleStage(options)
localeStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(localeStage)
}))
}

if p.FIPS {
p.addStagesForAllFilesAndInlineData(&pipeline, osbuild.GenFIPSFiles(), ref)
for _, stage := range osbuild.GenFIPSStages() {
stage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(stage)
}
p.addStagesForAllFilesAndInlineData(&pipeline, osbuild.GenFIPSFiles())
pipeline.AddStages(osbuild.GenFIPSStages()...)
}

if !p.UseBootupd {
Expand All @@ -473,34 +456,28 @@ func (p *OSTreeDeployment) serialize() (osbuild.Pipeline, error) {
Timeout: 1,
TerminalOutput: []string{"console"},
}
bootloader := osbuild.NewGRUB2Stage(grubOptions)
bootloader.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(bootloader)
pipeline.AddStage(osbuild.NewGRUB2Stage(grubOptions))
}

// First create custom directories, because some of the files may depend on them
if len(p.Directories) > 0 {
dirStages := osbuild.GenDirectoryNodesStages(p.Directories)
for _, stage := range dirStages {
stage.MountOSTree(p.osName, ref, 0)
}
pipeline.AddStages(dirStages...)
pipeline.AddStages(osbuild.GenDirectoryNodesStages(p.Directories)...)
}

if len(p.Files) > 0 {
p.addStagesForAllFilesAndInlineData(&pipeline, p.Files, ref)
p.addStagesForAllFilesAndInlineData(&pipeline, p.Files)
}

if len(p.EnabledServices) != 0 || len(p.DisabledServices) != 0 {
systemdStage := osbuild.NewSystemdStage(&osbuild.SystemdStageOptions{
pipeline.AddStage(osbuild.NewSystemdStage(&osbuild.SystemdStageOptions{
EnabledServices: p.EnabledServices,
DisabledServices: p.DisabledServices,
})
systemdStage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(systemdStage)
}))
}

pipeline.AddStage(osbuild.NewOSTreeSelinuxStage(
// The selinux stage references the deployment through its options,
// not through mounts, so it bypasses the pipeline mounts.
pipeline.AddStageDirect(osbuild.NewOSTreeSelinuxStage(
&osbuild.OSTreeSelinuxStageOptions{
Deployment: osbuild.OSTreeDeployment{
OSName: p.osName,
Expand All @@ -519,11 +496,8 @@ func (p *OSTreeDeployment) getInline() []string {
// addStagesForAllFilesAndInlineData generates stages for creating files and adds them to
// the pipeline. It also adds their data to the inlineData for the pipeline so
// that the appropriate sources are created.
func (p *OSTreeDeployment) addStagesForAllFilesAndInlineData(pipeline *osbuild.Pipeline, files []*fsnode.File, ref string) {
for _, stage := range osbuild.GenFileNodesStages(files) {
stage.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(stage)
}
func (p *OSTreeDeployment) addStagesForAllFilesAndInlineData(pipeline *osbuild.Pipeline, files []*fsnode.File) {
pipeline.AddStages(osbuild.GenFileNodesStages(files)...)

for _, file := range files {
p.inlineData = append(p.inlineData, string(file.Data()))
Expand Down
63 changes: 20 additions & 43 deletions pkg/manifest/raw_bootc.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,46 +189,39 @@ func (p *RawBootcImage) serialize() (osbuild.Pipeline, error) {
mounts = append(mounts, *osbuild.NewOSTreeDeploymentMountDefault("ostree.deployment", osbuild.OSTreeMountSourceMount))
mounts = append(mounts, *osbuild.NewBindMount("bind-ostree-deployment-to-tree", "mount://", "tree://"))

pipeline.SetMounts(mounts...)
pipeline.SetDevices(devices)

postStages := []*osbuild.Stage{}

fsCfgStages, err := filesystemConfigStages(pt, p.DiskCustomizations.MountConfiguration)
if err != nil {
return osbuild.Pipeline{}, err
}
for _, stage := range fsCfgStages {
stage.Mounts = mounts
stage.Devices = devices
postStages = append(postStages, stage)
}
postStages = append(postStages, fsCfgStages...)

// customize the image
if len(p.OSCustomizations.Groups) > 0 {
groupsStage := osbuild.GenGroupsStage(p.OSCustomizations.Groups)
groupsStage.Mounts = mounts
groupsStage.Devices = devices
postStages = append(postStages, groupsStage)
postStages = append(postStages, osbuild.GenGroupsStage(p.OSCustomizations.Groups))
}

if len(p.OSCustomizations.Users) > 0 {
// ensure home root dir (currently /var/home, /var/roothome) is
// available
mkdirStage := osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
postStages = append(postStages, osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: buildHomedirPaths(p.OSCustomizations.Users),
})
mkdirStage.Mounts = mounts
mkdirStage.Devices = devices
postStages = append(postStages, mkdirStage)
}))

// add the users
usersStage, err := osbuild.GenUsersStage(p.OSCustomizations.Users, false)
if err != nil {
return osbuild.Pipeline{}, fmt.Errorf("user stage failed %w", err)
}
usersStage.Mounts = mounts
usersStage.Devices = devices
postStages = append(postStages, usersStage)
}

pipeline.AddStages(postStages...)

if p.LiveBoot {
// The dracut dmsquash-live module has a check for the root filesystem
// This is a kludge to work around this: On Fedora and RHEL10 it expects /usr in the root
Expand All @@ -254,31 +247,22 @@ func (p *RawBootcImage) serialize() (osbuild.Pipeline, error) {
}
noDeploymentMounts = append(noDeploymentMounts, m)
}
// These stages use custom mounts (without the deployment
// mount) so they bypass the pipeline mounts.
for _, stage := range stages {
stage.Mounts = noDeploymentMounts
stage.Devices = devices
pipeline.AddStageDirect(stage)
}
postStages = append(postStages, stages...)
}

// First create custom directories, because some of the custom files may depend on them
if len(p.OSCustomizations.Directories) > 0 {

stages := osbuild.GenDirectoryNodesStages(p.OSCustomizations.Directories)
for _, stage := range stages {
stage.Mounts = mounts
stage.Devices = devices
}
postStages = append(postStages, stages...)
pipeline.AddStages(osbuild.GenDirectoryNodesStages(p.OSCustomizations.Directories)...)
}

if len(p.OSCustomizations.Files) > 0 {
stages := osbuild.GenFileNodesStages(p.OSCustomizations.Files)
for _, stage := range stages {
stage.Mounts = mounts
stage.Devices = devices
}
postStages = append(postStages, stages...)
pipeline.AddStages(osbuild.GenFileNodesStages(p.OSCustomizations.Files)...)
}

// The ignition stamp must be created after bootc install, otherwise bootc will error out
Expand All @@ -299,32 +283,25 @@ func (p *RawBootcImage) serialize() (osbuild.Pipeline, error) {
}
ignitionStage = osbuild.NewIgnitionStage(&opts)
}
var err error
// We cannot reuse the existing mounts because the generated ostree mounts are shadowing /boot and the file ends up
// in the wrong place. We reuse the bootupd mount generator as it's enough for this. We just need /boot.
// The ignition stage uses its own mounts and devices
// because the pipeline ostree mounts shadow /boot.
ignitionStage.Devices, ignitionStage.Mounts, err = osbuild.GenBootupdDevicesMounts(p.filename, p.PartitionTable, p.platform)
if err != nil {
return osbuild.Pipeline{}, fmt.Errorf("gen devices stage failed %w", err)
}
postStages = append(postStages, ignitionStage)
pipeline.AddStageDirect(ignitionStage)
}

pipeline.AddStages(postStages...)

// In case we created any files in the deploy directory we need to relabel
// then per the selinux policy
if p.OSCustomizations.SELinux != "" {
if len(postStages) > 0 {
if len(postStages) > 0 || len(p.OSCustomizations.Directories) > 0 || len(p.OSCustomizations.Files) > 0 {
for _, changedFile := range []string{"/etc", "/var"} {
opts := &osbuild.SELinuxStageOptions{
pipeline.AddStage(osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{
Target: "tree://" + changedFile,
FileContexts: fmt.Sprintf("etc/selinux/%s/contexts/files/file_contexts", p.OSCustomizations.SELinux),
ExcludePaths: []string{"/sysroot"},
}
selinuxStage := osbuild.NewSELinuxStage(opts)
selinuxStage.Mounts = mounts
selinuxStage.Devices = devices
pipeline.AddStage(selinuxStage)
}))
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions pkg/osbuild/osbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type Pipeline struct {
// Sequence of stages that produce the filesystem tree, which is the
// payload of the produced image.
Stages []*Stage `json:"stages,omitempty"`

mounts []Mount
devices map[string]Device
}

// SetBuild sets the pipeline and runner for generating the build environment
Expand All @@ -39,10 +42,43 @@ func (p *Pipeline) SetBuild(build string) {
p.Build = build
}

// SetMounts sets pipeline-level mounts that are appended to every stage
// added via AddStage or AddStages.
func (p *Pipeline) SetMounts(mounts ...Mount) {
p.mounts = append(p.mounts, mounts...)
}

// SetDevices sets pipeline-level devices that are merged into every stage
// added via AddStage or AddStages.
func (p *Pipeline) SetDevices(devices map[string]Device) {
p.devices = devices
}

// AddStage appends a stage to the list of stages of a pipeline. The stages
// will be executed in the order they are appended.
// If the argument is nil, it is not added.
func (p *Pipeline) AddStage(stage *Stage) {
if stage != nil {
stage.Mounts = append(stage.Mounts, p.mounts...)
if len(p.devices) > 0 {
if stage.Devices == nil {
stage.Devices = make(map[string]Device, len(p.devices))
}
for k, v := range p.devices {
if _, exists := stage.Devices[k]; exists {
panic(fmt.Sprintf("stage %q already defines device %q which conflicts with pipeline device", stage.Type, k))
}
stage.Devices[k] = v
}
}
p.Stages = append(p.Stages, stage)
}
}

// AddStageDirect appends a stage without merging pipeline-level mounts or
// devices. Use this for stages that intentionally bypass the pipeline
// mounts (e.g. stages that write outside the deployment).
func (p *Pipeline) AddStageDirect(stage *Stage) {
if stage != nil {
p.Stages = append(p.Stages, stage)
}
Expand Down
Loading
Loading