Repository: osbuild/osbuild-composer
Pull request #2953 (2022-09-02 15:43:19)

distro/`ImageConfig`: use pointers to simple types and reflection in `InheritFrom()`
These changes are required in order to enable unifying the `vhd` and `azure-rhui` image definitions. Detailed reasoning for each change is described in each commit message.

This pull request includes:

- [ ] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`

Information regarding our GitLab pipeline (Schutzbot):

CI will not be ran automatically if WIP label is applied or the PR is in DRAFT state, instead only
a link will be provided to the pipeline which can then be triggered manually if desired. To run the
CI automatically either switch the PR to ready or apply WIP+test label.

Outside contributors need manual approval from one of the osbuild-composer maintainers.

Schutzbot will only be triggered if all Tests jobs in GitHub workflow succeed.
-->


thozza: I removed the update to newer Golang version.

Patch:
diff --git a/internal/distro/fedora/distro.go b/internal/distro/fedora/distro.go
index df1be4ba6..81b975f22 100644
--- a/internal/distro/fedora/distro.go
+++ b/internal/distro/fedora/distro.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
+	"github.com/osbuild/osbuild-composer/internal/common"
 	"github.com/osbuild/osbuild-composer/internal/container"
 	"github.com/osbuild/osbuild-composer/internal/disk"
 	"github.com/osbuild/osbuild-composer/internal/distro"
@@ -122,7 +123,7 @@ var (
 			installerPkgsKey: iotInstallerPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale:          "en_US.UTF-8",
+			Locale:          common.StringToPtr("en_US.UTF-8"),
 			EnabledServices: iotServices,
 		},
 		rpmOstree:        true,
@@ -141,7 +142,7 @@ var (
 			osPkgsKey: qcow2CommonPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 			EnabledServices: []string{
 				"cloud-init.service",
 				"cloud-config.service",
@@ -167,11 +168,11 @@ var (
 			osPkgsKey: vhdCommonPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale: "en_US.UTF-8",
+			Locale: common.StringToPtr("en_US.UTF-8"),
 			EnabledServices: []string{
 				"sshd",
 			},
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 			DisabledServices: []string{
 				"proc-sys-fs-binfmt_misc.mount",
 				"loadmodules.service",
@@ -196,7 +197,7 @@ var (
 			osPkgsKey: vmdkCommonPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale: "en_US.UTF-8",
+			Locale: common.StringToPtr("en_US.UTF-8"),
 			EnabledServices: []string{
 				"cloud-init.service",
 				"cloud-config.service",
@@ -222,7 +223,7 @@ var (
 			osPkgsKey: openstackCommonPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale: "en_US.UTF-8",
+			Locale: common.StringToPtr("en_US.UTF-8"),
 			EnabledServices: []string{
 				"cloud-init.service",
 				"cloud-config.service",
@@ -242,7 +243,7 @@ var (
 
 	// default EC2 images config (common for all architectures)
 	defaultEc2ImageConfig = &distro.ImageConfig{
-		DefaultTarget: "multi-user.target",
+		DefaultTarget: common.StringToPtr("multi-user.target"),
 	}
 
 	amiImgType = imageType{
@@ -272,10 +273,10 @@ var (
 			osPkgsKey: containerPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			NoSElinux:   true,
-			ExcludeDocs: true,
-			Locale:      "C.UTF-8",
-			Timezone:    "Etc/UTC",
+			NoSElinux:   common.BoolToPtr(true),
+			ExcludeDocs: common.BoolToPtr(true),
+			Locale:      common.StringToPtr("C.UTF-8"),
+			Timezone:    common.StringToPtr("Etc/UTC"),
 		},
 		image:            containerImage,
 		bootable:         false,
@@ -300,8 +301,8 @@ type distribution struct {
 
 // Fedora based OS image configuration defaults
 var defaultDistroImageConfig = &distro.ImageConfig{
-	Timezone: "UTC",
-	Locale:   "en_US",
+	Timezone: common.StringToPtr("UTC"),
+	Locale:   common.StringToPtr("en_US"),
 }
 
 // distribution objects without the arches > image types
diff --git a/internal/distro/fedora/images.go b/internal/distro/fedora/images.go
index 5cdaf777d..a15014088 100644
--- a/internal/distro/fedora/images.go
+++ b/internal/distro/fedora/images.go
@@ -41,7 +41,9 @@ func osCustomizations(
 	osc.ExtraBaseRepos = osPackageSet.Repositories
 
 	osc.GPGKeyFiles = imageConfig.GPGKeyFiles
-	osc.ExcludeDocs = imageConfig.ExcludeDocs
+	if imageConfig.ExcludeDocs != nil {
+		osc.ExcludeDocs = *imageConfig.ExcludeDocs
+	}
 
 	if !t.bootISO {
 		// don't put users and groups in the payload of an installer
@@ -52,15 +54,17 @@ func osCustomizations(
 
 	osc.EnabledServices = imageConfig.EnabledServices
 	osc.DisabledServices = imageConfig.DisabledServices
-	osc.DefaultTarget = imageConfig.DefaultTarget
+	if imageConfig.DefaultTarget != nil {
+		osc.DefaultTarget = *imageConfig.DefaultTarget
+	}
 
 	osc.Firewall = c.GetFirewall()
 
 	language, keyboard := c.GetPrimaryLocale()
 	if language != nil {
 		osc.Language = *language
-	} else {
-		osc.Language = imageConfig.Locale
+	} else if imageConfig.Locale != nil {
+		osc.Language = *imageConfig.Locale
 	}
 	if keyboard != nil {
 		osc.Keyboard = keyboard
@@ -77,8 +81,8 @@ func osCustomizations(
 	timezone, ntpServers := c.GetTimezoneSettings()
 	if timezone != nil {
 		osc.Timezone = *timezone
-	} else {
-		osc.Timezone = imageConfig.Timezone
+	} else if imageConfig.Timezone != nil {
+		osc.Timezone = *imageConfig.Timezone
 	}
 
 	if len(ntpServers) > 0 {
@@ -87,7 +91,8 @@ func osCustomizations(
 		osc.NTPServers = imageConfig.TimeSynchronization.Timeservers
 	}
 
-	if !imageConfig.NoSElinux {
+	// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
+	if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
 		osc.SElinux = "targeted"
 	}
 
diff --git a/internal/distro/image_config.go b/internal/distro/image_config.go
index a428093fa..f558c43cd 100644
--- a/internal/distro/image_config.go
+++ b/internal/distro/image_config.go
@@ -1,6 +1,11 @@
 package distro
 
-import "github.com/osbuild/osbuild-composer/internal/osbuild"
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/osbuild/osbuild-composer/internal/osbuild"
+)
 
 type RHSMSubscriptionStatus string
 
@@ -11,23 +16,23 @@ const (
 
 // ImageConfig represents a (default) configuration applied to the image
 type ImageConfig struct {
-	Timezone            string
+	Timezone            *string
 	TimeSynchronization *osbuild.ChronyStageOptions
-	Locale              string
+	Locale              *string
 	Keyboard            *osbuild.KeymapStageOptions
 	EnabledServices     []string
 	DisabledServices    []string
-	DefaultTarget       string
+	DefaultTarget       *string
 	Sysconfig           []*osbuild.SysconfigStageOptions
 
 	// List of files from which to import GPG keys into the RPM database
 	GPGKeyFiles []string
 
 	// Disable SELinux labelling
-	NoSElinux bool
+	NoSElinux *bool
 
 	// Disable documentation
-	ExcludeDocs bool
+	ExcludeDocs *bool
 
 	// for RHSM configuration, we need to potentially distinguish the case
 	// when the user want the image to be subscribed on first boot and when not
@@ -62,98 +67,21 @@ type ImageConfig struct {
 func (c *ImageConfig) InheritFrom(parentConfig *ImageConfig) *ImageConfig {
 	finalConfig := ImageConfig(*c)
 	if parentConfig != nil {
-		if finalConfig.Timezone == "" {
-			finalConfig.Timezone = parentConfig.Timezone
-		}
-		if finalConfig.TimeSynchronization == nil {
-			finalConfig.TimeSynchronization = parentConfig.TimeSynchronization
-		}
-		if finalConfig.Locale == "" {
-			finalConfig.Locale = parentConfig.Locale
-		}
-		if finalConfig.Keyboard == nil {
-			finalConfig.Keyboard = parentConfig.Keyboard
-		}
-		if finalConfig.EnabledServices == nil {
-			finalConfig.EnabledServices = parentConfig.EnabledServices
-		}
-		if finalConfig.DisabledServices == nil {
-			finalConfig.DisabledServices = parentConfig.DisabledServices
-		}
-		if finalConfig.DefaultTarget == "" {
-			finalConfig.DefaultTarget = parentConfig.DefaultTarget
-		}
-		if finalConfig.Sysconfig == nil {
-			finalConfig.Sysconfig = parentConfig.Sysconfig
-		}
-		if finalConfig.GPGKeyFiles == nil {
-			finalConfig.GPGKeyFiles = parentConfig.GPGKeyFiles
-		}
-		if finalConfig.RHSMConfig == nil {
-			finalConfig.RHSMConfig = parentConfig.RHSMConfig
-		}
-		if finalConfig.SystemdLogind == nil {
-			finalConfig.SystemdLogind = parentConfig.SystemdLogind
-		}
-		if finalConfig.CloudInit == nil {
-			finalConfig.CloudInit = parentConfig.CloudInit
-		}
-		if finalConfig.Modprobe == nil {
-			finalConfig.Modprobe = parentConfig.Modprobe
-		}
-		if finalConfig.DracutConf == nil {
-			finalConfig.DracutConf = parentConfig.DracutConf
-		}
-		if finalConfig.SystemdUnit == nil {
-			finalConfig.SystemdUnit = parentConfig.SystemdUnit
-		}
-		if finalConfig.Authselect == nil {
-			finalConfig.Authselect = parentConfig.Authselect
-		}
-		if finalConfig.SELinuxConfig == nil {
-			finalConfig.SELinuxConfig = parentConfig.SELinuxConfig
-		}
-		if finalConfig.Tuned == nil {
-			finalConfig.Tuned = parentConfig.Tuned
-		}
-		if finalConfig.Tmpfilesd == nil {
-			finalConfig.Tmpfilesd = parentConfig.Tmpfilesd
-		}
-		if finalConfig.PamLimitsConf == nil {
-			finalConfig.PamLimitsConf = parentConfig.PamLimitsConf
-		}
-		if finalConfig.Sysctld == nil {
-			finalConfig.Sysctld = parentConfig.Sysctld
-		}
-		if finalConfig.DNFConfig == nil {
-			finalConfig.DNFConfig = parentConfig.DNFConfig
-		}
-		if finalConfig.SshdConfig == nil {
-			finalConfig.SshdConfig = parentConfig.SshdConfig
-		}
-		if finalConfig.Authconfig == nil {
-			finalConfig.Authconfig = parentConfig.Authconfig
-		}
-		if finalConfig.PwQuality == nil {
-			finalConfig.PwQuality = parentConfig.PwQuality
-		}
-		if finalConfig.DNFAutomaticConfig == nil {
-			finalConfig.DNFAutomaticConfig = parentConfig.DNFAutomaticConfig
-		}
-		if finalConfig.YumConfig == nil {
-			finalConfig.YumConfig = parentConfig.YumConfig
-		}
-		if finalConfig.YUMRepos == nil {
-			finalConfig.YUMRepos = parentConfig.YUMRepos
-		}
-		if finalConfig.Firewall == nil {
-			finalConfig.Firewall = parentConfig.Firewall
-		}
-		if finalConfig.UdevRules == nil {
-			finalConfig.UdevRules = parentConfig.UdevRules
-		}
-		if finalConfig.GCPGuestAgentConfig == nil {
-			finalConfig.GCPGuestAgentConfig = parentConfig.GCPGuestAgentConfig
+		// iterate over all struct fields and copy unset values from the parent
+		for i := 0; i < reflect.TypeOf(*c).NumField(); i++ {
+			fieldName := reflect.TypeOf(*c).Field(i).Name
+			field := reflect.ValueOf(&finalConfig).Elem().FieldByName(fieldName)
+
+			// Only container types or pointer are supported.
+			// The reason is that with basic types, we can't distinguish between unset value and zero value.
+			if kind := field.Kind(); kind != reflect.Ptr && kind != reflect.Slice && kind != reflect.Map {
+				panic(fmt.Sprintf("unsupported field type: %s (only container types or pointer are supported)",
+					field.Kind()))
+			}
+
+			if field.IsNil() {
+				field.Set(reflect.ValueOf(parentConfig).Elem().FieldByName(fieldName))
+			}
 		}
 	}
 	return &finalConfig
diff --git a/internal/distro/rhel7/azure.go b/internal/distro/rhel7/azure.go
index 5210d9505..e4a161580 100644
--- a/internal/distro/rhel7/azure.go
+++ b/internal/distro/rhel7/azure.go
@@ -221,8 +221,8 @@ var azureRhuiImgType = imageType{
 		osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 	},
 	defaultImageConfig: &distro.ImageConfig{
-		Timezone: "Etc/UTC",
-		Locale:   "en_US.UTF-8",
+		Timezone: common.StringToPtr("Etc/UTC"),
+		Locale:   common.StringToPtr("en_US.UTF-8"),
 		GPGKeyFiles: []string{
 			"/etc/pki/rpm-gpg/RPM-GPG-KEY-microsoft-azure-release",
 			"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
@@ -402,7 +402,7 @@ var azureRhuiImgType = imageType{
 				},
 			},
 		},
-		DefaultTarget: "multi-user.target",
+		DefaultTarget: common.StringToPtr("multi-user.target"),
 	},
 	kernelOptions:       "ro crashkernel=auto console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300 scsi_mod.use_blk_mq=y",
 	bootable:            true,
diff --git a/internal/distro/rhel7/distro.go b/internal/distro/rhel7/distro.go
index 2bae70616..6e4d43aed 100644
--- a/internal/distro/rhel7/distro.go
+++ b/internal/distro/rhel7/distro.go
@@ -9,6 +9,7 @@ import (
 	"strings"
 
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
+	"github.com/osbuild/osbuild-composer/internal/common"
 	"github.com/osbuild/osbuild-composer/internal/container"
 	"github.com/osbuild/osbuild-composer/internal/disk"
 	"github.com/osbuild/osbuild-composer/internal/distro"
@@ -31,8 +32,8 @@ const (
 
 // RHEL-based OS image configuration defaults
 var defaultDistroImageConfig = &distro.ImageConfig{
-	Timezone: "America/New_York",
-	Locale:   "en_US.UTF-8",
+	Timezone: common.StringToPtr("America/New_York"),
+	Locale:   common.StringToPtr("en_US.UTF-8"),
 	GPGKeyFiles: []string{
 		"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
 	},
diff --git a/internal/distro/rhel7/pipelines.go b/internal/distro/rhel7/pipelines.go
index 267680f78..c2134e6d4 100644
--- a/internal/distro/rhel7/pipelines.go
+++ b/internal/distro/rhel7/pipelines.go
@@ -47,7 +47,7 @@ func osPipeline(t *imageType,
 	rpmOptions := osbuild.NewRPMStageOptions(repos)
 	rpmOptions.GPGKeysFromTree = imageConfig.GPGKeyFiles
 
-	if imageConfig.ExcludeDocs {
+	if imageConfig.ExcludeDocs != nil && *imageConfig.ExcludeDocs {
 		if rpmOptions.Exclude == nil {
 			rpmOptions.Exclude = &osbuild.Exclude{}
 		}
@@ -60,8 +60,8 @@ func osPipeline(t *imageType,
 	language, keyboard := c.GetPrimaryLocale()
 	if language != nil {
 		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language}))
-	} else {
-		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: imageConfig.Locale}))
+	} else if imageConfig.Locale != nil {
+		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *imageConfig.Locale}))
 	}
 	if keyboard != nil {
 		p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard}))
@@ -76,8 +76,8 @@ func osPipeline(t *imageType,
 	timezone, ntpServers := c.GetTimezoneSettings()
 	if timezone != nil {
 		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone}))
-	} else {
-		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: imageConfig.Timezone}))
+	} else if imageConfig.Timezone != nil {
+		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *imageConfig.Timezone}))
 	}
 
 	if len(ntpServers) > 0 {
@@ -97,12 +97,16 @@ func osPipeline(t *imageType,
 	}
 
 	if services := c.GetServices(); services != nil || imageConfig.EnabledServices != nil ||
-		imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != "" {
+		imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != nil {
+		defaultTarget := ""
+		if imageConfig.DefaultTarget != nil {
+			defaultTarget = *imageConfig.DefaultTarget
+		}
 		p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(
 			imageConfig.EnabledServices,
 			imageConfig.DisabledServices,
 			services,
-			imageConfig.DefaultTarget,
+			defaultTarget,
 		)))
 	}
 
@@ -292,7 +296,8 @@ func osPipeline(t *imageType,
 		p.AddStage(bootloader)
 	}
 
-	if !imageConfig.NoSElinux {
+	// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
+	if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
 		p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false)))
 	}
 
diff --git a/internal/distro/rhel7/qcow2.go b/internal/distro/rhel7/qcow2.go
index 3e2b61a53..da120c25d 100644
--- a/internal/distro/rhel7/qcow2.go
+++ b/internal/distro/rhel7/qcow2.go
@@ -103,7 +103,7 @@ var qcow2ImgType = imageType{
 		osPkgsKey:    qcow2CommonPackageSet,
 	},
 	defaultImageConfig: &distro.ImageConfig{
-		DefaultTarget: "multi-user.target",
+		DefaultTarget: common.StringToPtr("multi-user.target"),
 		Sysconfig: []*osbuild.SysconfigStageOptions{
 			{
 				Kernel: &osbuild.SysconfigKernelOptions{
diff --git a/internal/distro/rhel8/distro.go b/internal/distro/rhel8/distro.go
index a1b3891dc..c4a86a722 100644
--- a/internal/distro/rhel8/distro.go
+++ b/internal/distro/rhel8/distro.go
@@ -106,8 +106,8 @@ type distribution struct {
 
 // RHEL-based OS image configuration defaults
 var defaultDistroImageConfig = &distro.ImageConfig{
-	Timezone: "America/New_York",
-	Locale:   "en_US.UTF-8",
+	Timezone: common.StringToPtr("America/New_York"),
+	Locale:   common.StringToPtr("en_US.UTF-8"),
 	Sysconfig: []*osbuild.SysconfigStageOptions{
 		{
 			Kernel: &osbuild.SysconfigKernelOptions{
@@ -939,7 +939,7 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 			RHSMConfig: map[distro.RHSMSubscriptionStatus]*osbuild.RHSMStageOptions{
 				distro.RHSMConfigNoSubscription: {
 					DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
@@ -978,7 +978,7 @@ func newDistro(distroName string) distro.Distro {
 				"sshd",
 				"waagent",
 			},
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 		},
 		kernelOptions:       "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0",
 		bootable:            true,
@@ -1002,8 +1002,8 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Timezone: "Etc/UTC",
-			Locale:   "en_US.UTF-8",
+			Timezone: common.StringToPtr("Etc/UTC"),
+			Locale:   common.StringToPtr("en_US.UTF-8"),
 			GPGKeyFiles: []string{
 				"/etc/pki/rpm-gpg/RPM-GPG-KEY-microsoft-azure-release",
 				"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
@@ -1179,7 +1179,7 @@ func newDistro(distroName string) distro.Distro {
 					},
 				},
 			},
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 		},
 		kernelOptions:       "ro crashkernel=auto console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300",
 		bootable:            true,
@@ -1235,7 +1235,7 @@ func newDistro(distroName string) distro.Distro {
 
 	// default EC2 images config (common for all architectures)
 	defaultEc2ImageConfig := &distro.ImageConfig{
-		Timezone: "UTC",
+		Timezone: common.StringToPtr("UTC"),
 		TimeSynchronization: &osbuild.ChronyStageOptions{
 			Servers: []osbuild.ChronyConfigServer{
 				{
@@ -1266,7 +1266,7 @@ func newDistro(distroName string) distro.Distro {
 			"cloud-final",
 			"reboot.target",
 		},
-		DefaultTarget: "multi-user.target",
+		DefaultTarget: common.StringToPtr("multi-user.target"),
 		Sysconfig: []*osbuild.SysconfigStageOptions{
 			{
 				Kernel: &osbuild.SysconfigKernelOptions{
@@ -1673,7 +1673,7 @@ func newDistro(distroName string) distro.Distro {
 
 	// GCE BYOS image
 	defaultGceByosImageConfig := &distro.ImageConfig{
-		Timezone: "UTC",
+		Timezone: common.StringToPtr("UTC"),
 		TimeSynchronization: &osbuild.ChronyStageOptions{
 			Timeservers: []string{"metadata.google.internal"},
 		},
@@ -1689,8 +1689,8 @@ func newDistro(distroName string) distro.Distro {
 			"sshd-keygen@",
 			"reboot.target",
 		},
-		DefaultTarget: "multi-user.target",
-		Locale:        "en_US.UTF-8",
+		DefaultTarget: common.StringToPtr("multi-user.target"),
+		Locale:        common.StringToPtr("en_US.UTF-8"),
 		Keyboard: &osbuild.KeymapStageOptions{
 			Keymap: "us",
 		},
diff --git a/internal/distro/rhel8/pipelines.go b/internal/distro/rhel8/pipelines.go
index 02fc55216..9e664ddf6 100644
--- a/internal/distro/rhel8/pipelines.go
+++ b/internal/distro/rhel8/pipelines.go
@@ -422,7 +422,7 @@ func osPipeline(t *imageType,
 	rpmOptions := osbuild.NewRPMStageOptions(repos)
 	rpmOptions.GPGKeysFromTree = imageConfig.GPGKeyFiles
 
-	if imageConfig.ExcludeDocs {
+	if imageConfig.ExcludeDocs != nil && *imageConfig.ExcludeDocs {
 		if rpmOptions.Exclude == nil {
 			rpmOptions.Exclude = &osbuild.Exclude{}
 		}
@@ -461,8 +461,8 @@ func osPipeline(t *imageType,
 	language, keyboard := c.GetPrimaryLocale()
 	if language != nil {
 		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language}))
-	} else {
-		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: imageConfig.Locale}))
+	} else if imageConfig.Locale != nil {
+		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *imageConfig.Locale}))
 	}
 	if keyboard != nil {
 		p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard}))
@@ -477,8 +477,8 @@ func osPipeline(t *imageType,
 	timezone, ntpServers := c.GetTimezoneSettings()
 	if timezone != nil {
 		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone}))
-	} else {
-		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: imageConfig.Timezone}))
+	} else if imageConfig.Timezone != nil {
+		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *imageConfig.Timezone}))
 	}
 
 	if len(ntpServers) > 0 {
@@ -514,12 +514,16 @@ func osPipeline(t *imageType,
 	}
 
 	if services := c.GetServices(); services != nil || imageConfig.EnabledServices != nil ||
-		imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != "" {
+		imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != nil {
+		defaultTarget := ""
+		if imageConfig.DefaultTarget != nil {
+			defaultTarget = *imageConfig.DefaultTarget
+		}
 		p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(
 			imageConfig.EnabledServices,
 			imageConfig.DisabledServices,
 			services,
-			imageConfig.DefaultTarget,
+			defaultTarget,
 		)))
 	}
 
@@ -690,7 +694,8 @@ func osPipeline(t *imageType,
 		p.AddStage(osbuild.NewOscapRemediationStage(remediationOptions))
 	}
 
-	if !imageConfig.NoSElinux {
+	// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
+	if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
 		p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false)))
 	}
 
diff --git a/internal/distro/rhel9/distro.go b/internal/distro/rhel9/distro.go
index 6df221757..fb1382feb 100644
--- a/internal/distro/rhel9/distro.go
+++ b/internal/distro/rhel9/distro.go
@@ -78,8 +78,8 @@ type distribution struct {
 
 // RHEL-based OS image configuration defaults
 var defaultDistroImageConfig = &distro.ImageConfig{
-	Timezone: "America/New_York",
-	Locale:   "C.UTF-8",
+	Timezone: common.StringToPtr("America/New_York"),
+	Locale:   common.StringToPtr("C.UTF-8"),
 	Sysconfig: []*osbuild.SysconfigStageOptions{
 		{
 			Kernel: &osbuild.SysconfigKernelOptions{
@@ -745,7 +745,7 @@ func newDistro(distroName string) distro.Distro {
 			buildPkgsKey: edgeRawImageBuildPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale: "en_US.UTF-8",
+			Locale: common.StringToPtr("en_US.UTF-8"),
 		},
 		defaultSize:         10 * GigaByte,
 		rpmOstree:           true,
@@ -779,7 +779,7 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale:          "en_US.UTF-8",
+			Locale:          common.StringToPtr("en_US.UTF-8"),
 			EnabledServices: edgeServices,
 		},
 		rpmOstree:        true,
@@ -833,7 +833,7 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 			RHSMConfig: map[distro.RHSMSubscriptionStatus]*osbuild.RHSMStageOptions{
 				distro.RHSMConfigNoSubscription: {
 					DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
@@ -868,12 +868,12 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale: "en_US.UTF-8",
+			Locale: common.StringToPtr("en_US.UTF-8"),
 			EnabledServices: []string{
 				"sshd",
 				"waagent",
 			},
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 		},
 		kernelOptions:       "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0",
 		bootable:            true,
@@ -894,8 +894,8 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey:    azureRhuiCommonPackageSet,
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Timezone: "Etc/UTC",
-			Locale:   "en_US.UTF-8",
+			Timezone: common.StringToPtr("Etc/UTC"),
+			Locale:   common.StringToPtr("en_US.UTF-8"),
 			GPGKeyFiles: []string{
 				"/etc/pki/rpm-gpg/RPM-GPG-KEY-microsoft-azure-release",
 				"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
@@ -1058,7 +1058,7 @@ func newDistro(distroName string) distro.Distro {
 					},
 				},
 			},
-			DefaultTarget: "multi-user.target",
+			DefaultTarget: common.StringToPtr("multi-user.target"),
 		},
 		kernelOptions:       "ro console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300",
 		bootable:            true,
@@ -1082,7 +1082,7 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale: "en_US.UTF-8",
+			Locale: common.StringToPtr("en_US.UTF-8"),
 		},
 		kernelOptions:       "ro net.ifnames=0",
 		bootable:            true,
@@ -1106,7 +1106,7 @@ func newDistro(distroName string) distro.Distro {
 			osPkgsKey: {osPkgsKey, blueprintPkgsKey},
 		},
 		defaultImageConfig: &distro.ImageConfig{
-			Locale: "en_US.UTF-8",
+			Locale: common.StringToPtr("en_US.UTF-8"),
 		},
 		kernelOptions:       "ro net.ifnames=0",
 		bootable:            true,
@@ -1120,8 +1120,8 @@ func newDistro(distroName string) distro.Distro {
 
 	// default EC2 images config (common for all architectures)
 	defaultEc2ImageConfig := &distro.ImageConfig{
-		Locale:   "en_US.UTF-8",
-		Timezone: "UTC",
+		Locale:   common.StringToPtr("en_US.UTF-8"),
+		Timezone: common.StringToPtr("UTC"),
 		TimeSynchronization: &osbuild.ChronyStageOptions{
 			Servers: []osbuild.ChronyConfigServer{
 				{
@@ -1153,7 +1153,7 @@ func newDistro(distroName string) distro.Distro {
 			"reboot.target",
 			"tuned",
 		},
-		DefaultTarget: "multi-user.target",
+		DefaultTarget: common.StringToPtr("multi-user.target"),
 		Sysconfig: []*osbuild.SysconfigStageOptions{
 			{
 				Kernel: &osbuild.SysconfigKernelOptions{
@@ -1559,7 +1559,7 @@ func newDistro(distroName string) distro.Distro {
 	}
 
 	defaultGceImageConfig := &distro.ImageConfig{
-		Timezone: "UTC",
+		Timezone: common.StringToPtr("UTC"),
 		TimeSynchronization: &osbuild.ChronyStageOptions{
 			Timeservers: []string{"metadata.google.internal"},
 		},
@@ -1575,8 +1575,8 @@ func newDistro(distroName string) distro.Distro {
 			"sshd-keygen@",
 			"reboot.target",
 		},
-		DefaultTarget: "multi-user.target",
-		Locale:        "en_US.UTF-8",
+		DefaultTarget: common.StringToPtr("multi-user.target"),
+		Locale:        common.StringToPtr("en_US.UTF-8"),
 		Keyboard: &osbuild.KeymapStageOptions{
 			Keymap: "us",
 		},
diff --git a/internal/distro/rhel9/pipelines.go b/internal/distro/rhel9/pipelines.go
index 1102c9e1b..4e7a67203 100644
--- a/internal/distro/rhel9/pipelines.go
+++ b/internal/distro/rhel9/pipelines.go
@@ -414,7 +414,7 @@ func osPipeline(t *imageType,
 	rpmOptions := osbuild.NewRPMStageOptions(repos)
 	rpmOptions.GPGKeysFromTree = imageConfig.GPGKeyFiles
 
-	if imageConfig.ExcludeDocs {
+	if imageConfig.ExcludeDocs != nil && *imageConfig.ExcludeDocs {
 		if rpmOptions.Exclude == nil {
 			rpmOptions.Exclude = &osbuild.Exclude{}
 		}
@@ -453,8 +453,8 @@ func osPipeline(t *imageType,
 	language, keyboard := c.GetPrimaryLocale()
 	if language != nil {
 		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language}))
-	} else {
-		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: imageConfig.Locale}))
+	} else if imageConfig.Locale != nil {
+		p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *imageConfig.Locale}))
 	}
 	if keyboard != nil {
 		p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard}))
@@ -469,8 +469,8 @@ func osPipeline(t *imageType,
 	timezone, ntpServers := c.GetTimezoneSettings()
 	if timezone != nil {
 		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone}))
-	} else {
-		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: imageConfig.Timezone}))
+	} else if imageConfig.Timezone != nil {
+		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *imageConfig.Timezone}))
 	}
 
 	if len(ntpServers) > 0 {
@@ -506,12 +506,16 @@ func osPipeline(t *imageType,
 	}
 
 	if services := c.GetServices(); services != nil || imageConfig.EnabledServices != nil ||
-		imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != "" {
+		imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != nil {
+		defaultTarget := ""
+		if imageConfig.DefaultTarget != nil {
+			defaultTarget = *imageConfig.DefaultTarget
+		}
 		p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(
 			imageConfig.EnabledServices,
 			imageConfig.DisabledServices,
 			services,
-			imageConfig.DefaultTarget,
+			defaultTarget,
 		)))
 	}
 
@@ -688,7 +692,8 @@ func osPipeline(t *imageType,
 		p.AddStage(osbuild.NewOscapRemediationStage(remediationOptions))
 	}
 
-	if !imageConfig.NoSElinux {
+	// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
+	if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
 		p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false)))
 	}
 
diff --git a/templates/packer/worker.pkr.hcl b/templates/packer/worker.pkr.hcl
index 9243e1ef0..e2a6b3a2e 100644
--- a/templates/packer/worker.pkr.hcl
+++ b/templates/packer/worker.pkr.hcl
@@ -62,6 +62,10 @@ build {
     source_ami = "ami-0c84d76d81209a0e2"
     ssh_username = "ec2-user"
     instance_type = "c6g.large"
+    aws_polling {
+      delay_seconds = 10
+      max_attempts  = 50
+    }
 
     # Set a name for the resulting AMI.
     ami_name = "${var.image_name}"


Test patch:
diff --git a/internal/distro/image_config_test.go b/internal/distro/image_config_test.go
index bb89f75a6..470f0f3c8 100644
--- a/internal/distro/image_config_test.go
+++ b/internal/distro/image_config_test.go
@@ -18,17 +18,17 @@ func TestImageConfigInheritFrom(t *testing.T) {
 		{
 			name: "inheritance with overridden values",
 			distroConfig: &ImageConfig{
-				Timezone: "America/New_York",
+				Timezone: common.StringToPtr("America/New_York"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Timeservers: []string{"127.0.0.1"},
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 				Sysconfig: []*osbuild.SysconfigStageOptions{
 					{
 						Kernel: &osbuild.SysconfigKernelOptions{
@@ -56,7 +56,7 @@ func TestImageConfigInheritFrom(t *testing.T) {
 				},
 			},
 			imageConfig: &ImageConfig{
-				Timezone: "UTC",
+				Timezone: common.StringToPtr("UTC"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Servers: []osbuild.ChronyConfigServer{
 						{
@@ -71,7 +71,7 @@ func TestImageConfigInheritFrom(t *testing.T) {
 				},
 			},
 			expectedConfig: &ImageConfig{
-				Timezone: "UTC",
+				Timezone: common.StringToPtr("UTC"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Servers: []osbuild.ChronyConfigServer{
 						{
@@ -84,13 +84,13 @@ func TestImageConfigInheritFrom(t *testing.T) {
 					},
 					LeapsecTz: common.StringToPtr(""),
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 				Sysconfig: []*osbuild.SysconfigStageOptions{
 					{
 						Kernel: &osbuild.SysconfigKernelOptions{
@@ -121,91 +121,91 @@ func TestImageConfigInheritFrom(t *testing.T) {
 		{
 			name: "empty image type configuration",
 			distroConfig: &ImageConfig{
-				Timezone: "America/New_York",
+				Timezone: common.StringToPtr("America/New_York"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Timeservers: []string{"127.0.0.1"},
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 			},
 			imageConfig: &ImageConfig{},
 			expectedConfig: &ImageConfig{
-				Timezone: "America/New_York",
+				Timezone: common.StringToPtr("America/New_York"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Timeservers: []string{"127.0.0.1"},
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 			},
 		},
 		{
 			name:         "empty distro configuration",
 			distroConfig: &ImageConfig{},
 			imageConfig: &ImageConfig{
-				Timezone: "America/New_York",
+				Timezone: common.StringToPtr("America/New_York"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Timeservers: []string{"127.0.0.1"},
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 			},
 			expectedConfig: &ImageConfig{
-				Timezone: "America/New_York",
+				Timezone: common.StringToPtr("America/New_York"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Timeservers: []string{"127.0.0.1"},
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 			},
 		},
 		{
 			name:         "empty distro configuration",
 			distroConfig: nil,
 			imageConfig: &ImageConfig{
-				Timezone: "America/New_York",
+				Timezone: common.StringToPtr("America/New_York"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Timeservers: []string{"127.0.0.1"},
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 			},
 			expectedConfig: &ImageConfig{
-				Timezone: "America/New_York",
+				Timezone: common.StringToPtr("America/New_York"),
 				TimeSynchronization: &osbuild.ChronyStageOptions{
 					Timeservers: []string{"127.0.0.1"},
 				},
-				Locale: "en_US.UTF-8",
+				Locale: common.StringToPtr("en_US.UTF-8"),
 				Keyboard: &osbuild.KeymapStageOptions{
 					Keymap: "us",
 				},
 				EnabledServices:  []string{"sshd"},
 				DisabledServices: []string{"named"},
-				DefaultTarget:    "multi-user.target",
+				DefaultTarget:    common.StringToPtr("multi-user.target"),
 			},
 		},
 	}