Remove all the internal package that are now in the github.com/osbuild/images package and vendor it. A new function in internal/blueprint/ converts from an osbuild-composer blueprint to an images blueprint. This is necessary for keeping the blueprint implementation in both packages. In the future, the images package will change the blueprint (and most likely rename it) and it will only be part of the osbuild-composer internals and interface. The Convert() function will be responsible for converting the blueprint into the new configuration object.
786 lines
24 KiB
Go
786 lines
24 KiB
Go
package manifest
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/osbuild/images/internal/common"
|
|
"github.com/osbuild/images/internal/environment"
|
|
"github.com/osbuild/images/internal/fsnode"
|
|
"github.com/osbuild/images/internal/shell"
|
|
"github.com/osbuild/images/internal/users"
|
|
"github.com/osbuild/images/internal/workload"
|
|
"github.com/osbuild/images/pkg/container"
|
|
"github.com/osbuild/images/pkg/disk"
|
|
"github.com/osbuild/images/pkg/osbuild"
|
|
"github.com/osbuild/images/pkg/ostree"
|
|
"github.com/osbuild/images/pkg/platform"
|
|
"github.com/osbuild/images/pkg/rhsm/facts"
|
|
"github.com/osbuild/images/pkg/rpmmd"
|
|
"github.com/osbuild/images/pkg/subscription"
|
|
)
|
|
|
|
// OSCustomizations encapsulates all configuration applied to the base
|
|
// operating system independently of where and how it is integrated and what
|
|
// workload it is running.
|
|
// TODO: move out kernel/bootloader/cloud-init/... to other
|
|
//
|
|
// abstractions, this should ideally only contain things that
|
|
// can always be applied.
|
|
type OSCustomizations struct {
|
|
|
|
// Packages to install in addition to the ones required by the
|
|
// pipeline.
|
|
ExtraBasePackages []string
|
|
|
|
// Packages to exclude from the base package set. This is useful in
|
|
// case of weak dependencies, comps groups, or where multiple packages
|
|
// can satisfy a dependency. Must not conflict with the included base
|
|
// package set.
|
|
ExcludeBasePackages []string
|
|
|
|
// Additional repos to install the base packages from.
|
|
ExtraBaseRepos []rpmmd.RepoConfig
|
|
|
|
// Containers to embed in the image (source specification)
|
|
// TODO: move to workload
|
|
Containers []container.SourceSpec
|
|
|
|
// KernelName indicates that a kernel is installed, and names the kernel
|
|
// package.
|
|
KernelName string
|
|
|
|
// KernelOptionsAppend are appended to the kernel commandline
|
|
KernelOptionsAppend []string
|
|
|
|
// KernelOptionsBootloader controls whether kernel command line options
|
|
// should be specified in the bootloader grubenv configuration. Otherwise
|
|
// they are specified in /etc/kernel/cmdline (default).
|
|
//
|
|
// NB: The kernel options need to be still specified in /etc/default/grub
|
|
// under the GRUB_CMDLINE_LINUX variable. The reason is that it is used by
|
|
// the 10_linux script executed by grub2-mkconfig to override the kernel
|
|
// options in /etc/kernel/cmdline if the file has older timestamp than
|
|
// /etc/default/grub.
|
|
//
|
|
// This should only be used for RHEL 8 and CentOS 8 images that use grub
|
|
// (non s390x). Newer releases (9+) should keep this disabled.
|
|
KernelOptionsBootloader bool
|
|
|
|
GPGKeyFiles []string
|
|
Language string
|
|
Keyboard *string
|
|
X11KeymapLayouts []string
|
|
Hostname string
|
|
Timezone string
|
|
EnabledServices []string
|
|
DisabledServices []string
|
|
DefaultTarget string
|
|
|
|
// SELinux policy, when set it enables the labeling of the tree with the
|
|
// selected profile
|
|
SElinux string
|
|
|
|
SELinuxForceRelabel *bool
|
|
|
|
// Do not install documentation
|
|
ExcludeDocs bool
|
|
|
|
Groups []users.Group
|
|
Users []users.User
|
|
|
|
ShellInit []shell.InitFile
|
|
|
|
// TODO: drop osbuild types from the API
|
|
Firewall *osbuild.FirewallStageOptions
|
|
Grub2Config *osbuild.GRUB2Config
|
|
Sysconfig []*osbuild.SysconfigStageOptions
|
|
SystemdLogind []*osbuild.SystemdLogindStageOptions
|
|
CloudInit []*osbuild.CloudInitStageOptions
|
|
Modprobe []*osbuild.ModprobeStageOptions
|
|
DracutConf []*osbuild.DracutConfStageOptions
|
|
SystemdUnit []*osbuild.SystemdUnitStageOptions
|
|
Authselect *osbuild.AuthselectStageOptions
|
|
SELinuxConfig *osbuild.SELinuxConfigStageOptions
|
|
Tuned *osbuild.TunedStageOptions
|
|
Tmpfilesd []*osbuild.TmpfilesdStageOptions
|
|
PamLimitsConf []*osbuild.PamLimitsConfStageOptions
|
|
Sysctld []*osbuild.SysctldStageOptions
|
|
DNFConfig []*osbuild.DNFConfigStageOptions
|
|
DNFAutomaticConfig *osbuild.DNFAutomaticConfigStageOptions
|
|
YUMConfig *osbuild.YumConfigStageOptions
|
|
YUMRepos []*osbuild.YumReposStageOptions
|
|
SshdConfig *osbuild.SshdConfigStageOptions
|
|
GCPGuestAgentConfig *osbuild.GcpGuestAgentConfigOptions
|
|
AuthConfig *osbuild.AuthconfigStageOptions
|
|
PwQuality *osbuild.PwqualityConfStageOptions
|
|
OpenSCAPConfig *osbuild.OscapRemediationStageOptions
|
|
NTPServers []osbuild.ChronyConfigServer
|
|
WAAgentConfig *osbuild.WAAgentConfStageOptions
|
|
UdevRules *osbuild.UdevRulesStageOptions
|
|
LeapSecTZ *string
|
|
FactAPIType *facts.APIType
|
|
|
|
Subscription *subscription.ImageOptions
|
|
RHSMConfig map[subscription.RHSMStatus]*osbuild.RHSMStageOptions
|
|
|
|
// Custom directories and files to create in the image
|
|
Directories []*fsnode.Directory
|
|
Files []*fsnode.File
|
|
}
|
|
|
|
// OS represents the filesystem tree of the target image. This roughly
|
|
// corresponds to the root filesystem once an instance of the image is running.
|
|
type OS struct {
|
|
Base
|
|
// Customizations to apply to the base OS
|
|
OSCustomizations
|
|
// Environment the system will run in
|
|
Environment environment.Environment
|
|
// Workload to install on top of the base system
|
|
Workload workload.Workload
|
|
// Ref of ostree commit (optional). If empty the tree cannot be in an ostree commit
|
|
OSTreeRef string
|
|
// OSTreeParent source spec (optional). If nil the new commit (if
|
|
// applicable) will have no parent
|
|
OSTreeParent *ostree.SourceSpec
|
|
// Partition table, if nil the tree cannot be put on a partitioned disk
|
|
PartitionTable *disk.PartitionTable
|
|
|
|
// content-related fields
|
|
repos []rpmmd.RepoConfig
|
|
packageSpecs []rpmmd.PackageSpec
|
|
containerSpecs []container.Spec
|
|
ostreeParentSpec *ostree.CommitSpec
|
|
|
|
platform platform.Platform
|
|
kernelVer string
|
|
|
|
// NoBLS configures the image bootloader with traditional menu entries
|
|
// instead of BLS. Required for legacy systems like RHEL 7.
|
|
NoBLS bool
|
|
OSProduct string
|
|
OSVersion string
|
|
OSNick string
|
|
}
|
|
|
|
// NewOS creates a new OS pipeline. build is the build pipeline to use for
|
|
// building the OS pipeline. platform is the target platform for the final
|
|
// image. repos are the repositories to install RPMs from.
|
|
func NewOS(m *Manifest,
|
|
buildPipeline *Build,
|
|
platform platform.Platform,
|
|
repos []rpmmd.RepoConfig) *OS {
|
|
name := "os"
|
|
p := &OS{
|
|
Base: NewBase(m, name, buildPipeline),
|
|
repos: filterRepos(repos, name),
|
|
platform: platform,
|
|
}
|
|
buildPipeline.addDependent(p)
|
|
m.addPipeline(p)
|
|
return p
|
|
}
|
|
|
|
func (p *OS) getPackageSetChain(Distro) []rpmmd.PackageSet {
|
|
packages := p.platform.GetPackages()
|
|
|
|
if p.KernelName != "" {
|
|
packages = append(packages, p.KernelName)
|
|
}
|
|
|
|
// If we have a logical volume we need to include the lvm2 package.
|
|
// OSTree-based images (commit and container) aren't bootable images and
|
|
// don't have partition tables.
|
|
if p.PartitionTable != nil && p.OSTreeRef == "" {
|
|
packages = append(packages, p.PartitionTable.GetBuildPackages()...)
|
|
}
|
|
|
|
if p.Environment != nil {
|
|
packages = append(packages, p.Environment.GetPackages()...)
|
|
}
|
|
|
|
if len(p.NTPServers) > 0 {
|
|
packages = append(packages, "chrony")
|
|
}
|
|
|
|
if p.SElinux != "" {
|
|
packages = append(packages, fmt.Sprintf("selinux-policy-%s", p.SElinux))
|
|
}
|
|
|
|
if p.OpenSCAPConfig != nil {
|
|
packages = append(packages, "openscap-scanner", "scap-security-guide")
|
|
}
|
|
|
|
// Make sure the right packages are included for subscriptions
|
|
// rhc always uses insights, and depends on subscription-manager
|
|
// non-rhc uses subscription-manager and optionally includes Insights
|
|
if p.Subscription != nil {
|
|
packages = append(packages, "subscription-manager")
|
|
if p.Subscription.Rhc {
|
|
packages = append(packages, "rhc", "insights-client", "rhc-worker-playbook")
|
|
} else if p.Subscription.Insights {
|
|
packages = append(packages, "insights-client")
|
|
}
|
|
}
|
|
|
|
osRepos := append(p.repos, p.ExtraBaseRepos...)
|
|
chain := []rpmmd.PackageSet{
|
|
{
|
|
Include: append(packages, p.ExtraBasePackages...),
|
|
Exclude: p.ExcludeBasePackages,
|
|
Repositories: osRepos,
|
|
},
|
|
}
|
|
|
|
if p.Workload != nil {
|
|
workloadPackages := p.Workload.GetPackages()
|
|
if len(workloadPackages) > 0 {
|
|
chain = append(chain, rpmmd.PackageSet{
|
|
Include: workloadPackages,
|
|
Repositories: append(osRepos, p.Workload.GetRepos()...),
|
|
})
|
|
}
|
|
}
|
|
|
|
return chain
|
|
}
|
|
|
|
func (p *OS) getContainerSources() []container.SourceSpec {
|
|
return p.OSCustomizations.Containers
|
|
}
|
|
|
|
func (p *OS) getBuildPackages(distro Distro) []string {
|
|
packages := p.platform.GetBuildPackages()
|
|
if p.PartitionTable != nil {
|
|
packages = append(packages, p.PartitionTable.GetBuildPackages()...)
|
|
}
|
|
packages = append(packages, "rpm")
|
|
if p.OSTreeRef != "" {
|
|
packages = append(packages, "rpm-ostree")
|
|
}
|
|
if p.SElinux != "" {
|
|
packages = append(packages, "policycoreutils", fmt.Sprintf("selinux-policy-%s", p.SElinux))
|
|
}
|
|
if len(p.CloudInit) > 0 {
|
|
switch distro {
|
|
case DISTRO_EL7:
|
|
packages = append(packages, "python3-PyYAML")
|
|
default:
|
|
packages = append(packages, "python3-pyyaml")
|
|
}
|
|
}
|
|
if len(p.DNFConfig) > 0 || len(p.RHSMConfig) > 0 {
|
|
packages = append(packages, "python3-iniparse")
|
|
}
|
|
|
|
if len(p.OSCustomizations.Containers) > 0 {
|
|
if p.OSTreeRef != "" {
|
|
switch distro {
|
|
case DISTRO_EL8:
|
|
packages = append(packages, "python3-pytoml")
|
|
default:
|
|
packages = append(packages, "python3-toml")
|
|
}
|
|
}
|
|
packages = append(packages, "skopeo")
|
|
}
|
|
|
|
return packages
|
|
}
|
|
|
|
func (p *OS) getOSTreeCommitSources() []ostree.SourceSpec {
|
|
if p.OSTreeParent == nil {
|
|
return nil
|
|
}
|
|
|
|
return []ostree.SourceSpec{
|
|
*p.OSTreeParent,
|
|
}
|
|
}
|
|
|
|
func (p *OS) getOSTreeCommits() []ostree.CommitSpec {
|
|
if p.ostreeParentSpec == nil {
|
|
return nil
|
|
}
|
|
return []ostree.CommitSpec{*p.ostreeParentSpec}
|
|
}
|
|
|
|
func (p *OS) getPackageSpecs() []rpmmd.PackageSpec {
|
|
return p.packageSpecs
|
|
}
|
|
|
|
func (p *OS) getContainerSpecs() []container.Spec {
|
|
return p.containerSpecs
|
|
}
|
|
|
|
func (p *OS) serializeStart(packages []rpmmd.PackageSpec, containers []container.Spec, commits []ostree.CommitSpec) {
|
|
if len(p.packageSpecs) > 0 {
|
|
panic("double call to serializeStart()")
|
|
}
|
|
|
|
p.packageSpecs = packages
|
|
p.containerSpecs = containers
|
|
if len(commits) > 0 {
|
|
if len(commits) > 1 {
|
|
panic("pipeline supports at most one ostree commit")
|
|
}
|
|
p.ostreeParentSpec = &commits[0]
|
|
}
|
|
|
|
if p.KernelName != "" {
|
|
p.kernelVer = rpmmd.GetVerStrFromPackageSpecListPanic(p.packageSpecs, p.KernelName)
|
|
}
|
|
}
|
|
|
|
func (p *OS) serializeEnd() {
|
|
if len(p.packageSpecs) == 0 {
|
|
panic("serializeEnd() call when serialization not in progress")
|
|
}
|
|
p.kernelVer = ""
|
|
p.packageSpecs = nil
|
|
p.containerSpecs = nil
|
|
p.ostreeParentSpec = nil
|
|
}
|
|
|
|
func (p *OS) serialize() osbuild.Pipeline {
|
|
if len(p.packageSpecs) == 0 {
|
|
panic("serialization not started")
|
|
}
|
|
|
|
pipeline := p.Base.serialize()
|
|
|
|
if p.ostreeParentSpec != nil {
|
|
pipeline.AddStage(osbuild.NewOSTreePasswdStage("org.osbuild.source", p.ostreeParentSpec.Checksum))
|
|
}
|
|
|
|
// collect all repos for this pipeline to create the repository options
|
|
allRepos := append(p.repos, p.ExtraBaseRepos...)
|
|
if p.Workload != nil {
|
|
allRepos = append(allRepos, p.Workload.GetRepos()...)
|
|
}
|
|
rpmOptions := osbuild.NewRPMStageOptions(allRepos)
|
|
if p.ExcludeDocs {
|
|
if rpmOptions.Exclude == nil {
|
|
rpmOptions.Exclude = &osbuild.Exclude{}
|
|
}
|
|
rpmOptions.Exclude.Docs = true
|
|
}
|
|
rpmOptions.GPGKeysFromTree = p.GPGKeyFiles
|
|
if p.OSTreeRef != "" {
|
|
rpmOptions.OSTreeBooted = common.ToPtr(true)
|
|
rpmOptions.DBPath = "/usr/share/rpm"
|
|
}
|
|
pipeline.AddStage(osbuild.NewRPMStage(rpmOptions, osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs)))
|
|
|
|
if !p.NoBLS {
|
|
// If the /boot is on a separate partition, the prefix for the BLS stage must be ""
|
|
if p.PartitionTable == nil || p.PartitionTable.FindMountable("/boot") == nil {
|
|
pipeline.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{}))
|
|
} else {
|
|
pipeline.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{Prefix: common.ToPtr("")}))
|
|
}
|
|
}
|
|
|
|
if len(p.containerSpecs) > 0 {
|
|
images := osbuild.NewContainersInputForSources(p.containerSpecs)
|
|
|
|
var storagePath string
|
|
|
|
// OSTree commits do not include data in `/var` since that is tied to the
|
|
// deployment, rather than the commit. Therefore the containers need to be
|
|
// stored in a different location, like `/usr/share`, and the container
|
|
// storage engine configured accordingly.
|
|
if p.OSTreeRef != "" {
|
|
storagePath = "/usr/share/containers/storage"
|
|
storageConf := "/etc/containers/storage.conf"
|
|
|
|
containerStoreOpts := osbuild.NewContainerStorageOptions(storageConf, storagePath)
|
|
pipeline.AddStage(osbuild.NewContainersStorageConfStage(containerStoreOpts))
|
|
}
|
|
|
|
manifests := osbuild.NewFilesInputForManifestLists(p.containerSpecs)
|
|
skopeo := osbuild.NewSkopeoStage(storagePath, images, manifests)
|
|
pipeline.AddStage(skopeo)
|
|
}
|
|
|
|
pipeline.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: p.Language}))
|
|
|
|
if p.Keyboard != nil {
|
|
keymapOptions := &osbuild.KeymapStageOptions{Keymap: *p.Keyboard}
|
|
if len(p.X11KeymapLayouts) > 0 {
|
|
keymapOptions.X11Keymap = &osbuild.X11KeymapOptions{Layouts: p.X11KeymapLayouts}
|
|
}
|
|
pipeline.AddStage(osbuild.NewKeymapStage(keymapOptions))
|
|
}
|
|
|
|
if p.Hostname != "" {
|
|
pipeline.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: p.Hostname}))
|
|
}
|
|
pipeline.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: p.Timezone}))
|
|
|
|
if len(p.NTPServers) > 0 {
|
|
chronyOptions := &osbuild.ChronyStageOptions{Servers: p.NTPServers}
|
|
if p.LeapSecTZ != nil {
|
|
chronyOptions.LeapsecTz = p.LeapSecTZ
|
|
}
|
|
pipeline.AddStage(osbuild.NewChronyStage(chronyOptions))
|
|
}
|
|
|
|
if len(p.Groups) > 0 {
|
|
pipeline.AddStage(osbuild.GenGroupsStage(p.Groups))
|
|
}
|
|
|
|
if len(p.Users) > 0 {
|
|
if p.OSTreeRef != "" {
|
|
// for ostree, writing the key during user creation is
|
|
// redundant and can cause issues so create users without keys
|
|
// and write them on first boot
|
|
usersStageSansKeys, err := osbuild.GenUsersStage(p.Users, true)
|
|
if err != nil {
|
|
// TODO: move encryption into weldr
|
|
panic("password encryption failed")
|
|
}
|
|
pipeline.AddStage(usersStageSansKeys)
|
|
pipeline.AddStage(osbuild.NewFirstBootStage(usersFirstBootOptions(p.Users)))
|
|
} else {
|
|
usersStage, err := osbuild.GenUsersStage(p.Users, false)
|
|
if err != nil {
|
|
// TODO: move encryption into weldr
|
|
panic("password encryption failed")
|
|
}
|
|
pipeline.AddStage(usersStage)
|
|
}
|
|
}
|
|
|
|
if p.Firewall != nil {
|
|
pipeline.AddStage(osbuild.NewFirewallStage(p.Firewall))
|
|
}
|
|
|
|
for _, sysconfigConfig := range p.Sysconfig {
|
|
pipeline.AddStage(osbuild.NewSysconfigStage(sysconfigConfig))
|
|
}
|
|
|
|
for _, systemdLogindConfig := range p.SystemdLogind {
|
|
pipeline.AddStage(osbuild.NewSystemdLogindStage(systemdLogindConfig))
|
|
}
|
|
|
|
for _, cloudInitConfig := range p.CloudInit {
|
|
pipeline.AddStage(osbuild.NewCloudInitStage(cloudInitConfig))
|
|
}
|
|
|
|
for _, modprobeConfig := range p.Modprobe {
|
|
pipeline.AddStage(osbuild.NewModprobeStage(modprobeConfig))
|
|
}
|
|
|
|
for _, dracutConfConfig := range p.DracutConf {
|
|
pipeline.AddStage(osbuild.NewDracutConfStage(dracutConfConfig))
|
|
}
|
|
|
|
for _, systemdUnitConfig := range p.SystemdUnit {
|
|
pipeline.AddStage(osbuild.NewSystemdUnitStage(systemdUnitConfig))
|
|
}
|
|
|
|
if p.Authselect != nil {
|
|
pipeline.AddStage(osbuild.NewAuthselectStage(p.Authselect))
|
|
}
|
|
|
|
if p.SELinuxConfig != nil {
|
|
pipeline.AddStage(osbuild.NewSELinuxConfigStage(p.SELinuxConfig))
|
|
}
|
|
|
|
if p.Tuned != nil {
|
|
pipeline.AddStage(osbuild.NewTunedStage(p.Tuned))
|
|
}
|
|
|
|
for _, tmpfilesdConfig := range p.Tmpfilesd {
|
|
pipeline.AddStage(osbuild.NewTmpfilesdStage(tmpfilesdConfig))
|
|
}
|
|
|
|
for _, pamLimitsConfConfig := range p.PamLimitsConf {
|
|
pipeline.AddStage(osbuild.NewPamLimitsConfStage(pamLimitsConfConfig))
|
|
}
|
|
|
|
for _, sysctldConfig := range p.Sysctld {
|
|
pipeline.AddStage(osbuild.NewSysctldStage(sysctldConfig))
|
|
}
|
|
|
|
for _, dnfConfig := range p.DNFConfig {
|
|
pipeline.AddStage(osbuild.NewDNFConfigStage(dnfConfig))
|
|
}
|
|
|
|
if p.DNFAutomaticConfig != nil {
|
|
pipeline.AddStage(osbuild.NewDNFAutomaticConfigStage(p.DNFAutomaticConfig))
|
|
}
|
|
|
|
for _, yumRepo := range p.YUMRepos {
|
|
pipeline.AddStage(osbuild.NewYumReposStage(yumRepo))
|
|
}
|
|
|
|
if p.YUMConfig != nil {
|
|
pipeline.AddStage(osbuild.NewYumConfigStage(p.YUMConfig))
|
|
}
|
|
|
|
if p.GCPGuestAgentConfig != nil {
|
|
pipeline.AddStage(osbuild.NewGcpGuestAgentConfigStage(p.GCPGuestAgentConfig))
|
|
}
|
|
|
|
if p.SshdConfig != nil {
|
|
pipeline.AddStage((osbuild.NewSshdConfigStage(p.SshdConfig)))
|
|
}
|
|
|
|
if p.AuthConfig != nil {
|
|
pipeline.AddStage(osbuild.NewAuthconfigStage(p.AuthConfig))
|
|
}
|
|
|
|
if p.PwQuality != nil {
|
|
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 {
|
|
var commands []string
|
|
if p.Subscription.Rhc {
|
|
// Use rhc for registration instead of subscription manager
|
|
commands = []string{fmt.Sprintf("/usr/bin/rhc connect -o=%s -a=%s --server %s", p.Subscription.Organization, p.Subscription.ActivationKey, 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")
|
|
} else {
|
|
commands = []string{fmt.Sprintf("/usr/sbin/subscription-manager register --org=%s --activationkey=%s --serverurl %s --baseurl %s", p.Subscription.Organization, p.Subscription.ActivationKey, 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")
|
|
}
|
|
}
|
|
|
|
pipeline.AddStage(osbuild.NewFirstBootStage(&osbuild.FirstBootStageOptions{
|
|
Commands: commands,
|
|
WaitForNetwork: true,
|
|
}))
|
|
|
|
if rhsmConfig, exists := p.RHSMConfig[subscription.RHSMConfigWithSubscription]; exists {
|
|
pipeline.AddStage(osbuild.NewRHSMStage(rhsmConfig))
|
|
}
|
|
} else {
|
|
if rhsmConfig, exists := p.RHSMConfig[subscription.RHSMConfigNoSubscription]; exists {
|
|
pipeline.AddStage(osbuild.NewRHSMStage(rhsmConfig))
|
|
}
|
|
}
|
|
|
|
if waConfig := p.WAAgentConfig; waConfig != nil {
|
|
pipeline.AddStage(osbuild.NewWAAgentConfStage(waConfig))
|
|
}
|
|
|
|
if udevRules := p.UdevRules; udevRules != nil {
|
|
pipeline.AddStage(osbuild.NewUdevRulesStage(udevRules))
|
|
}
|
|
|
|
if pt := p.PartitionTable; pt != nil {
|
|
kernelOptions := osbuild.GenImageKernelOptions(p.PartitionTable)
|
|
kernelOptions = append(kernelOptions, p.KernelOptionsAppend...)
|
|
if !p.KernelOptionsBootloader {
|
|
pipeline = prependKernelCmdlineStage(pipeline, strings.Join(kernelOptions, " "), pt)
|
|
}
|
|
|
|
pipeline.AddStage(osbuild.NewFSTabStage(osbuild.NewFSTabStageOptions(pt)))
|
|
|
|
var bootloader *osbuild.Stage
|
|
switch p.platform.GetArch() {
|
|
case platform.ARCH_S390X:
|
|
bootloader = osbuild.NewZiplStage(new(osbuild.ZiplStageOptions))
|
|
default:
|
|
if p.NoBLS {
|
|
// BLS entries not supported: use grub2.legacy
|
|
id := "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
|
|
product := osbuild.GRUB2Product{
|
|
Name: p.OSProduct,
|
|
Version: p.OSVersion,
|
|
Nick: p.OSNick,
|
|
}
|
|
|
|
_, err := rpmmd.GetVerStrFromPackageSpecList(p.packageSpecs, "dracut-config-rescue")
|
|
hasRescue := err == nil
|
|
bootloader = osbuild.NewGrub2LegacyStage(
|
|
osbuild.NewGrub2LegacyStageOptions(
|
|
p.Grub2Config,
|
|
p.PartitionTable,
|
|
kernelOptions,
|
|
p.platform.GetBIOSPlatform(),
|
|
p.platform.GetUEFIVendor(),
|
|
osbuild.MakeGrub2MenuEntries(id, p.kernelVer, product, hasRescue),
|
|
),
|
|
)
|
|
} else {
|
|
options := osbuild.NewGrub2StageOptionsUnified(pt,
|
|
strings.Join(kernelOptions, " "),
|
|
p.kernelVer,
|
|
p.platform.GetUEFIVendor() != "",
|
|
p.platform.GetBIOSPlatform(),
|
|
p.platform.GetUEFIVendor(), false)
|
|
if cfg := p.Grub2Config; cfg != nil {
|
|
// TODO: don't store Grub2Config in OSPipeline, making the overrides unnecessary
|
|
// grub2.Config.Default is owned and set by `NewGrub2StageOptionsUnified`
|
|
// and thus we need to preserve it
|
|
if options.Config != nil {
|
|
cfg.Default = options.Config.Default
|
|
}
|
|
|
|
options.Config = cfg
|
|
}
|
|
if p.KernelOptionsBootloader {
|
|
options.WriteCmdLine = nil
|
|
if options.UEFI != nil {
|
|
options.UEFI.Unified = false
|
|
}
|
|
}
|
|
bootloader = osbuild.NewGRUB2Stage(options)
|
|
}
|
|
}
|
|
|
|
pipeline.AddStage(bootloader)
|
|
}
|
|
|
|
if p.OpenSCAPConfig != nil {
|
|
pipeline.AddStage(osbuild.NewOscapRemediationStage(p.OpenSCAPConfig))
|
|
}
|
|
|
|
if p.FactAPIType != nil {
|
|
pipeline.AddStage(osbuild.NewRHSMFactsStage(&osbuild.RHSMFactsStageOptions{
|
|
Facts: osbuild.RHSMFacts{
|
|
ApiType: p.FactAPIType.String(),
|
|
},
|
|
}))
|
|
}
|
|
|
|
if p.OSTreeRef != "" {
|
|
pipeline.AddStage(osbuild.NewSystemdJournaldStage(
|
|
&osbuild.SystemdJournaldStageOptions{
|
|
Filename: "10-persistent.conf",
|
|
Config: osbuild.SystemdJournaldConfigDropin{
|
|
Journal: osbuild.SystemdJournaldConfigJournalSection{
|
|
Storage: osbuild.StoragePresistent,
|
|
},
|
|
},
|
|
}))
|
|
}
|
|
|
|
// First create custom directories, because some of the custom files may depend on them
|
|
if len(p.Directories) > 0 {
|
|
pipeline.AddStages(osbuild.GenDirectoryNodesStages(p.Directories)...)
|
|
}
|
|
|
|
if len(p.Files) > 0 {
|
|
pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...)
|
|
}
|
|
|
|
enabledServices := []string{}
|
|
disabledServices := []string{}
|
|
enabledServices = append(enabledServices, p.EnabledServices...)
|
|
disabledServices = append(disabledServices, p.DisabledServices...)
|
|
if p.Environment != nil {
|
|
enabledServices = append(enabledServices, p.Environment.GetServices()...)
|
|
}
|
|
if p.Workload != nil {
|
|
enabledServices = append(enabledServices, p.Workload.GetServices()...)
|
|
disabledServices = append(disabledServices, p.Workload.GetDisabledServices()...)
|
|
}
|
|
if len(enabledServices) != 0 ||
|
|
len(disabledServices) != 0 || p.DefaultTarget != "" {
|
|
pipeline.AddStage(osbuild.NewSystemdStage(&osbuild.SystemdStageOptions{
|
|
EnabledServices: enabledServices,
|
|
DisabledServices: disabledServices,
|
|
DefaultTarget: p.DefaultTarget,
|
|
}))
|
|
}
|
|
if len(p.ShellInit) > 0 {
|
|
pipeline.AddStage(osbuild.GenShellInitStage(p.ShellInit))
|
|
}
|
|
|
|
if p.SElinux != "" {
|
|
pipeline.AddStage(osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{
|
|
FileContexts: fmt.Sprintf("etc/selinux/%s/contexts/files/file_contexts", p.SElinux),
|
|
ForceAutorelabel: p.SELinuxForceRelabel,
|
|
}))
|
|
}
|
|
|
|
if p.OSTreeRef != "" {
|
|
pipeline.AddStage(osbuild.NewOSTreePrepTreeStage(&osbuild.OSTreePrepTreeStageOptions{
|
|
EtcGroupMembers: []string{
|
|
// NOTE: We may want to make this configurable.
|
|
"wheel", "docker",
|
|
},
|
|
}))
|
|
}
|
|
|
|
return pipeline
|
|
}
|
|
|
|
func prependKernelCmdlineStage(pipeline osbuild.Pipeline, kernelOptions string, pt *disk.PartitionTable) osbuild.Pipeline {
|
|
rootFs := pt.FindMountable("/")
|
|
if rootFs == nil {
|
|
panic("root filesystem must be defined for kernel-cmdline stage, this is a programming error")
|
|
}
|
|
rootFsUUID := rootFs.GetFSSpec().UUID
|
|
kernelStage := osbuild.NewKernelCmdlineStage(osbuild.NewKernelCmdlineStageOptions(rootFsUUID, kernelOptions))
|
|
pipeline.Stages = append([]*osbuild.Stage{kernelStage}, pipeline.Stages...)
|
|
return pipeline
|
|
}
|
|
|
|
func usersFirstBootOptions(users []users.User) *osbuild.FirstBootStageOptions {
|
|
cmds := make([]string, 0, 3*len(users)+2)
|
|
// workaround for creating authorized_keys file for user
|
|
// need to special case the root user, which has its home in a different place
|
|
varhome := filepath.Join("/var", "home")
|
|
roothome := filepath.Join("/var", "roothome")
|
|
|
|
for _, user := range users {
|
|
if user.Key != nil {
|
|
var home string
|
|
|
|
if user.Name == "root" {
|
|
home = roothome
|
|
} else {
|
|
home = filepath.Join(varhome, user.Name)
|
|
}
|
|
|
|
sshdir := filepath.Join(home, ".ssh")
|
|
|
|
cmds = append(cmds, fmt.Sprintf("mkdir -p %s", sshdir))
|
|
cmds = append(cmds, fmt.Sprintf("sh -c 'echo %q >> %q'", *user.Key, filepath.Join(sshdir, "authorized_keys")))
|
|
cmds = append(cmds, fmt.Sprintf("chown %s:%s -Rc %s", user.Name, user.Name, sshdir))
|
|
}
|
|
}
|
|
cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", varhome))
|
|
cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", roothome))
|
|
|
|
options := &osbuild.FirstBootStageOptions{
|
|
Commands: cmds,
|
|
WaitForNetwork: false,
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
func (p *OS) GetPlatform() platform.Platform {
|
|
return p.platform
|
|
}
|
|
|
|
func (p *OS) getInline() []string {
|
|
inlineData := []string{}
|
|
|
|
// inline data for custom files
|
|
for _, file := range p.Files {
|
|
inlineData = append(inlineData, string(file.Data()))
|
|
}
|
|
|
|
return inlineData
|
|
}
|