split: replace internal packages with images library

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.
This commit is contained in:
Achilleas Koutsou 2023-06-14 16:34:41 +02:00 committed by Sanne Raymaekers
parent d59199670f
commit 0e4a9e586f
446 changed files with 5690 additions and 13312 deletions

View file

@ -0,0 +1,394 @@
package manifest
import (
"fmt"
"os"
"github.com/osbuild/images/internal/fsnode"
"github.com/osbuild/images/internal/users"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
)
type AnacondaInstallerType int
const (
AnacondaInstallerTypeLive AnacondaInstallerType = iota + 1
AnacondaInstallerTypePayload
)
// An Anaconda represents the installer tree as found on an ISO this can be either
// a payload installer or a live installer depending on `Type`.
type AnacondaInstaller struct {
Base
// The type of the Anaconda installer tree to prepare, this can be either
// a 'live' or a 'payload' and it controls which stages are added to the
// manifest.
Type AnacondaInstallerType
// Packages to install in addition to the ones required by the
// pipeline.
ExtraPackages []string
// Extra repositories to install packages from
ExtraRepos []rpmmd.RepoConfig
// Users and Groups to create during installation.
// If empty, then the user can interactively create users at install time.
Users []users.User
Groups []users.Group
// Biosdevname indicates whether or not biosdevname should be used to
// name network devices when booting the installer. This may affect
// the naming of network devices on the target system.
Biosdevname bool
// Variant is the variant of the product being installed, if applicable.
Variant string
platform platform.Platform
repos []rpmmd.RepoConfig
packageSpecs []rpmmd.PackageSpec
kernelName string
kernelVer string
product string
version string
// Interactive defaults is a kickstart stage that can be provided, it
// will be written to /usr/share/anaconda/interactive-defaults
InteractiveDefaults *AnacondaInteractiveDefaults
// Additional anaconda modules to enable
AdditionalAnacondaModules []string
// Additional dracut modules and drivers to enable
AdditionalDracutModules []string
AdditionalDrivers []string
Files []*fsnode.File
}
func NewAnacondaInstaller(m *Manifest,
installerType AnacondaInstallerType,
buildPipeline *Build,
platform platform.Platform,
repos []rpmmd.RepoConfig,
kernelName,
product,
version string) *AnacondaInstaller {
name := "anaconda-tree"
p := &AnacondaInstaller{
Base: NewBase(m, name, buildPipeline),
Type: installerType,
platform: platform,
repos: filterRepos(repos, name),
kernelName: kernelName,
product: product,
version: version,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
// TODO: refactor - what is required to boot and what to build, and
// do they all belong in this pipeline?
func (p *AnacondaInstaller) anacondaBootPackageSet() []string {
packages := []string{
"grub2-tools",
"grub2-tools-extra",
"grub2-tools-minimal",
"efibootmgr",
}
switch p.platform.GetArch() {
case platform.ARCH_X86_64:
packages = append(packages,
"grub2-efi-x64",
"grub2-efi-x64-cdboot",
"grub2-pc",
"grub2-pc-modules",
"shim-x64",
"syslinux",
"syslinux-nonlinux",
)
case platform.ARCH_AARCH64:
packages = append(packages,
"grub2-efi-aa64-cdboot",
"grub2-efi-aa64",
"shim-aa64",
)
default:
panic(fmt.Sprintf("unsupported arch: %s", p.platform.GetArch()))
}
return packages
}
func (p *AnacondaInstaller) getBuildPackages(Distro) []string {
packages := p.anacondaBootPackageSet()
packages = append(packages,
"rpm",
"lorax-templates-generic",
)
return packages
}
func (p *AnacondaInstaller) getPackageSetChain(Distro) []rpmmd.PackageSet {
packages := p.anacondaBootPackageSet()
if p.Biosdevname {
packages = append(packages, "biosdevname")
}
return []rpmmd.PackageSet{
{
Include: append(packages, p.ExtraPackages...),
Repositories: append(p.repos, p.ExtraRepos...),
},
}
}
func (p *AnacondaInstaller) getPackageSpecs() []rpmmd.PackageSpec {
return p.packageSpecs
}
func (p *AnacondaInstaller) serializeStart(packages []rpmmd.PackageSpec, _ []container.Spec, _ []ostree.CommitSpec) {
if len(p.packageSpecs) > 0 {
panic("double call to serializeStart()")
}
p.packageSpecs = packages
if p.kernelName != "" {
p.kernelVer = rpmmd.GetVerStrFromPackageSpecListPanic(p.packageSpecs, p.kernelName)
}
}
func (p *AnacondaInstaller) serializeEnd() {
if len(p.packageSpecs) == 0 {
panic("serializeEnd() call when serialization not in progress")
}
p.kernelVer = ""
p.packageSpecs = nil
}
func (p *AnacondaInstaller) serialize() osbuild.Pipeline {
if len(p.packageSpecs) == 0 {
panic("serialization not started")
}
// Let's do a bunch of sanity checks that are dependent on the installer type
// being serialized
if p.Type == AnacondaInstallerTypeLive {
if len(p.Users) != 0 || len(p.Groups) != 0 {
panic("anaconda installer type payload does not support users and groups customization")
}
if p.InteractiveDefaults != nil {
panic("anaconda installer type payload does not support interactive defaults")
}
} else if p.Type == AnacondaInstallerTypePayload {
} else {
panic("invalid anaconda installer type")
}
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewRPMStage(osbuild.NewRPMStageOptions(p.repos), osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs)))
pipeline.AddStage(osbuild.NewBuildstampStage(&osbuild.BuildstampStageOptions{
Arch: p.platform.GetArch().String(),
Product: p.product,
Variant: p.Variant,
Version: p.version,
Final: true,
}))
pipeline.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"}))
rootPassword := ""
rootUser := osbuild.UsersStageOptionsUser{
Password: &rootPassword,
}
var usersStageOptions *osbuild.UsersStageOptions
if p.Type == AnacondaInstallerTypePayload {
installUID := 0
installGID := 0
installHome := "/root"
installShell := "/usr/libexec/anaconda/run-anaconda"
installPassword := ""
installUser := osbuild.UsersStageOptionsUser{
UID: &installUID,
GID: &installGID,
Home: &installHome,
Shell: &installShell,
Password: &installPassword,
}
usersStageOptions = &osbuild.UsersStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"root": rootUser,
"install": installUser,
},
}
} else if p.Type == AnacondaInstallerTypeLive {
usersStageOptions = &osbuild.UsersStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"root": rootUser,
},
}
}
pipeline.AddStage(osbuild.NewUsersStage(usersStageOptions))
if p.Type == AnacondaInstallerTypeLive {
systemdStageOptions := &osbuild.SystemdStageOptions{
EnabledServices: []string{
"livesys.service",
"livesys-late.service",
},
}
pipeline.AddStage(osbuild.NewSystemdStage(systemdStageOptions))
livesysMode := os.FileMode(int(0644))
livesysFile, err := fsnode.NewFile("/etc/sysconfig/livesys", &livesysMode, "root", "root", []byte("livesys_session=\"gnome\""))
if err != nil {
panic(err)
}
p.Files = []*fsnode.File{livesysFile}
pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...)
}
if p.Type == AnacondaInstallerTypePayload {
pipeline.AddStage(osbuild.NewAnacondaStage(osbuild.NewAnacondaStageOptions(p.AdditionalAnacondaModules)))
pipeline.AddStage(osbuild.NewLoraxScriptStage(&osbuild.LoraxScriptStageOptions{
Path: "99-generic/runtime-postinstall.tmpl",
BaseArch: p.platform.GetArch().String(),
}))
}
dracutModules := append(
p.AdditionalDracutModules,
"anaconda",
"rdma",
"rngd",
"multipath",
"fcoe",
"fcoe-uefi",
"iscsi",
"lunmask",
"nfs",
)
dracutOptions := dracutStageOptions(p.kernelVer, p.Biosdevname, dracutModules)
dracutOptions.AddDrivers = p.AdditionalDrivers
pipeline.AddStage(osbuild.NewDracutStage(dracutOptions))
pipeline.AddStage(osbuild.NewSELinuxConfigStage(&osbuild.SELinuxConfigStageOptions{State: osbuild.SELinuxStatePermissive}))
if p.Type == AnacondaInstallerTypePayload {
if p.InteractiveDefaults != nil {
kickstartOptions, err := osbuild.NewKickstartStageOptions(
"/usr/share/anaconda/interactive-defaults.ks",
p.InteractiveDefaults.TarPath,
p.Users,
p.Groups,
"",
"",
"",
)
if err != nil {
panic("failed to create kickstartstage options for interactive defaults")
}
pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions))
}
}
return pipeline
}
func dracutStageOptions(kernelVer string, biosdevname bool, additionalModules []string) *osbuild.DracutStageOptions {
kernel := []string{kernelVer}
modules := []string{
"bash",
"systemd",
"fips",
"systemd-initrd",
"modsign",
"nss-softokn",
"i18n",
"convertfs",
"network-manager",
"network",
"ifcfg",
"url-lib",
"drm",
"plymouth",
"crypt",
"dm",
"dmsquash-live",
"kernel-modules",
"kernel-modules-extra",
"kernel-network-modules",
"livenet",
"lvm",
"mdraid",
"qemu",
"qemu-net",
"resume",
"rootfs-block",
"terminfo",
"udev-rules",
"dracut-systemd",
"pollcdrom",
"usrmount",
"base",
"fs-lib",
"img-lib",
"shutdown",
"uefi-lib",
}
if biosdevname {
modules = append(modules, "biosdevname")
}
modules = append(modules, additionalModules...)
return &osbuild.DracutStageOptions{
Kernel: kernel,
Modules: modules,
Install: []string{"/.buildstamp"},
}
}
func (p *AnacondaInstaller) GetPlatform() platform.Platform {
return p.platform
}
type AnacondaInteractiveDefaults struct {
TarPath string
}
func NewAnacondaInteractiveDefaults(tarPath string) *AnacondaInteractiveDefaults {
i := &AnacondaInteractiveDefaults{
TarPath: tarPath,
}
return i
}
func (p *AnacondaInstaller) getInline() []string {
inlineData := []string{}
// inline data for custom files
for _, file := range p.Files {
inlineData = append(inlineData, string(file.Data()))
}
return inlineData
}

View file

@ -0,0 +1,316 @@
package manifest
import (
"fmt"
"path"
"github.com/osbuild/images/internal/users"
"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/rpmmd"
)
// An AnacondaInstallerISOTree represents a tree containing the anaconda installer,
// configuration in terms of a kickstart file, as well as an embedded
// payload to be installed, this payload can either be an ostree
// CommitSpec or OSPipeline for an OS.
type AnacondaInstallerISOTree struct {
Base
// TODO: review optional and mandatory fields and their meaning
OSName string
Release string
Users []users.User
Groups []users.Group
PartitionTable *disk.PartitionTable
anacondaPipeline *AnacondaInstaller
rootfsPipeline *ISORootfsImg
bootTreePipeline *EFIBootTree
// The location of the kickstart file, if it will be added to the
// bootiso-tree.
// Otherwise, it should be defined in the interactive defaults of the
// Anaconda pipeline.
KSPath string
// The path where the payload (tarball or ostree repo) will be stored.
PayloadPath string
isoLabel string
SquashfsCompression string
OSPipeline *OS
OSTreeCommitSource *ostree.SourceSpec
ostreeCommitSpec *ostree.CommitSpec
KernelOpts []string
// Enable ISOLinux stage
ISOLinux bool
}
func NewAnacondaInstallerISOTree(m *Manifest,
buildPipeline *Build,
anacondaPipeline *AnacondaInstaller,
rootfsPipeline *ISORootfsImg,
bootTreePipeline *EFIBootTree,
isoLabel string) *AnacondaInstallerISOTree {
p := &AnacondaInstallerISOTree{
Base: NewBase(m, "bootiso-tree", buildPipeline),
anacondaPipeline: anacondaPipeline,
rootfsPipeline: rootfsPipeline,
bootTreePipeline: bootTreePipeline,
isoLabel: isoLabel,
}
buildPipeline.addDependent(p)
if anacondaPipeline.Base.manifest != m {
panic("anaconda pipeline from different manifest")
}
m.addPipeline(p)
return p
}
func (p *AnacondaInstallerISOTree) getOSTreeCommitSources() []ostree.SourceSpec {
if p.OSTreeCommitSource == nil {
return nil
}
return []ostree.SourceSpec{
*p.OSTreeCommitSource,
}
}
func (p *AnacondaInstallerISOTree) getOSTreeCommits() []ostree.CommitSpec {
if p.ostreeCommitSpec == nil {
return nil
}
return []ostree.CommitSpec{*p.ostreeCommitSpec}
}
func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string {
packages := []string{
"squashfs-tools",
}
if p.OSTreeCommitSource != nil {
packages = append(packages, "rpm-ostree")
}
if p.OSPipeline != nil {
packages = append(packages, "tar")
}
return packages
}
func (p *AnacondaInstallerISOTree) serializeStart(_ []rpmmd.PackageSpec, _ []container.Spec, commits []ostree.CommitSpec) {
if len(commits) == 0 {
// nothing to do
return
}
if len(commits) > 1 {
panic("pipeline supports at most one ostree commit")
}
p.ostreeCommitSpec = &commits[0]
}
func (p *AnacondaInstallerISOTree) serializeEnd() {
p.ostreeCommitSpec = nil
}
func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline {
// If the anaconda pipeline is a payload then we need one of two payload types
if p.anacondaPipeline.Type == AnacondaInstallerTypePayload {
if p.ostreeCommitSpec == nil && p.OSPipeline == nil {
panic("missing ostree or ospipeline parameters in ISO tree pipeline")
}
// But not both payloads
if p.ostreeCommitSpec != nil && p.OSPipeline != nil {
panic("got both ostree and ospipeline parameters in ISO tree pipeline")
}
}
pipeline := p.Base.serialize()
kernelOpts := []string{}
if p.anacondaPipeline.Type == AnacondaInstallerTypePayload {
kernelOpts = append(kernelOpts, fmt.Sprintf("inst.stage2=hd:LABEL=%s", p.isoLabel))
if p.KSPath != "" {
kernelOpts = append(kernelOpts, fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", p.isoLabel, p.KSPath))
}
}
if len(p.KernelOpts) > 0 {
kernelOpts = append(kernelOpts, p.KernelOpts...)
}
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: "images",
},
{
Path: "images/pxeboot",
},
},
}))
if p.anacondaPipeline.Type == AnacondaInstallerTypeLive {
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: "LiveOS",
},
},
}))
}
inputName := "tree"
copyStageOptions := &osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/boot/vmlinuz-%s", inputName, p.anacondaPipeline.kernelVer),
To: "tree:///images/pxeboot/vmlinuz",
},
{
From: fmt.Sprintf("input://%s/boot/initramfs-%s.img", inputName, p.anacondaPipeline.kernelVer),
To: "tree:///images/pxeboot/initrd.img",
},
},
}
copyStageInputs := osbuild.NewPipelineTreeInputs(inputName, p.anacondaPipeline.Name())
copyStage := osbuild.NewCopyStageSimple(copyStageOptions, copyStageInputs)
pipeline.AddStage(copyStage)
var squashfsOptions osbuild.SquashfsStageOptions
if p.anacondaPipeline.Type == AnacondaInstallerTypePayload {
squashfsOptions = osbuild.SquashfsStageOptions{
Filename: "images/install.img",
}
} else if p.anacondaPipeline.Type == AnacondaInstallerTypeLive {
squashfsOptions = osbuild.SquashfsStageOptions{
Filename: "LiveOS/squashfs.img",
}
}
if p.SquashfsCompression != "" {
squashfsOptions.Compression.Method = p.SquashfsCompression
} else {
// default to xz if not specified
squashfsOptions.Compression.Method = "xz"
}
if squashfsOptions.Compression.Method == "xz" {
squashfsOptions.Compression.Options = &osbuild.FSCompressionOptions{
BCJ: osbuild.BCJOption(p.anacondaPipeline.platform.GetArch().String()),
}
}
squashfsStage := osbuild.NewSquashfsStage(&squashfsOptions, p.rootfsPipeline.Name())
pipeline.AddStage(squashfsStage)
if p.ISOLinux {
isoLinuxOptions := &osbuild.ISOLinuxStageOptions{
Product: osbuild.ISOLinuxProduct{
Name: p.anacondaPipeline.product,
Version: p.anacondaPipeline.version,
},
Kernel: osbuild.ISOLinuxKernel{
Dir: "/images/pxeboot",
Opts: kernelOpts,
},
}
isoLinuxStage := osbuild.NewISOLinuxStage(isoLinuxOptions, p.anacondaPipeline.Name())
pipeline.AddStage(isoLinuxStage)
}
filename := "images/efiboot.img"
pipeline.AddStage(osbuild.NewTruncateStage(&osbuild.TruncateStageOptions{
Filename: filename,
Size: fmt.Sprintf("%d", p.PartitionTable.Size),
}))
efibootDevice := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: filename})
for _, stage := range osbuild.GenMkfsStages(p.PartitionTable, efibootDevice) {
pipeline.AddStage(stage)
}
inputName = "root-tree"
copyInputs := osbuild.NewPipelineTreeInputs(inputName, p.bootTreePipeline.Name())
copyOptions, copyDevices, copyMounts := osbuild.GenCopyFSTreeOptions(inputName, p.bootTreePipeline.Name(), filename, p.PartitionTable)
pipeline.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts))
copyInputs = osbuild.NewPipelineTreeInputs(inputName, p.bootTreePipeline.Name())
pipeline.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/EFI", inputName),
To: "tree:///",
},
},
},
copyInputs,
))
if p.ostreeCommitSpec != nil {
// Set up the payload ostree repo
pipeline.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: p.PayloadPath}))
pipeline.AddStage(osbuild.NewOSTreePullStage(
&osbuild.OSTreePullStageOptions{Repo: p.PayloadPath},
osbuild.NewOstreePullStageInputs("org.osbuild.source", p.ostreeCommitSpec.Checksum, p.ostreeCommitSpec.Ref),
))
// Configure the kickstart file with the payload and any user options
kickstartOptions, err := osbuild.NewKickstartStageOptions(p.KSPath, "", p.Users, p.Groups, makeISORootPath(p.PayloadPath), p.ostreeCommitSpec.Ref, p.OSName)
if err != nil {
panic("failed to create kickstartstage options")
}
pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions))
}
if p.OSPipeline != nil {
// Create the payload tarball
pipeline.AddStage(osbuild.NewTarStage(&osbuild.TarStageOptions{Filename: p.PayloadPath}, p.OSPipeline.name))
// If the KSPath is set, we need to add the kickstart stage to this (bootiso-tree) pipeline.
// If it's not specified here, it should have been added to the InteractiveDefaults in the anaconda-tree.
if p.KSPath != "" {
kickstartOptions, err := osbuild.NewKickstartStageOptions(p.KSPath, makeISORootPath(p.PayloadPath), p.Users, p.Groups, "", "", p.OSName)
if err != nil {
panic("failed to create kickstartstage options")
}
pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions))
}
}
pipeline.AddStage(osbuild.NewDiscinfoStage(&osbuild.DiscinfoStageOptions{
BaseArch: p.anacondaPipeline.platform.GetArch().String(),
Release: p.Release,
}))
return pipeline
}
// makeISORootPath return a path that can be used to address files and folders
// in the root of the iso
func makeISORootPath(p string) string {
fullpath := path.Join("/run/install/repo", p)
return fmt.Sprintf("file://%s", fullpath)
}

116
vendor/github.com/osbuild/images/pkg/manifest/build.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
package manifest
import (
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/runner"
)
// A Build represents the build environment for other pipelines. As a
// general rule, tools required to build pipelines are used from the build
// environment, rather than from the pipeline itself. Without a specified
// build environment, the build host's root filesystem would be used, which
// is not predictable nor reproducible. For the purposes of building the
// build pipeline, we do use the build host's filesystem, this means we should
// make minimal assumptions about what's available there.
type Build struct {
Base
runner runner.Runner
dependents []Pipeline
repos []rpmmd.RepoConfig
packageSpecs []rpmmd.PackageSpec
}
// NewBuild creates a new build pipeline from the repositories in repos
// and the specified packages.
func NewBuild(m *Manifest, runner runner.Runner, repos []rpmmd.RepoConfig) *Build {
name := "build"
pipeline := &Build{
Base: NewBase(m, name, nil),
runner: runner,
dependents: make([]Pipeline, 0),
repos: filterRepos(repos, name),
}
m.addPipeline(pipeline)
return pipeline
}
func (p *Build) addDependent(dep Pipeline) {
p.dependents = append(p.dependents, dep)
}
func (p *Build) getPackageSetChain(distro Distro) []rpmmd.PackageSet {
// TODO: make the /usr/bin/cp dependency conditional
// TODO: make the /usr/bin/xz dependency conditional
packages := []string{
"selinux-policy-targeted", // needed to build the build pipeline
"coreutils", // /usr/bin/cp - used all over
"xz", // usage unclear
}
packages = append(packages, p.runner.GetBuildPackages()...)
for _, pipeline := range p.dependents {
packages = append(packages, pipeline.getBuildPackages(distro)...)
}
return []rpmmd.PackageSet{
{
Include: packages,
Repositories: p.repos,
},
}
}
func (p *Build) getPackageSpecs() []rpmmd.PackageSpec {
return p.packageSpecs
}
func (p *Build) serializeStart(packages []rpmmd.PackageSpec, _ []container.Spec, _ []ostree.CommitSpec) {
if len(p.packageSpecs) > 0 {
panic("double call to serializeStart()")
}
p.packageSpecs = packages
}
func (p *Build) serializeEnd() {
if len(p.packageSpecs) == 0 {
panic("serializeEnd() call when serialization not in progress")
}
p.packageSpecs = nil
}
func (p *Build) serialize() osbuild.Pipeline {
if len(p.packageSpecs) == 0 {
panic("serialization not started")
}
pipeline := p.Base.serialize()
pipeline.Runner = p.runner.String()
pipeline.AddStage(osbuild.NewRPMStage(osbuild.NewRPMStageOptions(p.repos), osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs)))
pipeline.AddStage(osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{
FileContexts: "etc/selinux/targeted/contexts/files/file_contexts",
Labels: p.getSELinuxLabels(),
},
))
return pipeline
}
// Returns a map of paths to labels for the SELinux stage based on specific
// packages found in the pipeline.
func (p *Build) getSELinuxLabels() map[string]string {
labels := make(map[string]string)
for _, pkg := range p.getPackageSpecs() {
switch pkg.Name {
case "coreutils":
labels["/usr/bin/cp"] = "system_u:object_r:install_exec_t:s0"
case "tar":
labels["/usr/bin/tar"] = "system_u:object_r:install_exec_t:s0"
}
}
return labels
}

View file

@ -0,0 +1,170 @@
package manifest
import (
"crypto/sha256"
"fmt"
"github.com/osbuild/images/internal/users"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/osbuild"
)
type CoreOSISOTree struct {
Base
// TODO: review optional and mandatory fields and their meaning
OSName string
Release string
Users []users.User
Groups []users.Group
PartitionTable *disk.PartitionTable
payloadPipeline *XZ
coiPipeline *CoreOSInstaller
bootTreePipeline *EFIBootTree
// The path where the payload (tarball or ostree repo) will be stored.
PayloadPath string
isoLabel string
// Enable ISOLinux stage
ISOLinux bool
KernelOpts []string
}
func NewCoreOSISOTree(m *Manifest,
buildPipeline *Build,
payloadPipeline *XZ,
coiPipeline *CoreOSInstaller,
bootTreePipeline *EFIBootTree,
isoLabel string) *CoreOSISOTree {
p := &CoreOSISOTree{
Base: NewBase(m, "bootiso-tree", buildPipeline),
payloadPipeline: payloadPipeline,
coiPipeline: coiPipeline,
bootTreePipeline: bootTreePipeline,
isoLabel: isoLabel,
}
buildPipeline.addDependent(p)
if coiPipeline.Base.manifest != m {
panic("anaconda pipeline from different manifest")
}
m.addPipeline(p)
return p
}
func (p *CoreOSISOTree) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://file/%s", p.payloadPipeline.Filename),
To: fmt.Sprintf("tree://%s", p.PayloadPath),
},
},
},
osbuild.NewXzStageInputs(osbuild.NewFilesInputPipelineObjectRef(p.payloadPipeline.Name(), p.payloadPipeline.Filename, nil)),
))
if p.coiPipeline.Ignition != nil {
filename := ""
copyInput := ""
// These specific filenames in the root of the ISO are expected by
// coreos-installer-dracut during installation
if p.coiPipeline.Ignition.Config != "" {
filename = "ignition_config"
copyInput = p.coiPipeline.Ignition.Config
}
pipeline.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://inlinefile/sha256:%x", sha256.Sum256([]byte(copyInput))),
To: fmt.Sprintf("tree:///%s", filename),
},
},
},
osbuild.NewIgnitionInlineInput(copyInput)))
}
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: "images",
},
{
Path: "images/pxeboot",
},
},
}))
filename := "images/efiboot.img"
pipeline.AddStage(osbuild.NewTruncateStage(&osbuild.TruncateStageOptions{
Filename: filename,
Size: fmt.Sprintf("%d", p.PartitionTable.Size),
}))
efibootDevice := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: filename})
for _, stage := range osbuild.GenMkfsStages(p.PartitionTable, efibootDevice) {
pipeline.AddStage(stage)
}
inputName := "root-tree"
copyInputs := osbuild.NewPipelineTreeInputs(inputName, p.bootTreePipeline.Name())
copyOptions, copyDevices, copyMounts := osbuild.GenCopyFSTreeOptions(inputName, p.bootTreePipeline.Name(), filename, p.PartitionTable)
pipeline.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts))
inputName = "tree"
copyStageOptions := &osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/boot/vmlinuz-%s", inputName, p.coiPipeline.kernelVer),
To: "tree:///images/pxeboot/vmlinuz",
},
{
From: fmt.Sprintf("input://%s/boot/initramfs-%s.img", inputName, p.coiPipeline.kernelVer),
To: "tree:///images/pxeboot/initrd.img",
},
},
}
copyStageInputs := osbuild.NewPipelineTreeInputs(inputName, p.coiPipeline.Name())
copyStage := osbuild.NewCopyStageSimple(copyStageOptions, copyStageInputs)
pipeline.AddStage(copyStage)
if p.ISOLinux {
isoLinuxOptions := &osbuild.ISOLinuxStageOptions{
Product: osbuild.ISOLinuxProduct{
Name: p.coiPipeline.product,
Version: p.coiPipeline.version,
},
Kernel: osbuild.ISOLinuxKernel{
Dir: "/images/pxeboot",
Opts: p.KernelOpts,
},
}
isoLinuxStage := osbuild.NewISOLinuxStage(isoLinuxOptions, p.coiPipeline.Name())
pipeline.AddStage(isoLinuxStage)
}
copyInputs = osbuild.NewPipelineTreeInputs(inputName, p.bootTreePipeline.Name())
pipeline.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/EFI", inputName),
To: "tree:///",
},
},
},
copyInputs,
))
return pipeline
}

View file

@ -0,0 +1,72 @@
package manifest
import (
"github.com/osbuild/images/pkg/osbuild"
)
// OSTreeCommit represents an ostree with one commit.
type OSTreeCommit struct {
Base
OSVersion string
treePipeline *OS
ref string
}
// NewOSTreeCommit creates a new OSTree commit pipeline. The
// treePipeline is the tree representing the content of the commit.
// ref is the ref to create the commit under.
func NewOSTreeCommit(m *Manifest,
buildPipeline *Build,
treePipeline *OS,
ref string) *OSTreeCommit {
p := &OSTreeCommit{
Base: NewBase(m, "ostree-commit", buildPipeline),
treePipeline: treePipeline,
ref: ref,
}
if treePipeline.Base.manifest != m {
panic("tree pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *OSTreeCommit) getBuildPackages(Distro) []string {
packages := []string{
"rpm-ostree",
}
return packages
}
func (p *OSTreeCommit) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
if p.treePipeline.OSTreeRef == "" {
panic("tree is not ostree")
}
pipeline.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: "/repo"}))
var parentID string
treeCommits := p.treePipeline.getOSTreeCommits()
if len(treeCommits) > 0 {
if len(treeCommits) > 1 {
panic("multiple ostree commit specs found; this is a programming error")
}
parentCommit := &treeCommits[0]
parentID = parentCommit.Checksum
}
pipeline.AddStage(osbuild.NewOSTreeCommitStage(
&osbuild.OSTreeCommitStageOptions{
Ref: p.ref,
OSVersion: p.OSVersion,
Parent: parentID,
},
p.treePipeline.Name()),
)
return pipeline
}

View file

@ -0,0 +1,153 @@
package manifest
import (
"path/filepath"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
)
// An OSTreeCommitServer contains an nginx server serving
// an embedded ostree commit.
type OSTreeCommitServer struct {
Base
// Packages to install in addition to the ones required by the
// pipeline.
ExtraPackages []string
// Extra repositories to install packages from
ExtraRepos []rpmmd.RepoConfig
// TODO: should this be configurable?
Language string
platform platform.Platform
repos []rpmmd.RepoConfig
packageSpecs []rpmmd.PackageSpec
commitPipeline *OSTreeCommit
nginxConfigPath string
listenPort string
}
// NewOSTreeCommitServer creates a new pipeline. The content
// is built from repos and packages, which must contain nginx. commitPipeline
// is a pipeline producing an ostree commit to be served. nginxConfigPath
// is the path to the main nginx config file and listenPort is the port
// nginx will be listening on.
func NewOSTreeCommitServer(m *Manifest,
buildPipeline *Build,
platform platform.Platform,
repos []rpmmd.RepoConfig,
commitPipeline *OSTreeCommit,
nginxConfigPath,
listenPort string) *OSTreeCommitServer {
name := "container-tree"
p := &OSTreeCommitServer{
Base: NewBase(m, name, buildPipeline),
platform: platform,
repos: filterRepos(repos, name),
commitPipeline: commitPipeline,
nginxConfigPath: nginxConfigPath,
listenPort: listenPort,
Language: "en_US",
}
if commitPipeline.Base.manifest != m {
panic("commit pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *OSTreeCommitServer) getPackageSetChain(Distro) []rpmmd.PackageSet {
// FIXME: container package is defined here
packages := []string{"nginx"}
return []rpmmd.PackageSet{
{
Include: append(packages, p.ExtraPackages...),
Repositories: append(p.repos, p.ExtraRepos...),
},
}
}
func (p *OSTreeCommitServer) getBuildPackages(Distro) []string {
packages := []string{
"rpm",
"rpm-ostree",
}
return packages
}
func (p *OSTreeCommitServer) getPackageSpecs() []rpmmd.PackageSpec {
return p.packageSpecs
}
func (p *OSTreeCommitServer) serializeStart(packages []rpmmd.PackageSpec, _ []container.Spec, _ []ostree.CommitSpec) {
if len(p.packageSpecs) > 0 {
panic("double call to serializeStart()")
}
p.packageSpecs = packages
}
func (p *OSTreeCommitServer) serializeEnd() {
if len(p.packageSpecs) == 0 {
panic("serializeEnd() call when serialization not in progress")
}
p.packageSpecs = nil
}
func (p *OSTreeCommitServer) serialize() osbuild.Pipeline {
if len(p.packageSpecs) == 0 {
panic("serialization not started")
}
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewRPMStage(osbuild.NewRPMStageOptions(p.repos), osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs)))
pipeline.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: p.Language}))
htmlRoot := "/usr/share/nginx/html"
repoPath := filepath.Join(htmlRoot, "repo")
pipeline.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: repoPath}))
pipeline.AddStage(osbuild.NewOSTreePullStage(
&osbuild.OSTreePullStageOptions{Repo: repoPath},
osbuild.NewOstreePullStageInputs("org.osbuild.pipeline", "name:"+p.commitPipeline.Name(), p.commitPipeline.ref),
))
// make nginx log and lib directories world writeable, otherwise nginx can't start in
// an unprivileged container
pipeline.AddStage(osbuild.NewChmodStage(chmodStageOptions("/var/log/nginx", "a+rwX", true)))
pipeline.AddStage(osbuild.NewChmodStage(chmodStageOptions("/var/lib/nginx", "a+rwX", true)))
pipeline.AddStage(osbuild.NewNginxConfigStage(nginxConfigStageOptions(p.nginxConfigPath, htmlRoot, p.listenPort)))
return pipeline
}
func nginxConfigStageOptions(path, htmlRoot, listen string) *osbuild.NginxConfigStageOptions {
// configure nginx to work in an unprivileged container
cfg := &osbuild.NginxConfig{
Listen: listen,
Root: htmlRoot,
Daemon: common.ToPtr(false),
PID: "/tmp/nginx.pid",
}
return &osbuild.NginxConfigStageOptions{
Path: path,
Config: cfg,
}
}
func chmodStageOptions(path, mode string, recursive bool) *osbuild.ChmodStageOptions {
return &osbuild.ChmodStageOptions{
Items: map[string]osbuild.ChmodStagePathOptions{
path: {Mode: mode, Recursive: recursive},
},
}
}
func (p *OSTreeCommitServer) GetPlatform() platform.Platform {
return p.platform
}

View file

@ -0,0 +1,189 @@
package manifest
import (
"fmt"
"github.com/osbuild/images/internal/fdo"
"github.com/osbuild/images/internal/ignition"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
)
type CoreOSInstaller struct {
Base
// Packages to install in addition to the ones required by the
// pipeline.
ExtraPackages []string
// Extra repositories to install packages from
ExtraRepos []rpmmd.RepoConfig
platform platform.Platform
repos []rpmmd.RepoConfig
packageSpecs []rpmmd.PackageSpec
kernelName string
kernelVer string
product string
version string
Variant string
// Biosdevname indicates whether or not biosdevname should be used to
// name network devices when booting the installer. This may affect
// the naming of network devices on the target system.
Biosdevname bool
FDO *fdo.Options
// For the coreos-installer we only have EmbeddedOptions for ignition
Ignition *ignition.EmbeddedOptions
AdditionalDracutModules []string
}
// NewCoreOSInstaller creates an CoreOS installer pipeline object.
func NewCoreOSInstaller(m *Manifest,
buildPipeline *Build,
platform platform.Platform,
repos []rpmmd.RepoConfig,
kernelName,
product,
version string) *CoreOSInstaller {
name := "coi-tree"
p := &CoreOSInstaller{
Base: NewBase(m, name, buildPipeline),
platform: platform,
repos: filterRepos(repos, name),
kernelName: kernelName,
product: product,
version: version,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
// TODO: refactor - what is required to boot and what to build, and
// do they all belong in this pipeline?
func (p *CoreOSInstaller) getBootPackages() []string {
packages := []string{
"grub2-tools",
"grub2-tools-extra",
"grub2-tools-minimal",
"efibootmgr",
}
switch p.platform.GetArch() {
case platform.ARCH_X86_64:
packages = append(packages,
"grub2-efi-x64",
"grub2-efi-x64-cdboot",
"grub2-pc",
"grub2-pc-modules",
"shim-x64",
"syslinux",
"syslinux-nonlinux",
)
case platform.ARCH_AARCH64:
packages = append(packages,
"grub2-efi-aa64-cdboot",
"grub2-efi-aa64",
"shim-aa64",
)
default:
panic(fmt.Sprintf("unsupported arch: %s", p.platform.GetArch()))
}
return packages
}
func (p *CoreOSInstaller) getBuildPackages(Distro) []string {
packages := p.getBootPackages()
packages = append(packages,
"rpm",
"lorax-templates-generic",
)
return packages
}
func (p *CoreOSInstaller) getPackageSetChain(Distro) []rpmmd.PackageSet {
packages := p.getBootPackages()
return []rpmmd.PackageSet{
{
Include: append(packages, p.ExtraPackages...),
Repositories: append(p.repos, p.ExtraRepos...),
},
}
}
func (p *CoreOSInstaller) getPackageSpecs() []rpmmd.PackageSpec {
return p.packageSpecs
}
func (p *CoreOSInstaller) serializeStart(packages []rpmmd.PackageSpec, _ []container.Spec, _ []ostree.CommitSpec) {
if len(p.packageSpecs) > 0 {
panic("double call to serializeStart()")
}
p.packageSpecs = packages
if p.kernelName != "" {
p.kernelVer = rpmmd.GetVerStrFromPackageSpecListPanic(p.packageSpecs, p.kernelName)
}
}
func (p *CoreOSInstaller) getInline() []string {
inlineData := []string{}
// inline data for FDO cert
if p.FDO != nil && p.FDO.DiunPubKeyRootCerts != "" {
inlineData = append(inlineData, p.FDO.DiunPubKeyRootCerts)
}
// inline data for ignition embedded (url or data)
if p.Ignition != nil {
if p.Ignition.Config != "" {
inlineData = append(inlineData, p.Ignition.Config)
}
}
return inlineData
}
func (p *CoreOSInstaller) serializeEnd() {
if len(p.packageSpecs) == 0 {
panic("serializeEnd() call when serialization not in progress")
}
p.kernelVer = ""
p.packageSpecs = nil
}
func (p *CoreOSInstaller) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewRPMStage(osbuild.NewRPMStageOptions(p.repos), osbuild.NewRpmStageSourceFilesInputs(p.packageSpecs)))
pipeline.AddStage(osbuild.NewBuildstampStage(&osbuild.BuildstampStageOptions{
Arch: p.platform.GetArch().String(),
Product: p.product,
Variant: p.Variant,
Version: p.version,
Final: true,
}))
pipeline.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "C.UTF-8"}))
dracutModules := append(
p.AdditionalDracutModules,
"coreos-installer",
"fdo",
)
dracutStageOptions := dracutStageOptions(p.kernelVer, p.Biosdevname, dracutModules)
if p.FDO != nil && p.FDO.DiunPubKeyRootCerts != "" {
pipeline.AddStage(osbuild.NewFDOStageForRootCerts(p.FDO.DiunPubKeyRootCerts))
dracutStageOptions.Install = []string{"/fdo_diun_pub_key_root_certs.pem"}
}
pipeline.AddStage(osbuild.NewDracutStage(dracutStageOptions))
return pipeline
}
func (p *CoreOSInstaller) GetPlatform() platform.Platform {
return p.platform
}

View file

@ -0,0 +1,62 @@
package manifest
import (
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/platform"
)
type EFIBootTree struct {
Base
Platform platform.Platform
product string
version string
UEFIVendor string
ISOLabel string
KernelOpts []string
}
func NewEFIBootTree(m *Manifest, buildPipeline *Build, product, version string) *EFIBootTree {
p := &EFIBootTree{
Base: NewBase(m, "efiboot-tree", buildPipeline),
product: product,
version: version,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *EFIBootTree) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
arch := p.Platform.GetArch().String()
var architectures []string
if arch == platform.ARCH_X86_64.String() {
architectures = []string{"X64"}
} else if arch == platform.ARCH_AARCH64.String() {
architectures = []string{"AA64"}
} else {
panic("unsupported architecture")
}
grubOptions := &osbuild.GrubISOStageOptions{
Product: osbuild.Product{
Name: p.product,
Version: p.version,
},
Kernel: osbuild.ISOKernel{
Dir: "/images/pxeboot",
Opts: p.KernelOpts,
},
ISOLabel: p.ISOLabel,
Architectures: architectures,
Vendor: p.UEFIVendor,
}
grub2Stage := osbuild.NewGrubISOStage(grubOptions)
pipeline.AddStage(grub2Stage)
return pipeline
}

98
vendor/github.com/osbuild/images/pkg/manifest/empty.go generated vendored Normal file
View file

@ -0,0 +1,98 @@
package manifest
import (
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/rpmmd"
)
// A ContentTest can be used to define content sources without generating
// pipelines. It is useful for testing but not much else.
type ContentTest struct {
Base
// content sources
packageSets []rpmmd.PackageSet
containers []container.SourceSpec
commits []ostree.SourceSpec
// resolved content
packageSpecs []rpmmd.PackageSpec
containerSpecs []container.Spec
commitSpecs []ostree.CommitSpec
// serialization flag
serializing bool
}
// NewContentTest creates a new ContentTest pipeline with a given name and
// content sources.
func NewContentTest(m *Manifest, name string, packageSets []rpmmd.PackageSet, containers []container.SourceSpec, commits []ostree.SourceSpec) *ContentTest {
pipeline := &ContentTest{
Base: NewBase(m, name, nil),
packageSets: packageSets,
containers: containers,
commits: commits,
}
m.addPipeline(pipeline)
return pipeline
}
func (p *ContentTest) getPackageSetChain(Distro) []rpmmd.PackageSet {
return p.packageSets
}
func (p *ContentTest) getContainerSources() []container.SourceSpec {
return p.containers
}
func (p *ContentTest) getOSTreeCommitSources() []ostree.SourceSpec {
return p.commits
}
func (p *ContentTest) getPackageSpecs() []rpmmd.PackageSpec {
return p.packageSpecs
}
func (p *ContentTest) getContainerSpecs() []container.Spec {
return p.containerSpecs
}
func (p *ContentTest) getOSTreeCommits() []ostree.CommitSpec {
return p.commitSpecs
}
func (p *ContentTest) serializeStart(pkgs []rpmmd.PackageSpec, containers []container.Spec, commits []ostree.CommitSpec) {
if p.serializing {
panic("double call to serializeStart()")
}
p.packageSpecs = pkgs
p.containerSpecs = containers
p.commitSpecs = commits
p.serializing = true
}
func (p *ContentTest) serializeEnd() {
if !p.serializing {
panic("serializeEnd() call when serialization not in progress")
}
p.packageSpecs = nil
p.containerSpecs = nil
p.commitSpecs = nil
p.serializing = false
}
func (p *ContentTest) serialize() osbuild.Pipeline {
if !p.serializing {
panic("serialization not started")
}
// no stages
return osbuild.Pipeline{
Name: p.name,
}
}

75
vendor/github.com/osbuild/images/pkg/manifest/iso.go generated vendored Normal file
View file

@ -0,0 +1,75 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
)
// An ISO represents a bootable ISO file created from an
// an existing ISOTreePipeline.
type ISO struct {
Base
ISOLinux bool
Filename string
treePipeline Pipeline
isoLabel string
}
func NewISO(m *Manifest,
buildPipeline *Build,
treePipeline Pipeline,
isoLabel string) *ISO {
p := &ISO{
Base: NewBase(m, "bootiso", buildPipeline),
treePipeline: treePipeline,
Filename: "image.iso",
isoLabel: isoLabel,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *ISO) getBuildPackages(Distro) []string {
return []string{
"isomd5sum",
"xorriso",
}
}
func (p *ISO) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewXorrisofsStage(xorrisofsStageOptions(p.Filename, p.isoLabel, p.ISOLinux), p.treePipeline.Name()))
pipeline.AddStage(osbuild.NewImplantisomd5Stage(&osbuild.Implantisomd5StageOptions{Filename: p.Filename}))
return pipeline
}
func xorrisofsStageOptions(filename, isolabel string, isolinux bool) *osbuild.XorrisofsStageOptions {
options := &osbuild.XorrisofsStageOptions{
Filename: filename,
VolID: isolabel,
SysID: "LINUX",
EFI: "images/efiboot.img",
ISOLevel: 3,
}
if isolinux {
options.Boot = &osbuild.XorrisofsBoot{
Image: "isolinux/isolinux.bin",
Catalog: "isolinux/boot.cat",
}
options.IsohybridMBR = "/usr/share/syslinux/isohdpfx.bin"
}
return options
}
func (p *ISO) Export() *artifact.Artifact {
p.Base.export = true
mimeType := "application/x-iso9660-image"
return artifact.New(p.Name(), p.Filename, &mimeType)
}

View file

@ -0,0 +1,71 @@
package manifest
import (
"fmt"
"github.com/osbuild/images/pkg/osbuild"
)
type ISORootfsImg struct {
Base
Size uint64
installerPipeline Pipeline
}
func NewISORootfsImg(m *Manifest, buildPipeline *Build, installerPipeline Pipeline) *ISORootfsImg {
p := &ISORootfsImg{
Base: NewBase(m, "rootfs-image", buildPipeline),
installerPipeline: installerPipeline,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *ISORootfsImg) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: "LiveOS",
},
},
}))
pipeline.AddStage(osbuild.NewTruncateStage(&osbuild.TruncateStageOptions{
Filename: "LiveOS/rootfs.img",
Size: fmt.Sprintf("%d", p.Size),
}))
mkfsStageOptions := &osbuild.MkfsExt4StageOptions{
UUID: "2fe99653-f7ff-44fd-bea8-fa70107524fb",
Label: "Anaconda",
}
lodevice := osbuild.NewLoopbackDevice(
&osbuild.LoopbackDeviceOptions{
Filename: "LiveOS/rootfs.img",
},
)
devName := "device"
devices := osbuild.Devices{devName: *lodevice}
mkfsStage := osbuild.NewMkfsExt4Stage(mkfsStageOptions, devices)
pipeline.AddStage(mkfsStage)
inputName := "tree"
copyStageOptions := &osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/", inputName),
To: fmt.Sprintf("mount://%s/", devName),
},
},
}
copyStageInputs := osbuild.NewPipelineTreeInputs(inputName, p.installerPipeline.Name())
copyStageMounts := &osbuild.Mounts{*osbuild.NewExt4Mount(devName, devName, "/")}
copyStage := osbuild.NewCopyStage(copyStageOptions, copyStageInputs, &devices, copyStageMounts)
pipeline.AddStage(copyStage)
return pipeline
}

View file

@ -0,0 +1,199 @@
// Package manifest is used to define an osbuild manifest as a series of
// pipelines with content. Typically, a Manifest is created using
// manifest.New() and pipelines are defined and added to it using the pipeline
// constructors (e.g., NewBuild()) with the manifest as the first argument. The
// pipelines are added in the order they are called.
//
// The package implements a standard set of osbuild pipelines. A pipeline
// conceptually represents a named filesystem tree, optionally generated
// in a provided build root (represented by another pipeline). All inputs
// to a pipeline must be explicitly specified, either in terms of another
// pipeline, in terms of content addressable inputs or in terms of static
// parameters to the inherited Pipeline structs.
package manifest
import (
"encoding/json"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/rpmmd"
)
type Arch uint64
const (
ARCH_X86_64 Arch = iota
ARCH_AARCH64
ARCH_S390X
ARCH_PPC64LE
)
type Distro uint64
const (
DISTRO_NULL = iota
DISTRO_EL9
DISTRO_EL8
DISTRO_EL7
DISTRO_FEDORA
)
// An OSBuildManifest is an opaque JSON object, which is a valid input to osbuild
type OSBuildManifest []byte
func (m OSBuildManifest) MarshalJSON() ([]byte, error) {
return json.RawMessage(m).MarshalJSON()
}
func (m *OSBuildManifest) UnmarshalJSON(payload []byte) error {
var raw json.RawMessage
err := (&raw).UnmarshalJSON(payload)
if err != nil {
return err
}
*m = OSBuildManifest(raw)
return nil
}
// Manifest represents a manifest initialised with all the information required
// to generate the pipelines but no content. The content type sources
// (PackageSetChains, ContainerSourceSpecs, OSTreeSourceSpecs) must be
// retrieved through their corresponding Getters and resolved before
// serializing.
type Manifest struct {
// pipelines describe the build process for an image.
pipelines []Pipeline
// Distro defines the distribution of the image that this manifest will
// generate. It is used for determining package names that differ between
// different distributions and version.
Distro Distro
}
func New() Manifest {
return Manifest{
pipelines: make([]Pipeline, 0),
Distro: DISTRO_NULL,
}
}
func (m *Manifest) addPipeline(p Pipeline) {
for _, pipeline := range m.pipelines {
if pipeline.Name() == p.Name() {
panic("duplicate pipeline name in manifest")
}
}
m.pipelines = append(m.pipelines, p)
}
type PackageSelector func([]rpmmd.PackageSet) []rpmmd.PackageSet
func (m Manifest) GetPackageSetChains() map[string][]rpmmd.PackageSet {
chains := make(map[string][]rpmmd.PackageSet)
for _, pipeline := range m.pipelines {
if chain := pipeline.getPackageSetChain(m.Distro); chain != nil {
chains[pipeline.Name()] = chain
}
}
return chains
}
func (m Manifest) GetContainerSourceSpecs() map[string][]container.SourceSpec {
// Containers should only appear in the payload pipeline.
// Let's iterate over all pipelines to avoid assuming pipeline names, but
// return all the specs as a single slice.
containerSpecs := make(map[string][]container.SourceSpec)
for _, pipeline := range m.pipelines {
if containers := pipeline.getContainerSources(); len(containers) > 0 {
containerSpecs[pipeline.Name()] = containers
}
}
return containerSpecs
}
func (m Manifest) GetOSTreeSourceSpecs() map[string][]ostree.SourceSpec {
// OSTree commits should only appear in one pipeline.
// Let's iterate over all pipelines to avoid assuming pipeline names, but
// return all the specs as a single slice if there are multiple.
ostreeSpecs := make(map[string][]ostree.SourceSpec)
for _, pipeline := range m.pipelines {
if commits := pipeline.getOSTreeCommitSources(); len(commits) > 0 {
ostreeSpecs[pipeline.Name()] = commits
}
}
return ostreeSpecs
}
func (m Manifest) Serialize(packageSets map[string][]rpmmd.PackageSpec, containerSpecs map[string][]container.Spec, ostreeCommits map[string][]ostree.CommitSpec) (OSBuildManifest, error) {
pipelines := make([]osbuild.Pipeline, 0)
packages := make([]rpmmd.PackageSpec, 0)
commits := make([]ostree.CommitSpec, 0)
inline := make([]string, 0)
containers := make([]container.Spec, 0)
for _, pipeline := range m.pipelines {
pipeline.serializeStart(packageSets[pipeline.Name()], containerSpecs[pipeline.Name()], ostreeCommits[pipeline.Name()])
}
for _, pipeline := range m.pipelines {
commits = append(commits, pipeline.getOSTreeCommits()...)
pipelines = append(pipelines, pipeline.serialize())
packages = append(packages, packageSets[pipeline.Name()]...)
inline = append(inline, pipeline.getInline()...)
containers = append(containers, pipeline.getContainerSpecs()...)
}
for _, pipeline := range m.pipelines {
pipeline.serializeEnd()
}
return json.Marshal(
osbuild.Manifest{
Version: "2",
Pipelines: pipelines,
Sources: osbuild.GenSources(packages, commits, inline, containers),
},
)
}
func (m Manifest) GetCheckpoints() []string {
checkpoints := []string{}
for _, p := range m.pipelines {
if p.getCheckpoint() {
checkpoints = append(checkpoints, p.Name())
}
}
return checkpoints
}
func (m Manifest) GetExports() []string {
exports := []string{}
for _, p := range m.pipelines {
if p.getExport() {
exports = append(exports, p.Name())
}
}
return exports
}
// filterRepos returns a list of repositories that specify the given pipeline
// name in their PackageSets list in addition to any global repositories
// (global repositories are ones that do not specify any PackageSets).
func filterRepos(repos []rpmmd.RepoConfig, plName string) []rpmmd.RepoConfig {
filtered := make([]rpmmd.RepoConfig, 0, len(repos))
for _, repo := range repos {
if len(repo.PackageSets) == 0 {
filtered = append(filtered, repo)
continue
}
for _, ps := range repo.PackageSets {
if ps == plName {
filtered = append(filtered, repo)
continue
}
}
}
return filtered
}

View file

@ -0,0 +1,61 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
)
// An OCIContainer represents an OCI container, containing a filesystem
// tree created by another Pipeline.
type OCIContainer struct {
Base
Filename string
Cmd []string
ExposedPorts []string
treePipeline Tree
}
func NewOCIContainer(m *Manifest,
buildPipeline *Build,
treePipeline Tree) *OCIContainer {
p := &OCIContainer{
Base: NewBase(m, "container", buildPipeline),
treePipeline: treePipeline,
Filename: "oci-archive.tar",
}
if treePipeline.GetManifest() != m {
panic("tree pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *OCIContainer) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
options := &osbuild.OCIArchiveStageOptions{
Architecture: p.treePipeline.GetPlatform().GetArch().String(),
Filename: p.Filename,
Config: &osbuild.OCIArchiveConfig{
Cmd: p.Cmd,
ExposedPorts: p.ExposedPorts,
},
}
baseInput := osbuild.NewTreeInput("name:" + p.treePipeline.Name())
inputs := &osbuild.OCIArchiveStageInputs{Base: baseInput}
pipeline.AddStage(osbuild.NewOCIArchiveStage(options, inputs))
return pipeline
}
func (p *OCIContainer) getBuildPackages(Distro) []string {
return []string{"tar"}
}
func (p *OCIContainer) Export() *artifact.Artifact {
p.Base.export = true
mimeType := "application/x-tar"
return artifact.New(p.Name(), p.Filename, &mimeType)
}

786
vendor/github.com/osbuild/images/pkg/manifest/os.go generated vendored Normal file
View file

@ -0,0 +1,786 @@
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
}

View file

@ -0,0 +1,348 @@
package manifest
import (
"os"
"strings"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/internal/fsnode"
"github.com/osbuild/images/internal/users"
"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/rpmmd"
)
// OSTreeDeployment represents the filesystem tree of a target image based
// on a deployed ostree commit.
type OSTreeDeployment struct {
Base
Remote ostree.Remote
OSVersion string
commitSource ostree.SourceSpec
ostreeSpecs []ostree.CommitSpec
SysrootReadOnly bool
osName string
KernelOptionsAppend []string
Keyboard string
Locale string
Users []users.User
Groups []users.Group
platform platform.Platform
PartitionTable *disk.PartitionTable
// Whether ignition is in use or not
ignition bool
Directories []*fsnode.Directory
Files []*fsnode.File
EnabledServices []string
DisabledServices []string
}
// NewOSTreeDeployment creates a pipeline for an ostree deployment from a
// commit.
func NewOSTreeDeployment(m *Manifest,
buildPipeline *Build,
commit ostree.SourceSpec,
osName string,
ignition bool,
platform platform.Platform) *OSTreeDeployment {
p := &OSTreeDeployment{
Base: NewBase(m, "ostree-deployment", buildPipeline),
commitSource: commit,
osName: osName,
platform: platform,
ignition: ignition,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *OSTreeDeployment) getBuildPackages(Distro) []string {
packages := []string{
"rpm-ostree",
}
return packages
}
func (p *OSTreeDeployment) getOSTreeCommits() []ostree.CommitSpec {
return p.ostreeSpecs
}
func (p *OSTreeDeployment) getOSTreeCommitSources() []ostree.SourceSpec {
return []ostree.SourceSpec{
p.commitSource,
}
}
func (p *OSTreeDeployment) serializeStart(packages []rpmmd.PackageSpec, containers []container.Spec, commits []ostree.CommitSpec) {
if len(p.ostreeSpecs) > 0 {
panic("double call to serializeStart()")
}
if len(commits) != 1 {
panic("pipeline requires exactly one ostree commit")
}
p.ostreeSpecs = commits
}
func (p *OSTreeDeployment) serializeEnd() {
if len(p.ostreeSpecs) == 0 {
panic("serializeEnd() call when serialization not in progress")
}
p.ostreeSpecs = nil
}
func (p *OSTreeDeployment) serialize() osbuild.Pipeline {
if len(p.ostreeSpecs) == 0 {
panic("serialization not started")
}
if len(p.ostreeSpecs) > 1 {
panic("multiple ostree commit specs found; this is a programming error")
}
commit := p.ostreeSpecs[0]
const repoPath = "/ostree/repo"
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.OSTreeInitFsStage())
pipeline.AddStage(osbuild.NewOSTreePullStage(
&osbuild.OSTreePullStageOptions{Repo: repoPath, Remote: p.Remote.Name},
osbuild.NewOstreePullStageInputs("org.osbuild.source", commit.Checksum, commit.Ref),
))
pipeline.AddStage(osbuild.NewOSTreeOsInitStage(
&osbuild.OSTreeOsInitStageOptions{
OSName: p.osName,
},
))
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: "/boot/efi",
Mode: common.ToPtr(os.FileMode(0700)),
},
},
}))
kernelOpts := osbuild.GenImageKernelOptions(p.PartitionTable)
kernelOpts = append(kernelOpts, p.KernelOptionsAppend...)
if p.ignition {
kernelOpts = append(kernelOpts,
"coreos.no_persist_ip", // users cannot add connections as we don't have a live iso, this prevents connections to bleed into the system from the ign initrd
"ignition.platform.id=metal",
"$ignition_firstboot",
)
}
pipeline.AddStage(osbuild.NewOSTreeDeployStage(
&osbuild.OSTreeDeployStageOptions{
OsName: p.osName,
Ref: commit.Ref,
Remote: p.Remote.Name,
Mounts: []string{"/boot", "/boot/efi"},
Rootfs: osbuild.Rootfs{
Label: "root",
},
KernelOpts: kernelOpts,
},
))
remoteURL := p.Remote.URL
if remoteURL == "" {
// if the remote URL for the image is not specified, use the source commit URL
remoteURL = commit.URL
}
pipeline.AddStage(osbuild.NewOSTreeRemotesStage(
&osbuild.OSTreeRemotesStageOptions{
Repo: "/ostree/repo",
Remotes: []osbuild.OSTreeRemote{
{
Name: p.Remote.Name,
URL: remoteURL,
ContentURL: p.Remote.ContentURL,
GPGKeyPaths: p.Remote.GPGKeyPaths,
},
},
},
))
pipeline.AddStage(osbuild.NewOSTreeFillvarStage(
&osbuild.OSTreeFillvarStageOptions{
Deployment: osbuild.OSTreeDeployment{
OSName: p.osName,
Ref: commit.Ref,
},
},
))
configStage := osbuild.NewOSTreeConfigStage(
&osbuild.OSTreeConfigStageOptions{
Repo: repoPath,
Config: &osbuild.OSTreeConfig{
Sysroot: &osbuild.SysrootOptions{
ReadOnly: &p.SysrootReadOnly,
Bootloader: "none",
},
},
},
)
configStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(configStage)
fstabOptions := osbuild.NewFSTabStageOptions(p.PartitionTable)
fstabStage := osbuild.NewFSTabStage(fstabOptions)
fstabStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(fstabStage)
if len(p.Users) > 0 {
usersStage, err := osbuild.GenUsersStage(p.Users, false)
if err != nil {
panic("password encryption failed")
}
usersStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(usersStage)
}
if len(p.Groups) > 0 {
grpStage := osbuild.GenGroupsStage(p.Groups)
grpStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(grpStage)
}
if p.ignition {
pipeline.AddStage(osbuild.NewIgnitionStage(&osbuild.IgnitionStageOptions{
// This is a workaround to make the systemd believe it's firstboot when ignition runs on real firstboot.
// Right now, since we ship /etc/machine-id, systemd thinks it's not firstboot and ignition depends on it
// to run on the real firstboot to enable services from presets.
// Since this only applies to artifacts with ignition and changing machineid-compat at commit creation time may
// have undesiderable effect, we're doing it here as a stopgap. We may revisit this in the future.
Network: []string{
"systemd.firstboot=off",
"systemd.condition-first-boot=true",
},
}))
}
// if no root password is set, lock the root account
hasRoot := false
for _, user := range p.Users {
if user.Name == "root" {
hasRoot = true
break
}
}
if !hasRoot {
userOptions := &osbuild.UsersStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"root": {
Password: common.ToPtr("!locked"), // this is treated as crypted and locks/disables the password
},
},
}
rootLockStage := osbuild.NewUsersStage(userOptions)
rootLockStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(rootLockStage)
}
if p.Keyboard != "" {
options := &osbuild.KeymapStageOptions{
Keymap: p.Keyboard,
}
keymapStage := osbuild.NewKeymapStage(options)
keymapStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(keymapStage)
}
if p.Locale != "" {
options := &osbuild.LocaleStageOptions{
Language: p.Locale,
}
localeStage := osbuild.NewLocaleStage(options)
localeStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(localeStage)
}
grubOptions := osbuild.NewGrub2StageOptionsUnified(p.PartitionTable,
strings.Join(kernelOpts, " "),
"",
p.platform.GetUEFIVendor() != "",
p.platform.GetBIOSPlatform(),
p.platform.GetUEFIVendor(), true)
grubOptions.Greenboot = true
grubOptions.Ignition = p.ignition
grubOptions.Config = &osbuild.GRUB2Config{
Default: "saved",
Timeout: 1,
TerminalOutput: []string{"console"},
}
bootloader := osbuild.NewGRUB2Stage(grubOptions)
bootloader.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(bootloader)
// First create custom directories, because some of the files may depend on them
if len(p.Directories) > 0 {
dirStages := osbuild.GenDirectoryNodesStages(p.Directories)
for _, stage := range dirStages {
stage.MountOSTree(p.osName, commit.Ref, 0)
}
pipeline.AddStages(dirStages...)
}
if len(p.Files) > 0 {
fileStages := osbuild.GenFileNodesStages(p.Files)
for _, stage := range fileStages {
stage.MountOSTree(p.osName, commit.Ref, 0)
}
pipeline.AddStages(fileStages...)
}
if len(p.EnabledServices) != 0 || len(p.DisabledServices) != 0 {
systemdStage := osbuild.NewSystemdStage(&osbuild.SystemdStageOptions{
EnabledServices: p.EnabledServices,
DisabledServices: p.DisabledServices,
})
systemdStage.MountOSTree(p.osName, commit.Ref, 0)
pipeline.AddStage(systemdStage)
}
pipeline.AddStage(osbuild.NewOSTreeSelinuxStage(
&osbuild.OSTreeSelinuxStageOptions{
Deployment: osbuild.OSTreeDeployment{
OSName: p.osName,
Ref: commit.Ref,
},
},
))
return pipeline
}
func (p *OSTreeDeployment) getInline() []string {
inlineData := []string{}
// inline data for custom files
for _, file := range p.Files {
inlineData = append(inlineData, string(file.Data()))
}
return inlineData
}

57
vendor/github.com/osbuild/images/pkg/manifest/ovf.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
package manifest
import (
"fmt"
"github.com/osbuild/images/pkg/osbuild"
)
// A OVF copies a vmdk image to it's own tree and generates an OVF descriptor
type OVF struct {
Base
imgPipeline *VMDK
}
// NewOVF creates a new OVF pipeline. imgPipeline is the pipeline producing the vmdk image.
func NewOVF(m *Manifest,
buildPipeline *Build,
imgPipeline *VMDK) *OVF {
p := &OVF{
Base: NewBase(m, "ovf", buildPipeline),
imgPipeline: imgPipeline,
}
if imgPipeline.Base.manifest != m {
panic("live image pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *OVF) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
inputName := "vmdk-tree"
pipeline.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
osbuild.CopyStagePath{
From: fmt.Sprintf("input://%s/%s", inputName, p.imgPipeline.Export().Filename()),
To: "tree:///",
},
},
},
osbuild.NewPipelineTreeInputs(inputName, p.imgPipeline.Name()),
))
pipeline.AddStage(osbuild.NewOVFStage(&osbuild.OVFStageOptions{
Vmdk: p.imgPipeline.Filename,
}))
return pipeline
}
func (p *OVF) getBuildPackages(Distro) []string {
return []string{"qemu-img"}
}

View file

@ -0,0 +1,183 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
)
// Pipeline serializes to a series of stages that modify a file system tree
// when used as input to osbuild. Different Pipelines serialize to different
// sequences of stages depending on their type and configuration.
type Pipeline interface {
// Name of the pipeline.
Name() string
// Checkpoint this pipeline when osbuild is called.
Checkpoint()
// Export this tree of this pipeline as an artifact when osbuild is called.
Export() *artifact.Artifact
getCheckpoint() bool
getExport() bool
// getBuildPackages returns the list of packages required for the pipeline
// at build time.
getBuildPackages(Distro) []string
// getPackageSetChain returns the list of package names to be required by
// the pipeline. Each set should be depsolved sequentially to resolve
// dependencies and full package specs. See the dnfjson package for more
// details.
getPackageSetChain(Distro) []rpmmd.PackageSet
// getContainerSources returns the list of containers sources to be resolved and
// embedded by the pipeline. Each source should be resolved to its full
// Spec. See the container package for more details.
getContainerSources() []container.SourceSpec
// getOSTreeCommitSources returns the list of ostree commit sources to be
// resolved and added to the pipeline. Each source should be resolved to
// its full Spec. See the ostree package for more details.
getOSTreeCommitSources() []ostree.SourceSpec
serializeStart([]rpmmd.PackageSpec, []container.Spec, []ostree.CommitSpec)
serializeEnd()
serialize() osbuild.Pipeline
// getPackageSpecs returns the list of specifications for packages that
// will be installed to the pipeline tree.
getPackageSpecs() []rpmmd.PackageSpec
// getContainerSpecs returns the list of specifications for the containers
// that will be installed to the pipeline tree.
getContainerSpecs() []container.Spec
// getOSTreeCommits returns the list of specifications for the commits
// required by the pipeline.
getOSTreeCommits() []ostree.CommitSpec
// getInline returns the list of inlined data content that will be used to
// embed files in the pipeline tree.
getInline() []string
}
// A Base represents the core functionality shared between each of the pipeline
// implementations, and the Base struct must be embedded in each of them.
type Base struct {
manifest *Manifest
name string
build *Build
checkpoint bool
export bool
}
// Name returns the name of the pipeline. The name must be unique for a given manifest.
// Pipeline names are used to refer to pipelines either as dependencies between pipelines
// or for exporting them.
func (p Base) Name() string {
return p.name
}
func (p *Base) Checkpoint() {
p.checkpoint = true
}
func (p Base) getCheckpoint() bool {
return p.checkpoint
}
func (p *Base) Export() *artifact.Artifact {
panic("can't export pipeline")
}
func (p Base) getExport() bool {
return p.export
}
func (p Base) GetManifest() *Manifest {
return p.manifest
}
func (p Base) getBuildPackages(Distro) []string {
return []string{}
}
func (p Base) getPackageSetChain(Distro) []rpmmd.PackageSet {
return nil
}
func (p Base) getContainerSources() []container.SourceSpec {
return nil
}
func (p Base) getOSTreeCommitSources() []ostree.SourceSpec {
return nil
}
func (p Base) getPackageSpecs() []rpmmd.PackageSpec {
return []rpmmd.PackageSpec{}
}
func (p Base) getOSTreeCommits() []ostree.CommitSpec {
return nil
}
func (p Base) getContainerSpecs() []container.Spec {
return nil
}
func (p Base) getInline() []string {
return []string{}
}
// NewBase returns a generic Pipeline object. The name is mandatory, immutable and must
// be unique among all the pipelines used in a manifest, which is currently not enforced.
// The build argument is a pipeline representing a build root in which the rest of the
// pipeline is built. In order to ensure reproducibility a build pipeline must always be
// provided, except for int he build pipeline itself. When a build pipeline is not provided
// the build host's filesystem is used as the build root. The runner specifies how to use this
// pipeline as a build pipeline, by naming the distro it contains. When the host system is used
// as a build root, then the necessary runner is autodetected.
func NewBase(m *Manifest, name string, build *Build) Base {
p := Base{
manifest: m,
name: name,
build: build,
}
if build != nil {
if build.Base.manifest != m {
panic("build pipeline from a different manifest")
}
}
return p
}
// serializeStart must be called exactly once before each call
// to serialize().
func (p Base) serializeStart([]rpmmd.PackageSpec, []container.Spec, []ostree.CommitSpec) {
}
// serializeEnd must be called exactly once after each call to
// serialize().
func (p Base) serializeEnd() {
}
// Serialize turns a given pipeline into an osbuild.Pipeline object. This object is
// meant to be treated as opaque and not to be modified further outside of the pipeline
// package.
func (p Base) serialize() osbuild.Pipeline {
pipeline := osbuild.Pipeline{
Name: p.name,
}
if p.build != nil {
pipeline.Build = "name:" + p.build.Name()
}
return pipeline
}
type Tree interface {
Name() string
GetManifest() *Manifest
GetPlatform() platform.Platform
}

59
vendor/github.com/osbuild/images/pkg/manifest/qcow2.go generated vendored Normal file
View file

@ -0,0 +1,59 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
)
// A QCOW2 turns a raw image file into qcow2 image.
type QCOW2 struct {
Base
Filename string
Compat string
imgPipeline *RawImage
}
// NewQCOW2 createsa new QCOW2 pipeline. imgPipeline is the pipeline producing the
// raw image. The pipeline name is the name of the new pipeline. Filename is the name
// of the produced qcow2 image.
func NewQCOW2(m *Manifest,
buildPipeline *Build,
imgPipeline *RawImage) *QCOW2 {
p := &QCOW2{
Base: NewBase(m, "qcow2", buildPipeline),
imgPipeline: imgPipeline,
Filename: "image.qcow2",
}
if imgPipeline.Base.manifest != m {
panic("live image pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *QCOW2) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewQEMUStage(
osbuild.NewQEMUStageOptions(p.Filename,
osbuild.QEMUFormatQCOW2,
osbuild.QCOW2Options{
Compat: p.Compat,
}),
osbuild.NewQemuStagePipelineFilesInputs(p.imgPipeline.Name(), p.imgPipeline.Filename),
))
return pipeline
}
func (p *QCOW2) getBuildPackages(Distro) []string {
return []string{"qemu-img"}
}
func (p *QCOW2) Export() *artifact.Artifact {
p.Base.export = true
mimeType := "application/x-qemu-disk"
return artifact.New(p.Name(), p.Filename, &mimeType)
}

80
vendor/github.com/osbuild/images/pkg/manifest/raw.go generated vendored Normal file
View file

@ -0,0 +1,80 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/platform"
)
// A RawImage represents a raw image file which can be booted in a
// hypervisor. It is created from an existing OSPipeline.
type RawImage struct {
Base
treePipeline *OS
Filename string
PartTool osbuild.PartTool
}
func NewRawImage(m *Manifest,
buildPipeline *Build,
treePipeline *OS) *RawImage {
p := &RawImage{
Base: NewBase(m, "image", buildPipeline),
treePipeline: treePipeline,
Filename: "disk.img",
}
buildPipeline.addDependent(p)
if treePipeline.Base.manifest != m {
panic("tree pipeline from different manifest")
}
p.PartTool = osbuild.PTSfdisk // default; can be changed after initialisation
m.addPipeline(p)
return p
}
func (p *RawImage) getBuildPackages(d Distro) []string {
pkgs := p.treePipeline.getBuildPackages(d)
if p.PartTool == osbuild.PTSgdisk {
pkgs = append(pkgs, "gdisk")
}
return pkgs
}
func (p *RawImage) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pt := p.treePipeline.PartitionTable
if pt == nil {
panic("no partition table in live image")
}
for _, stage := range osbuild.GenImagePrepareStages(pt, p.Filename, p.PartTool) {
pipeline.AddStage(stage)
}
inputName := "root-tree"
copyOptions, copyDevices, copyMounts := osbuild.GenCopyFSTreeOptions(inputName, p.treePipeline.Name(), p.Filename, pt)
copyInputs := osbuild.NewPipelineTreeInputs(inputName, p.treePipeline.Name())
pipeline.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts))
for _, stage := range osbuild.GenImageFinishStages(pt, p.Filename) {
pipeline.AddStage(stage)
}
switch p.treePipeline.platform.GetArch() {
case platform.ARCH_S390X:
loopback := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: p.Filename})
pipeline.AddStage(osbuild.NewZiplInstStage(osbuild.NewZiplInstStageOptions(p.treePipeline.kernelVer, pt), loopback, copyDevices, copyMounts))
default:
if grubLegacy := p.treePipeline.platform.GetBIOSPlatform(); grubLegacy != "" {
pipeline.AddStage(osbuild.NewGrub2InstStage(osbuild.NewGrub2InstStageOption(p.Filename, pt, grubLegacy)))
}
}
return pipeline
}
func (p *RawImage) Export() *artifact.Artifact {
p.Base.export = true
return artifact.New(p.Name(), p.Filename, nil)
}

View file

@ -0,0 +1,108 @@
package manifest
import (
"fmt"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/platform"
)
// A RawOSTreeImage represents a raw ostree image file which can be booted in a
// hypervisor. It is created from an existing OSTreeDeployment.
type RawOSTreeImage struct {
Base
treePipeline *OSTreeDeployment
Filename string
platform platform.Platform
}
func NewRawOStreeImage(m *Manifest,
buildPipeline *Build,
platform platform.Platform,
treePipeline *OSTreeDeployment) *RawOSTreeImage {
p := &RawOSTreeImage{
Base: NewBase(m, "image", buildPipeline),
treePipeline: treePipeline,
Filename: "disk.img",
platform: platform,
}
buildPipeline.addDependent(p)
if treePipeline.Base.manifest != m {
panic("tree pipeline from different manifest")
}
m.addPipeline(p)
return p
}
func (p *RawOSTreeImage) getBuildPackages(Distro) []string {
packages := p.platform.GetBuildPackages()
packages = append(packages, p.platform.GetPackages()...)
packages = append(packages, p.treePipeline.PartitionTable.GetBuildPackages()...)
packages = append(packages,
"rpm-ostree",
// these should be defined on the platform
"dracut-config-generic",
"efibootmgr",
)
return packages
}
func (p *RawOSTreeImage) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pt := p.treePipeline.PartitionTable
if pt == nil {
panic("no partition table in live image")
}
for _, stage := range osbuild.GenImagePrepareStages(pt, p.Filename, osbuild.PTSfdisk) {
pipeline.AddStage(stage)
}
inputName := "root-tree"
treeCopyOptions, treeCopyDevices, treeCopyMounts := osbuild.GenCopyFSTreeOptions(inputName, p.treePipeline.Name(), p.Filename, pt)
treeCopyInputs := osbuild.NewPipelineTreeInputs(inputName, p.treePipeline.Name())
pipeline.AddStage(osbuild.NewCopyStage(treeCopyOptions, treeCopyInputs, treeCopyDevices, treeCopyMounts))
bootFiles := p.platform.GetBootFiles()
if len(bootFiles) > 0 {
// we ignore the bootcopyoptions as they contain a full tree copy instead we make our own, we *do* still want all the other
// information such as mountpoints and devices
_, bootCopyDevices, bootCopyMounts := osbuild.GenCopyFSTreeOptions(inputName, p.treePipeline.Name(), p.Filename, pt)
bootCopyOptions := &osbuild.CopyStageOptions{}
commit := p.treePipeline.ostreeSpecs[0]
commitChecksum := commit.Checksum
bootCopyInputs := osbuild.OSTreeCheckoutInputs{
"ostree-tree": *osbuild.NewOSTreeCheckoutInput("org.osbuild.source", commitChecksum),
}
for _, paths := range bootFiles {
bootCopyOptions.Paths = append(bootCopyOptions.Paths, osbuild.CopyStagePath{
From: fmt.Sprintf("input://ostree-tree/%s%s", commitChecksum, paths[0]),
To: fmt.Sprintf("mount://root%s", paths[1]),
})
}
pipeline.AddStage(osbuild.NewCopyStage(bootCopyOptions, bootCopyInputs, bootCopyDevices, bootCopyMounts))
}
for _, stage := range osbuild.GenImageFinishStages(pt, p.Filename) {
pipeline.AddStage(stage)
}
if grubLegacy := p.treePipeline.platform.GetBIOSPlatform(); grubLegacy != "" {
pipeline.AddStage(osbuild.NewGrub2InstStage(osbuild.NewGrub2InstStageOption(p.Filename, pt, grubLegacy)))
}
return pipeline
}
func (p *RawOSTreeImage) Export() *artifact.Artifact {
p.Base.export = true
return artifact.New(p.Name(), p.Filename, nil)
}

64
vendor/github.com/osbuild/images/pkg/manifest/tar.go generated vendored Normal file
View file

@ -0,0 +1,64 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
)
// A Tar represents the contents of another pipeline in a tar file
type Tar struct {
Base
Filename string
Format osbuild.TarArchiveFormat
RootNode osbuild.TarRootNode
ACLs *bool
SELinux *bool
Xattrs *bool
inputPipeline Pipeline
}
// NewTar creates a new TarPipeline. The inputPipeline represents the
// filesystem tree which will be the contents of the tar file. The pipelinename
// is the name of the pipeline. The filename is the name of the output tar file.
func NewTar(m *Manifest,
buildPipeline *Build,
inputPipeline Pipeline,
pipelinename string) *Tar {
p := &Tar{
Base: NewBase(m, pipelinename, buildPipeline),
inputPipeline: inputPipeline,
Filename: "image.tar",
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *Tar) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
tarOptions := &osbuild.TarStageOptions{
Filename: p.Filename,
Format: p.Format,
ACLs: p.ACLs,
SELinux: p.SELinux,
Xattrs: p.Xattrs,
RootNode: p.RootNode,
}
tarStage := osbuild.NewTarStage(tarOptions, p.inputPipeline.Name())
pipeline.AddStage(tarStage)
return pipeline
}
func (p *Tar) getBuildPackages(Distro) []string {
return []string{"tar"}
}
func (p *Tar) Export() *artifact.Artifact {
p.Base.export = true
mimeType := "application/x-tar"
return artifact.New(p.Name(), p.Filename, &mimeType)
}

55
vendor/github.com/osbuild/images/pkg/manifest/vmdk.go generated vendored Normal file
View file

@ -0,0 +1,55 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
)
// A VMDK turns a raw image file into vmdk image.
type VMDK struct {
Base
Filename string
imgPipeline *RawImage
}
// NewVMDK creates a new VMDK pipeline. imgPipeline is the pipeline producing the
// raw image. Filename is the name of the produced image.
func NewVMDK(m *Manifest,
buildPipeline *Build,
imgPipeline *RawImage) *VMDK {
p := &VMDK{
Base: NewBase(m, "vmdk", buildPipeline),
imgPipeline: imgPipeline,
Filename: "image.vmdk",
}
if imgPipeline.Base.manifest != m {
panic("live image pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *VMDK) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewQEMUStage(
osbuild.NewQEMUStageOptions(p.Filename, osbuild.QEMUFormatVMDK, osbuild.VMDKOptions{
Subformat: osbuild.VMDKSubformatStreamOptimized,
}),
osbuild.NewQemuStagePipelineFilesInputs(p.imgPipeline.Name(), p.imgPipeline.Filename),
))
return pipeline
}
func (p *VMDK) getBuildPackages(Distro) []string {
return []string{"qemu-img"}
}
func (p *VMDK) Export() *artifact.Artifact {
p.Base.export = true
mimeType := "application/x-vmdk"
return artifact.New(p.Name(), p.Filename, &mimeType)
}

58
vendor/github.com/osbuild/images/pkg/manifest/vpc.go generated vendored Normal file
View file

@ -0,0 +1,58 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
)
// A VPC turns a raw image file into qemu-based image format, such as qcow2.
type VPC struct {
Base
Filename string
ForceSize *bool
imgPipeline *RawImage
}
// NewVPC createsa new Qemu pipeline. imgPipeline is the pipeline producing the
// raw image. The pipeline name is the name of the new pipeline. Filename is the name
// of the produced image.
func NewVPC(m *Manifest,
buildPipeline *Build,
imgPipeline *RawImage) *VPC {
p := &VPC{
Base: NewBase(m, "vpc", buildPipeline),
imgPipeline: imgPipeline,
Filename: "image.vhd",
}
if imgPipeline.Base.manifest != m {
panic("live image pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *VPC) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
formatOptions := osbuild.VPCOptions{ForceSize: p.ForceSize}
pipeline.AddStage(osbuild.NewQEMUStage(
osbuild.NewQEMUStageOptions(p.Filename, osbuild.QEMUFormatVPC, formatOptions),
osbuild.NewQemuStagePipelineFilesInputs(p.imgPipeline.Name(), p.imgPipeline.Filename),
))
return pipeline
}
func (p *VPC) getBuildPackages(Distro) []string {
return []string{"qemu-img"}
}
func (p *VPC) Export() *artifact.Artifact {
p.Base.export = true
mimeType := "application/x-vhd"
return artifact.New(p.Name(), p.Filename, &mimeType)
}

50
vendor/github.com/osbuild/images/pkg/manifest/xz.go generated vendored Normal file
View file

@ -0,0 +1,50 @@
package manifest
import (
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/osbuild"
)
// The XZ pipeline compresses a raw image file using xz.
type XZ struct {
Base
Filename string
imgPipeline Pipeline
}
// NewXZ creates a new XZ pipeline. imgPipeline is the pipeline producing the
// raw image that will be xz compressed.
func NewXZ(m *Manifest,
buildPipeline *Build,
imgPipeline Pipeline) *XZ {
p := &XZ{
Base: NewBase(m, "xz", buildPipeline),
Filename: "image.xz",
imgPipeline: imgPipeline,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
func (p *XZ) serialize() osbuild.Pipeline {
pipeline := p.Base.serialize()
pipeline.AddStage(osbuild.NewXzStage(
osbuild.NewXzStageOptions(p.Filename),
osbuild.NewXzStageInputs(osbuild.NewFilesInputPipelineObjectRef(p.imgPipeline.Name(), p.imgPipeline.Export().Filename(), nil)),
))
return pipeline
}
func (p *XZ) getBuildPackages(Distro) []string {
return []string{"xz"}
}
func (p *XZ) Export() *artifact.Artifact {
p.Base.export = true
mimeType := "application/xz"
return artifact.New(p.Name(), p.Filename, &mimeType)
}