go.mod: update to osbuild/images@v0.83.0

This commit is contained in:
Sanne Raymaekers 2024-09-05 14:45:55 +02:00
parent 5b4bbf2e87
commit 387f971bf0
15 changed files with 391 additions and 154 deletions

2
go.mod
View file

@ -46,7 +46,7 @@ require (
github.com/labstack/gommon v0.4.2
github.com/openshift-online/ocm-sdk-go v0.1.438
github.com/oracle/oci-go-sdk/v54 v54.0.0
github.com/osbuild/images v0.82.0
github.com/osbuild/images v0.83.0
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d
github.com/osbuild/pulp-client v0.1.0
github.com/prometheus/client_golang v1.20.2

4
go.sum
View file

@ -510,8 +510,8 @@ github.com/openshift-online/ocm-sdk-go v0.1.438 h1:tsLCCUzbLCTL4RZG02y9RuopmGCXp
github.com/openshift-online/ocm-sdk-go v0.1.438/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y=
github.com/oracle/oci-go-sdk/v54 v54.0.0 h1:CDLjeSejv2aDpElAJrhKpi6zvT/zhZCZuXchUUZ+LS4=
github.com/oracle/oci-go-sdk/v54 v54.0.0/go.mod h1:+t+yvcFGVp+3ZnztnyxqXfQDsMlq8U25faBLa+mqCMc=
github.com/osbuild/images v0.82.0 h1:bWfcGHHQR6pYZnv4jAxmLWxEkw669Zb6C2ADcyuf49g=
github.com/osbuild/images v0.82.0/go.mod h1:1kJyvTtEbJfRv00phwd9Dlkai4/V05JhNACglxFTxS8=
github.com/osbuild/images v0.83.0 h1:TFz9/nlueUK0dI3HpRCeUuT+2CeNTnejR/vGlzel1lE=
github.com/osbuild/images v0.83.0/go.mod h1:1kJyvTtEbJfRv00phwd9Dlkai4/V05JhNACglxFTxS8=
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d h1:r9BFPDv0uuA9k1947Jybcxs36c/pTywWS1gjeizvtcQ=
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d/go.mod h1:zR1iu/hOuf+OQNJlk70tju9IqzzM4ycq0ectkFBm94U=
github.com/osbuild/pulp-client v0.1.0 h1:L0C4ezBJGTamN3BKdv+rKLuq/WxXJbsFwz/Hj7aEmJ8=

View file

@ -145,7 +145,7 @@ func osCustomizations(
}
if t.IsRHEL() && options.Facts != nil {
osc.FactAPIType = &options.Facts.APIType
osc.RHSMFacts = options.Facts
}
var err error
@ -467,6 +467,7 @@ func EdgeInstallerImage(workload workload.Workload,
img.Platform = t.platform
img.ExtraBasePackages = packageSets[InstallerPkgsKey]
img.Subscription = options.Subscription
if t.Arch().Distro().Releasever() == "8" {
// NOTE: RHEL 8 only supports the older Anaconda configs

View file

@ -96,15 +96,6 @@ func baseEc2ImageConfig() *distro.ImageConfig {
},
},
},
// COMPOSER-1807
DracutConf: []*osbuild.DracutConfStageOptions{
{
Filename: "sgdisk.conf",
Config: osbuild.DracutConfigFile{
Install: []string{"sgdisk"},
},
},
},
SystemdUnit: []*osbuild.SystemdUnitStageOptions{
// RHBZ#1822863
{

View file

@ -65,6 +65,9 @@ func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest,
livePipeline.Variant = img.Variant
livePipeline.Biosdevname = (img.Platform.GetArch() == arch.ARCH_X86_64)
// The live installer has SElinux enabled and targeted
livePipeline.SElinux = "targeted"
livePipeline.Checkpoint()
rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, livePipeline)

View file

@ -9,6 +9,7 @@ import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/customizations/anaconda"
"github.com/osbuild/images/pkg/customizations/kickstart"
"github.com/osbuild/images/pkg/customizations/subscription"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
@ -24,6 +25,9 @@ type AnacondaOSTreeInstaller struct {
Kickstart *kickstart.Options
// Subscription options to include
Subscription *subscription.ImageOptions
SquashfsCompression string
ISOLabel string
@ -119,6 +123,12 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest,
// enable ISOLinux on x86_64 only
isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64
var subscriptionPipeline *manifest.Subscription
if img.Subscription != nil {
// pipeline that will create subscription service and key file to be copied out
subscriptionPipeline = manifest.NewSubscription(buildPipeline, img.Subscription)
}
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(buildPipeline, anacondaPipeline, rootfsImagePipeline, bootTreePipeline)
isoTreePipeline.PartitionTable = efiBootPartitionTable(rng)
isoTreePipeline.Release = img.Release
@ -132,6 +142,7 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest,
if img.FIPS {
isoTreePipeline.KernelOpts = append(isoTreePipeline.KernelOpts, "fips=1")
}
isoTreePipeline.SubscriptionPipeline = subscriptionPipeline
isoPipeline := manifest.NewISO(buildPipeline, isoTreePipeline, img.ISOLabel)
isoPipeline.SetFilename(img.Filename)

View file

@ -84,6 +84,11 @@ type AnacondaInstaller struct {
// Uses the old, deprecated, Anaconda config option "kickstart-modules".
// Only for RHEL 8.
UseLegacyAnacondaConfig bool
// SELinux policy, when set it enables the labeling of the installer
// tree with the selected profile and selects the required package
// for depsolving
SElinux string
}
func NewAnacondaInstaller(installerType AnacondaInstallerType,
@ -159,14 +164,24 @@ func (p *AnacondaInstaller) getBuildPackages(Distro) []string {
)
}
if p.SElinux != "" {
packages = append(packages, "policycoreutils", fmt.Sprintf("selinux-policy-%s", p.SElinux))
}
return packages
}
func (p *AnacondaInstaller) getPackageSetChain(Distro) []rpmmd.PackageSet {
packages := p.anacondaBootPackageSet()
if p.Biosdevname {
packages = append(packages, "biosdevname")
}
if p.SElinux != "" {
packages = append(packages, fmt.Sprintf("selinux-policy-%s", p.SElinux))
}
return []rpmmd.PackageSet{
{
Include: append(packages, p.ExtraPackages...),
@ -306,6 +321,13 @@ func (p *AnacondaInstaller) payloadStages() []*osbuild.Stage {
stages = append(stages, osbuild.NewSELinuxConfigStage(&osbuild.SELinuxConfigStageOptions{State: osbuild.SELinuxStatePermissive}))
// SElinux is not supported on the non-live-installers (see the previous
// stage setting SELinux to permissive. It's an error to set it to anything
// that isn't an empty string
if p.SElinux != "" {
panic("payload installers do not support SELinux policies")
}
if p.InteractiveDefaults != nil {
var ksUsers []users.User
var ksGroups []users.Group
@ -370,7 +392,11 @@ func (p *AnacondaInstaller) liveStages() []*osbuild.Stage {
dracutOptions.AddDrivers = p.AdditionalDrivers
stages = append(stages, osbuild.NewDracutStage(dracutOptions))
stages = append(stages, osbuild.NewSELinuxConfigStage(&osbuild.SELinuxConfigStageOptions{State: osbuild.SELinuxStatePermissive}))
if p.SElinux != "" {
stages = append(stages, osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{
FileContexts: fmt.Sprintf("etc/selinux/%s/contexts/files/file_contexts", p.SElinux),
}))
}
return stages
}

View file

@ -3,6 +3,7 @@ package manifest
import (
"fmt"
"path"
"path/filepath"
"sort"
"strings"
@ -58,6 +59,10 @@ type AnacondaInstallerISOTree struct {
Kickstart *kickstart.Options
Files []*fsnode.File
// Pipeline object where subscription-related files are created for copying
// onto the ISO.
SubscriptionPipeline *Subscription
}
func NewAnacondaInstallerISOTree(buildPipeline Build, anacondaPipeline *AnacondaInstaller, rootfsPipeline *ISORootfsImg, bootTreePipeline *EFIBootTree) *AnacondaInstallerISOTree {
@ -446,7 +451,7 @@ func (p *AnacondaInstallerISOTree) bootcInstallerKickstartStages() []*osbuild.St
if err != nil {
panic(err)
}
p.Files = []*fsnode.File{kickstartFile}
p.Files = append(p.Files, kickstartFile)
return append(stages, osbuild.GenFileNodesStages(p.Files)...)
}
@ -516,8 +521,7 @@ bootc switch --mutate-in-place --transport %s %s
panic(err)
}
p.Files = []*fsnode.File{kickstartFile}
p.Files = append(p.Files, kickstartFile)
return append(stages, osbuild.GenFileNodesStages(p.Files)...)
}
@ -570,9 +574,7 @@ func (p *AnacondaInstallerISOTree) makeKickstartStages(stageOptions *osbuild.Kic
panic(err)
}
p.Files = []*fsnode.File{kickstartFile}
stages = append(stages, osbuild.GenFileNodesStages(p.Files)...)
p.Files = append(p.Files, kickstartFile)
}
}
@ -610,7 +612,44 @@ func (p *AnacondaInstallerISOTree) makeKickstartStages(stageOptions *osbuild.Kic
stages = append(stages, osbuild.NewKickstartStage(stageOptions))
hardcodedKickstartBits := makeKickstartSudoersPost(kickstartOptions.SudoNopasswd)
hardcodedKickstartBits := ""
hardcodedKickstartBits += makeKickstartSudoersPost(kickstartOptions.SudoNopasswd)
if p.SubscriptionPipeline != nil {
subscriptionPath := "/subscription"
stages = append(stages, osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{Paths: []osbuild.MkdirStagePath{{Path: subscriptionPath, Parents: true, ExistOk: true}}}))
inputName := "subscription-tree"
copyInputs := osbuild.NewPipelineTreeInputs(inputName, p.SubscriptionPipeline.Name())
copyOptions := &osbuild.CopyStageOptions{}
copyOptions.Paths = append(copyOptions.Paths,
osbuild.CopyStagePath{
From: fmt.Sprintf("input://%s/", inputName),
To: fmt.Sprintf("tree://%s/", subscriptionPath),
},
)
stages = append(stages, osbuild.NewCopyStageSimple(copyOptions, copyInputs))
systemPath := "/mnt/sysimage"
if p.ostreeCommitSpec != nil || p.containerSpec != nil {
// ostree based system: use /mnt/sysroot instead
systemPath = "/mnt/sysroot"
}
hardcodedKickstartBits += makeKickstartSubscriptionPost(subscriptionPath, systemPath)
// include a readme file on the ISO in the subscription path to explain what it's for
subscriptionReadme, err := fsnode.NewFile(
filepath.Join(subscriptionPath, "README"),
nil, nil, nil,
[]byte(`Subscription services and credentials
This directory contains files necessary for registering the system on first boot after installation. These files are copied to the installed system and services are enabled to activate the subscription on boot.`),
)
if err != nil {
panic(err)
}
p.Files = append(p.Files, subscriptionReadme)
}
if hardcodedKickstartBits != "" {
// Because osbuild core only supports a subset of options,
// we append to the base here with hardcoded wheel group with NOPASSWD option
@ -619,10 +658,10 @@ func (p *AnacondaInstallerISOTree) makeKickstartStages(stageOptions *osbuild.Kic
panic(err)
}
p.Files = []*fsnode.File{kickstartFile}
stages = append(stages, osbuild.GenFileNodesStages(p.Files)...)
p.Files = append(p.Files, kickstartFile)
}
stages = append(stages, osbuild.GenFileNodesStages(p.Files)...)
return stages
}
@ -660,3 +699,17 @@ restorecon -rvF /etc/sudoers.d
return fmt.Sprintf(kickstartSudoersPost, strings.Join(entries, "\n"))
}
func makeKickstartSubscriptionPost(source, dest string) string {
// we need to use --nochroot so the command can access files on the ISO
fullSourcePath := filepath.Join("/run/install/repo", source, "etc/*")
kickstartSubscriptionPost := `
%%post --nochroot
cp -r %s %s
%%end
%%post
systemctl enable osbuild-subscription-register.service
%%end
`
return fmt.Sprintf(kickstartSubscriptionPost, fullSourcePath, dest)
}

View file

@ -5,6 +5,8 @@ import (
"path/filepath"
"strings"
"github.com/google/uuid"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/internal/environment"
"github.com/osbuild/images/internal/workload"
@ -124,7 +126,6 @@ type OSCustomizations struct {
UdevRules *osbuild.UdevRulesStageOptions
WSLConfig *osbuild.WSLConfStageOptions
LeapSecTZ *string
FactAPIType *facts.APIType
Presets []osbuild.Preset
ContainersStorage *string
@ -134,6 +135,7 @@ type OSCustomizations struct {
Subscription *subscription.ImageOptions
// The final RHSM config to be applied to the image
RHSMConfig *subscription.RHSMConfig
RHSMFacts *facts.ImageOptions
// Custom directories and files to create in the image
Directories []*fsnode.Directory
@ -424,6 +426,13 @@ func (p *OS) serialize() osbuild.Pipeline {
if p.OSTreeRef != "" {
rpmOptions.OSTreeBooted = common.ToPtr(true)
rpmOptions.DBPath = "/usr/share/rpm"
// The dracut-config-rescue package will create a rescue kernel when
// installed. This creates an issue with ostree-based images because
// rpm-ostree requires that only one kernel exists in the image.
// Disabling dracut for ostree-based systems resolves this issue.
// Dracut will be run by rpm-ostree itself while composing the image.
// https://github.com/osbuild/images/issues/624
rpmOptions.DisableDracut = true
}
pipeline.AddStage(osbuild.NewRPMStage(rpmOptions, osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs)))
@ -580,74 +589,15 @@ func (p *OS) serialize() osbuild.Pipeline {
pipeline.AddStage(osbuild.NewPwqualityConfStage(p.PwQuality))
}
// If subscription settings are included there are 3 possible setups:
// - Register the system with rhc and enable Insights
// - Register with subscription-manager, no Insights or rhc
// - Register with subscription-manager and enable Insights, no rhc
if p.Subscription != nil {
// Write a key file that will contain the org ID and activation key to be sourced in the systemd service.
// The file will also act as the ConditionFirstBoot file.
subkeyFilepath := "/etc/osbuild-subscription-register.env"
subkeyContent := fmt.Sprintf("ORG_ID=%s\nACTIVATION_KEY=%s", p.Subscription.Organization, p.Subscription.ActivationKey)
if subkeyFile, err := fsnode.NewFile(subkeyFilepath, nil, "root", "root", []byte(subkeyContent)); err == nil {
p.Files = append(p.Files, subkeyFile)
} else {
subStage, subDirs, subFiles, subServices, err := subscriptionService(*p.Subscription, &subscriptionServiceOptions{InsightsOnBoot: p.OSTreeRef != ""})
if err != nil {
panic(err)
}
var commands []string
if p.Subscription.Rhc {
// TODO: replace org ID and activation key with env vars
// Use rhc for registration instead of subscription manager
commands = []string{fmt.Sprintf("/usr/bin/rhc connect --organization=${ORG_ID} --activation-key=${ACTIVATION_KEY} --server %s", p.Subscription.ServerUrl)}
// insights-client creates the .gnupg directory during boot process, and is labeled incorrectly
commands = append(commands, "restorecon -R /root/.gnupg")
// execute the rhc post install script as the selinuxenabled check doesn't work in the buildroot container
commands = append(commands, "/usr/sbin/semanage permissive --add rhcd_t")
if p.OSTreeRef != "" {
p.runInsightsClientOnBoot()
}
} else {
commands = []string{fmt.Sprintf("/usr/sbin/subscription-manager register --org=${ORG_ID} --activationkey=${ACTIVATION_KEY} --serverurl %s --baseurl %s", p.Subscription.ServerUrl, p.Subscription.BaseUrl)}
// Insights is optional when using subscription-manager
if p.Subscription.Insights {
commands = append(commands, "/usr/bin/insights-client --register")
// insights-client creates the .gnupg directory during boot process, and is labeled incorrectly
commands = append(commands, "restorecon -R /root/.gnupg")
if p.OSTreeRef != "" {
p.runInsightsClientOnBoot()
}
}
}
commands = append(commands, fmt.Sprintf("/usr/bin/rm %s", subkeyFilepath))
subscribeServiceFile := "osbuild-subscription-register.service"
regServiceStageOptions := &osbuild.SystemdUnitCreateStageOptions{
Filename: subscribeServiceFile,
UnitType: "system",
UnitPath: osbuild.Usr,
Config: osbuild.SystemdServiceUnit{
Unit: &osbuild.Unit{
Description: "First-boot service for registering with Red Hat subscription manager and/or insights",
ConditionPathExists: []string{subkeyFilepath},
Wants: []string{"network-online.target"},
After: []string{"network-online.target"},
},
Service: &osbuild.Service{
Type: osbuild.Oneshot,
RemainAfterExit: false,
ExecStart: commands,
EnvironmentFile: []string{subkeyFilepath},
},
Install: &osbuild.Install{
WantedBy: []string{"default.target"},
},
},
}
pipeline.AddStage(osbuild.NewSystemdUnitCreateStage(regServiceStageOptions))
p.EnabledServices = append(p.EnabledServices, subscribeServiceFile)
pipeline.AddStage(subStage)
p.Directories = append(p.Directories, subDirs...)
p.Files = append(p.Files, subFiles...)
p.EnabledServices = append(p.EnabledServices, subServices...)
}
if p.RHSMConfig != nil {
@ -740,11 +690,21 @@ func (p *OS) serialize() osbuild.Pipeline {
pipeline.AddStage(bootloader)
}
if p.FactAPIType != nil {
if p.RHSMFacts != nil {
rhsmFacts := osbuild.RHSMFacts{
ApiType: p.RHSMFacts.APIType.String(),
}
if p.RHSMFacts.OpenSCAPProfileID != "" {
rhsmFacts.OpenSCAPProfileID = p.RHSMFacts.OpenSCAPProfileID
}
if p.RHSMFacts.CompliancePolicyID != uuid.Nil {
rhsmFacts.CompliancePolicyID = p.RHSMFacts.CompliancePolicyID.String()
}
pipeline.AddStage(osbuild.NewRHSMFactsStage(&osbuild.RHSMFactsStageOptions{
Facts: osbuild.RHSMFacts{
ApiType: p.FactAPIType.String(),
},
Facts: rhsmFacts,
}))
}
@ -919,43 +879,3 @@ func (p *OS) getInline() []string {
return inlineData
}
// For ostree-based systems, creates a drop-in file for the insights-client
// service to run on boot and enables the service. This is only meant for
// ostree-based systems.
func (p *OS) runInsightsClientOnBoot() {
// Insights-client collection must occur at boot time so
// that the current ostree commit hash can be reflected
// after upgrade. Otherwise, the upgrade shows as failed in
// the console UI.
// Add a drop-in file that enables insights-client.service to
// run on successful boot.
// See https://issues.redhat.com/browse/HMS-4031
//
// NOTE(akoutsou): drop-in files can normally be created with the
// org.osbuild.systemd.unit stage but the stage doesn't support
// all the options we need. This is a temporary workaround
// until we get the stage updated to support everything we need.
icDropinFilepath, icDropinContents := insightsClientDropin()
if icDropinDirectory, err := fsnode.NewDirectory(filepath.Dir(icDropinFilepath), nil, "root", "root", true); err == nil {
p.Directories = append(p.Directories, icDropinDirectory)
}
if icDropinFile, err := fsnode.NewFile(icDropinFilepath, nil, "root", "root", []byte(icDropinContents)); err == nil {
p.Files = append(p.Files, icDropinFile)
} else {
panic(err)
}
// Enable the service now that it's "enable-able"
p.EnabledServices = append(p.EnabledServices, "insights-client.service")
}
// Filename and contents for the insights-client service drop-in.
// This is a temporary workaround until the org.osbuild.systemd.unit stage
// gains support for all the options we need.
func insightsClientDropin() (string, string) {
return "/etc/systemd/system/insights-client.service.d/override.conf", `[Unit]
Requisite=greenboot-healthcheck.service
After=network-online.target greenboot-healthcheck.service osbuild-first-boot.service
[Install]
WantedBy=multi-user.target`
}

View file

@ -521,7 +521,7 @@ func createMountpointService(serviceName string, mountpoints []string) *osbuild.
After: []string{"ostree-remount.service"},
}
service := osbuild.Service{
Type: osbuild.Oneshot,
Type: osbuild.OneshotServiceType,
RemainAfterExit: false,
// compatibility with composefs, will require transient rootfs to be enabled too.
ExecStartPre: []string{"/bin/sh -c \"if grep -Uq composefs /run/ostree-booted; then echo 'Warning: composefs enabled! ensure transient rootfs is enabled too.'; else chattr -i /; fi\""},
@ -547,7 +547,7 @@ func createMountpointService(serviceName string, mountpoints []string) *osbuild.
}
options := osbuild.SystemdUnitCreateStageOptions{
Filename: serviceName,
UnitPath: osbuild.Etc,
UnitPath: osbuild.EtcUnitPath,
UnitType: osbuild.System,
Config: osbuild.SystemdServiceUnit{
Unit: &unit,

View file

@ -0,0 +1,223 @@
package manifest
import (
"fmt"
"path/filepath"
"github.com/osbuild/images/pkg/customizations/fsnode"
"github.com/osbuild/images/pkg/customizations/subscription"
"github.com/osbuild/images/pkg/osbuild"
)
type Subscription struct {
Base
Subscription *subscription.ImageOptions
// Custom directories and files to create in the pipeline
Directories []*fsnode.Directory
Files []*fsnode.File
}
// NewSubscription creates a new subscription pipeline for creating files
// required to register a system on first boot.
// The pipeline is intended to be used to create the files necessary for
// registering a system, but outside the OS tree, so they can be copied to
// other locations in the tree after they're created (for example, to an ISO).
func NewSubscription(buildPipeline Build, subOptions *subscription.ImageOptions) *Subscription {
name := "subscription"
p := &Subscription{
Base: NewBase(name, buildPipeline),
Subscription: subOptions,
}
buildPipeline.addDependent(p)
return p
}
func (p *Subscription) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
if p.Subscription != nil {
serviceDir, err := fsnode.NewDirectory("/etc/systemd/system", nil, nil, nil, true)
if err != nil {
panic(err)
}
p.Directories = append(p.Directories, serviceDir)
subStage, subDirs, subFiles, _, err := subscriptionService(*p.Subscription, &subscriptionServiceOptions{InsightsOnBoot: true, UnitPath: osbuild.EtcUnitPath})
if err != nil {
panic(err)
}
p.Directories = append(p.Directories, subDirs...)
p.Files = append(p.Files, subFiles...)
pipeline.AddStages(osbuild.GenDirectoryNodesStages(p.Directories)...)
pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...)
pipeline.AddStage(subStage)
}
return pipeline
}
func (p *Subscription) getInline() []string {
inlineData := []string{}
// inline data for custom files
for _, file := range p.Files {
inlineData = append(inlineData, string(file.Data()))
}
return inlineData
}
type subscriptionServiceOptions struct {
// InsightsOnBoot controls whether the insights client service will be
// modified (with a drop-in) to run on boot as well as on a timer.
InsightsOnBoot bool
// UnitPath controls the path where the systemd unit will be created,
// /usr/lib/systemd or /etc/systemd.
UnitPath osbuild.SystemdUnitPath
}
// subscriptionService creates the necessary stage and modifications to the
// pipeline for activating a system on first boot.
//
// If subscription settings are included there are 3 possible setups:
// - Register the system with rhc and enable Insights
// - Register with subscription-manager, no Insights or rhc
// - Register with subscription-manager and enable Insights, no rhc
func subscriptionService(subscriptionOptions subscription.ImageOptions, serviceOptions *subscriptionServiceOptions) (*osbuild.Stage, []*fsnode.Directory, []*fsnode.File, []string, error) {
dirs := make([]*fsnode.Directory, 0)
files := make([]*fsnode.File, 0)
services := make([]string, 0)
insightsOnBoot := false
unitPath := osbuild.UsrUnitPath
if serviceOptions != nil {
insightsOnBoot = serviceOptions.InsightsOnBoot
if serviceOptions.UnitPath != "" {
unitPath = serviceOptions.UnitPath
}
}
// Write a key file that will contain the org ID and activation key to be sourced in the systemd service.
// The file will also act as the ConditionFirstBoot file.
subkeyFilepath := "/etc/osbuild-subscription-register.env"
subkeyContent := fmt.Sprintf("ORG_ID=%s\nACTIVATION_KEY=%s", subscriptionOptions.Organization, subscriptionOptions.ActivationKey)
// NOTE: Ownership is left as nil:nil, which implicitly creates files as
// root:root. Adding an explicit owner requires chroot to run the
// org.osbuild.chown stage, which we can't run in the subscription pipeline
// since it has no packages.
if subkeyFile, err := fsnode.NewFile(subkeyFilepath, nil, nil, nil, []byte(subkeyContent)); err == nil {
files = append(files, subkeyFile)
} else {
return nil, nil, nil, nil, err
}
var commands []string
if subscriptionOptions.Rhc {
// Use rhc for registration instead of subscription manager
commands = []string{fmt.Sprintf("/usr/bin/rhc connect --organization=${ORG_ID} --activation-key=${ACTIVATION_KEY} --server %s", subscriptionOptions.ServerUrl)}
// insights-client creates the .gnupg directory during boot process, and is labeled incorrectly
commands = append(commands, "restorecon -R /root/.gnupg")
// execute the rhc post install script as the selinuxenabled check doesn't work in the buildroot container
commands = append(commands, "/usr/sbin/semanage permissive --add rhcd_t")
if insightsOnBoot {
icDir, icFile, err := runInsightsClientOnBoot()
if err != nil {
return nil, nil, nil, nil, err
}
dirs = append(dirs, icDir)
files = append(files, icFile)
}
} else {
commands = []string{fmt.Sprintf("/usr/sbin/subscription-manager register --org=${ORG_ID} --activationkey=${ACTIVATION_KEY} --serverurl %s --baseurl %s", subscriptionOptions.ServerUrl, subscriptionOptions.BaseUrl)}
// Insights is optional when using subscription-manager
if subscriptionOptions.Insights {
commands = append(commands, "/usr/bin/insights-client --register")
// insights-client creates the .gnupg directory during boot process, and is labeled incorrectly
commands = append(commands, "restorecon -R /root/.gnupg")
if insightsOnBoot {
icDir, icFile, err := runInsightsClientOnBoot()
if err != nil {
return nil, nil, nil, nil, err
}
dirs = append(dirs, icDir)
files = append(files, icFile)
}
}
}
commands = append(commands, fmt.Sprintf("/usr/bin/rm %s", subkeyFilepath))
subscribeServiceFile := "osbuild-subscription-register.service"
regServiceStageOptions := &osbuild.SystemdUnitCreateStageOptions{
Filename: subscribeServiceFile,
UnitType: "system",
UnitPath: unitPath,
Config: osbuild.SystemdServiceUnit{
Unit: &osbuild.Unit{
Description: "First-boot service for registering with Red Hat subscription manager and/or insights",
ConditionPathExists: []string{subkeyFilepath},
Wants: []string{"network-online.target"},
After: []string{"network-online.target"},
},
Service: &osbuild.Service{
Type: osbuild.OneshotServiceType,
RemainAfterExit: false,
ExecStart: commands,
EnvironmentFile: []string{subkeyFilepath},
},
Install: &osbuild.Install{
WantedBy: []string{"default.target"},
},
},
}
services = append(services, subscribeServiceFile)
unitStage := osbuild.NewSystemdUnitCreateStage(regServiceStageOptions)
return unitStage, dirs, files, services, nil
}
// Creates a drop-in file for the insights-client service to run on boot and
// enables the service. This is only meant for ostree-based systems.
func runInsightsClientOnBoot() (*fsnode.Directory, *fsnode.File, error) {
// Insights-client collection must occur at boot time so
// that the current ostree commit hash can be reflected
// after upgrade. Otherwise, the upgrade shows as failed in
// the console UI.
// Add a drop-in file that enables insights-client.service to
// run on successful boot.
// See https://issues.redhat.com/browse/HMS-4031
//
// NOTE(akoutsou): drop-in files can normally be created with the
// org.osbuild.systemd.unit stage but the stage doesn't support
// all the options we need. This is a temporary workaround
// until we get the stage updated to support everything we need.
icDropinFilepath, icDropinContents := insightsClientDropin()
// NOTE: Ownership is left as nil:nil, which implicitly creates files as
// root:root. Adding an explicit owner requires chroot to run the
// org.osbuild.chown stage, which we can't run in the subscription pipeline
// since it has no packages.
icDropinDirectory, err := fsnode.NewDirectory(filepath.Dir(icDropinFilepath), nil, nil, nil, true)
if err != nil {
return nil, nil, err
}
icDropinFile, err := fsnode.NewFile(icDropinFilepath, nil, nil, nil, []byte(icDropinContents))
if err != nil {
return nil, nil, err
}
return icDropinDirectory, icDropinFile, nil
}
// Filename and contents for the insights-client service drop-in.
// This is a temporary workaround until the org.osbuild.systemd.unit stage
// gains support for all the options we need.
func insightsClientDropin() (string, string) {
return "/etc/systemd/system/insights-client.service.d/override.conf", `[Unit]
Requisite=greenboot-healthcheck.service
After=network-online.target greenboot-healthcheck.service osbuild-first-boot.service
[Install]
WantedBy=multi-user.target`
}

View file

@ -5,7 +5,9 @@ type RHSMFactsStageOptions struct {
}
type RHSMFacts struct {
ApiType string `json:"image-builder.osbuild-composer.api-type"`
ApiType string `json:"image-builder.osbuild-composer.api-type"`
OpenSCAPProfileID string `json:"image-builder.insights.openscap-profile-id,omitempty"`
CompliancePolicyID string `json:"image-builder.insights.compliance-policy-id,omitempty"`
}
func (RHSMFactsStageOptions) isStageOptions() {}

View file

@ -5,20 +5,21 @@ import (
"regexp"
)
type serviceType string
type unitPath string
type SystemdServiceType string
type SystemdUnitPath 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"
SimpleServiceType SystemdServiceType = "simple"
ExecServiceType SystemdServiceType = "exec"
ForkingServiceType SystemdServiceType = "forking"
OneshotServiceType SystemdServiceType = "oneshot"
DbusServiceType SystemdServiceType = "dbus"
NotifyServiceType SystemdServiceType = "notify"
NotifyReloadServiceType SystemdServiceType = "notify-reload"
IdleServiceType SystemdServiceType = "idle"
EtcUnitPath SystemdUnitPath = "etc"
UsrUnitPath SystemdUnitPath = "usr"
)
type Unit struct {
@ -33,7 +34,7 @@ type Unit struct {
}
type Service struct {
Type serviceType `json:"Type,omitempty"`
Type SystemdServiceType `json:"Type,omitempty"`
RemainAfterExit bool `json:"RemainAfterExit,omitempty"`
ExecStartPre []string `json:"ExecStartPre,omitempty"`
ExecStopPost []string `json:"ExecStopPost,omitempty"`
@ -56,7 +57,7 @@ type SystemdServiceUnit struct {
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"`
UnitPath SystemdUnitPath `json:"unit-path,omitempty"`
Config SystemdServiceUnit `json:"config"`
}

View file

@ -1,6 +1,10 @@
package facts
import "fmt"
import (
"fmt"
"github.com/google/uuid"
)
type APIType uint64
@ -25,5 +29,7 @@ const (
// The ImageOptions specify things to be stored into the Insights facts
// storage. This mostly relates to how the build of the image was performed.
type ImageOptions struct {
APIType APIType
APIType APIType
OpenSCAPProfileID string
CompliancePolicyID uuid.UUID
}

2
vendor/modules.txt vendored
View file

@ -947,7 +947,7 @@ github.com/oracle/oci-go-sdk/v54/identity
github.com/oracle/oci-go-sdk/v54/objectstorage
github.com/oracle/oci-go-sdk/v54/objectstorage/transfer
github.com/oracle/oci-go-sdk/v54/workrequests
# github.com/osbuild/images v0.82.0
# github.com/osbuild/images v0.83.0
## explicit; go 1.21.0
github.com/osbuild/images/internal/common
github.com/osbuild/images/internal/environment