osbuild2: new schema types: stages, inputs, sources

Adding new types and adapting copies of all the old types to match the
new Manifest schema:

New types:
- Stages
    - org.osbuild.ostree.init
    - org.osbuild.ostree.pull
    - org.osbuild.ostree.preptree (replaces org.osbuild.rpm-ostree)
    - org.osbuild.curl
- Converted from assemblers
    The concept of a Build and Assembler stage in gone now. Instead they
    are regular Stages like any other.
    - org.osbuild.oci-archive
    - org.osbuild.ostree.commit
- Sources
    - org.osbuild.curl
    - org.osbuild.ostree
- Inputs
    - org.osbuild.files
    - org.osbuild.ostree

Types with changes:
- Stages
    - org.osbuild.rpm:
        - New input structure for defining packages
        - New options

Basically copies:
- The rest simply rename the `Name` field to `Type`

Decoding types with interface fields:
Types that contain interfaces with multiple implementations implement
their own UnmarshalJSON method.  In these cases, we use a JSON decoder
with the `DisallowUnknownFields` option to catch errors during the
deserialization while trying to determine which implementation matches
the data.

Copied tests for copied types are adapted accordingly.
This commit is contained in:
Achilleas Koutsou 2021-02-20 17:40:11 +01:00 committed by Tom Gundersen
parent 8090621300
commit 756d5b063f
58 changed files with 1926 additions and 0 deletions

View file

@ -0,0 +1,14 @@
package osbuild2
type ChronyStageOptions struct {
Timeservers []string `json:"timeservers"`
}
func (ChronyStageOptions) isStageOptions() {}
func NewChronyStage(options *ChronyStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.chrony",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewChronyStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.chrony",
Options: &ChronyStageOptions{},
}
actualStage := NewChronyStage(&ChronyStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,66 @@
package osbuild2
import (
"bytes"
"encoding/json"
)
type CurlSource struct {
Items map[string]CurlSourceItem `json:"items"`
}
func (CurlSource) isSource() {}
// CurlSourceItem can be either a URL string or a URL paired with a secrets
// provider
type CurlSourceItem interface {
isCurlSourceItem()
}
type URL string
func (URL) isCurlSourceItem() {}
type URLWithSecrets struct {
URL string `json:"url"`
Secrets *URLSecrets `json:"secrets,omitempty"`
}
func (URLWithSecrets) isCurlSourceItem() {}
type URLSecrets struct {
Name string `json:"name"`
}
// Unmarshal method for CurlSource for handling the CurlSourceItem interface:
// Tries each of the implementations until it finds the one that works.
func (cs *CurlSource) UnmarshalJSON(data []byte) (err error) {
cs.Items = make(map[string]CurlSourceItem)
type csSimple struct {
Items map[string]URL `json:"items"`
}
simple := new(csSimple)
b := bytes.NewReader(data)
dec := json.NewDecoder(b)
dec.DisallowUnknownFields()
if err = dec.Decode(simple); err == nil {
for k, v := range simple.Items {
cs.Items[k] = v
}
return
}
type csWithSecrets struct {
Items map[string]URLWithSecrets `json:"items"`
}
withSecrets := new(csWithSecrets)
b.Reset(data)
if err = dec.Decode(withSecrets); err == nil {
for k, v := range withSecrets.Items {
cs.Items[k] = v
}
return
}
return
}

View file

@ -0,0 +1,18 @@
package osbuild2
// Inputs for individual files
// Provides all the files, named via their content hash, specified
// via `references` in a new directory.
type FilesInput struct {
inputCommon
}
func (FilesInput) isInput() {}
func NewFilesInput() *FilesInput {
input := new(FilesInput)
input.Type = "org.osbuild.files"
input.Origin = "org.osbuild.source"
return input
}

View file

@ -0,0 +1,16 @@
package osbuild2
type FirewallStageOptions struct {
Ports []string `json:"ports,omitempty"`
EnabledServices []string `json:"enabled_services,omitempty"`
DisabledServices []string `json:"disabled_services,omitempty"`
}
func (FirewallStageOptions) isStageOptions() {}
func NewFirewallStage(options *FirewallStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.firewall",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewFirewallStage(t *testing.T) {
expectedFirewall := &Stage{
Type: "org.osbuild.firewall",
Options: &FirewallStageOptions{},
}
actualFirewall := NewFirewallStage(&FirewallStageOptions{})
assert.Equal(t, expectedFirewall, actualFirewall)
}

View file

@ -0,0 +1,15 @@
package osbuild2
type FirstBootStageOptions struct {
Commands []string `json:"commands"`
WaitForNetwork bool `json:"wait_for_network,omitempty"`
}
func (FirstBootStageOptions) isStageOptions() {}
func NewFirstBootStage(options *FirstBootStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.first-boot",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewFirstBootStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.first-boot",
Options: &FirstBootStageOptions{},
}
actualStage := NewFirstBootStage(&FirstBootStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,21 @@
package osbuild2
// A FixBLSStageOptions struct is empty, as the stage takes no options.
//
// The FixBLSStage fixes the paths in the Boot Loader Specification
// snippets installed into /boot. grub2's kernel install script will
// try to guess the correct path to the kernel and bootloader, and adjust
// the boot loader scripts accordingly. When run under OSBuild this does
// not work correctly, so this stage essentially reverts the "fixup".
type FixBLSStageOptions struct {
}
func (FixBLSStageOptions) isStageOptions() {}
// NewFixBLSStage creates a new FixBLSStage.
func NewFixBLSStage() *Stage {
return &Stage{
Type: "org.osbuild.fix-bls",
Options: &FixBLSStageOptions{},
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewFixBLSStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.fix-bls",
Options: &FixBLSStageOptions{},
}
actualStage := NewFixBLSStage()
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,44 @@
package osbuild2
// The FSTabStageOptions describe the content of the /etc/fstab file.
//
// The structure of the options follows the format of /etc/fstab, except
// that filesystem must be identified by their UUID and ommitted fields
// are set to their defaults (if possible).
type FSTabStageOptions struct {
FileSystems []*FSTabEntry `json:"filesystems"`
}
func (FSTabStageOptions) isStageOptions() {}
// NewFSTabStage creates a now FSTabStage object
func NewFSTabStage(options *FSTabStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.fstab",
Options: options,
}
}
// An FSTabEntry represents one line in /etc/fstab. With the one exception
// that the the spec field must be represented as an UUID.
type FSTabEntry struct {
UUID string `json:"uuid,omitempty"`
Label string `json:"label,omitempty"`
VFSType string `json:"vfs_type"`
Path string `json:"path,omitempty"`
Options string `json:"options,omitempty"`
Freq uint64 `json:"freq,omitempty"`
PassNo uint64 `json:"passno,omitempty"`
}
// AddFilesystem adds one entry to and FSTabStageOptions object.
func (options *FSTabStageOptions) AddFilesystem(id string, vfsType string, path string, opts string, freq uint64, passNo uint64) {
options.FileSystems = append(options.FileSystems, &FSTabEntry{
UUID: id,
VFSType: vfsType,
Path: path,
Options: opts,
Freq: freq,
PassNo: passNo,
})
}

View file

@ -0,0 +1,52 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewFSTabStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.fstab",
Options: &FSTabStageOptions{},
}
actualStage := NewFSTabStage(&FSTabStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}
func TestAddFilesystem(t *testing.T) {
options := &FSTabStageOptions{}
filesystems := []*FSTabEntry{
{
UUID: "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
VFSType: "ext4",
Path: "/",
Options: "defaults",
Freq: 1,
PassNo: 1,
},
{
UUID: "bba22bf4-f153-4541-b6c7-0332c0dfaeac",
VFSType: "xfs",
Path: "/home",
Options: "defaults",
Freq: 1,
PassNo: 2,
},
{
UUID: "cca22bf4-f153-4541-b6c7-0332c0dfaeac",
VFSType: "xfs",
Path: "/var",
Options: "defaults",
Freq: 1,
PassNo: 1,
},
}
for i, fs := range filesystems {
options.AddFilesystem(fs.UUID, fs.VFSType, fs.Path, fs.Options, fs.Freq, fs.PassNo)
assert.Equal(t, options.FileSystems[i], fs)
}
assert.Equal(t, len(filesystems), len(options.FileSystems))
}

View file

@ -0,0 +1,19 @@
package osbuild2
type GroupsStageOptions struct {
Groups map[string]GroupsStageOptionsGroup `json:"groups"`
}
func (GroupsStageOptions) isStageOptions() {}
type GroupsStageOptionsGroup struct {
Name string `json:"name"`
GID *int `json:"gid,omitempty"`
}
func NewGroupsStage(options *GroupsStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.groups",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewGroupsStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.groups",
Options: &GroupsStageOptions{},
}
actualStage := NewGroupsStage(&GroupsStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,33 @@
package osbuild2
import "github.com/google/uuid"
// The GRUB2StageOptions describes the bootloader configuration.
//
// The stage is responsible for installing all bootloader files in
// /boot as well as config files in /etc necessary for regenerating
// the configuration in /boot.
//
// Note that it is the role of an assembler to install any necessary
// bootloaders that are stored in the image outside of any filesystem.
type GRUB2StageOptions struct {
RootFilesystemUUID uuid.UUID `json:"root_fs_uuid"`
BootFilesystemUUID *uuid.UUID `json:"boot_fs_uuid,omitempty"`
KernelOptions string `json:"kernel_opts,omitempty"`
Legacy string `json:"legacy,omitempty"`
UEFI *GRUB2UEFI `json:"uefi,omitempty"`
}
type GRUB2UEFI struct {
Vendor string `json:"vendor"`
}
func (GRUB2StageOptions) isStageOptions() {}
// NewGRUB2Stage creates a new GRUB2 stage object.
func NewGRUB2Stage(options *GRUB2StageOptions) *Stage {
return &Stage{
Type: "org.osbuild.grub2",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewGRUB2Stage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.grub2",
Options: &GRUB2StageOptions{},
}
actualStage := NewGRUB2Stage(&GRUB2StageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,14 @@
package osbuild2
type HostnameStageOptions struct {
Hostname string `json:"hostname"`
}
func (HostnameStageOptions) isStageOptions() {}
func NewHostnameStage(options *HostnameStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.hostname",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewHostnameStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.hostname",
Options: &HostnameStageOptions{},
}
actualStage := NewHostnameStage(&HostnameStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,19 @@
package osbuild2
// KernelCmdlineStageOptions describe how to create kernel-cmdline stage
//
// Configures the kernel boot parameters, also known as the kernel command line.
type KernelCmdlineStageOptions struct {
RootFsUUID string `json:"root_fs_uuid,omitempty"`
KernelOpts string `json:"kernel_opts,omitempty"`
}
func (KernelCmdlineStageOptions) isStageOptions() {}
// NewKernelCmdlineStage creates a new kernel-cmdline Stage object.
func NewKernelCmdlineStage(options *KernelCmdlineStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.kernel-cmdline",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewKernelCmdlineStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.kernel-cmdline",
Options: &KernelCmdlineStageOptions{},
}
actualStage := NewKernelCmdlineStage(&KernelCmdlineStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,14 @@
package osbuild2
type KeymapStageOptions struct {
Keymap string `json:"keymap"`
}
func (KeymapStageOptions) isStageOptions() {}
func NewKeymapStage(options *KeymapStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.keymap",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewKeymapStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.keymap",
Options: &KeymapStageOptions{},
}
actualStage := NewKeymapStage(&KeymapStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,19 @@
package osbuild2
// The LocaleStageOptions describes the image's locale.
//
// A locale is typically specified as language_[territory], where language
// is specified in ISO 639 and territory in ISO 3166.
type LocaleStageOptions struct {
Language string `json:"language"`
}
func (LocaleStageOptions) isStageOptions() {}
// NewLocaleStage creates a new Locale Stage object.
func NewLocaleStage(options *LocaleStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.locale",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewLocaleStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.locale",
Options: &LocaleStageOptions{},
}
actualStage := NewLocaleStage(&LocaleStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,51 @@
package osbuild2
type OCIArchiveStageOptions struct {
// The CPU architecture of the image
Architecture string `json:"architecture"`
// Resulting image filename
Filename string `json:"filename"`
// The execution parameters
Config *OCIArchiveConfig `json:"config,omitempty"`
}
type OCIArchiveConfig struct {
Cmd []string `json:"Cmd,omitempty"`
Env []string `json:"Env,omitempty"`
ExposedPorts []string `json:"ExposedPorts,omitempty"`
User string `json:"User,omitempty"`
Labels map[string]string `json:"Labels,omitempty"`
StopSignal string `json:"StopSignal,omitempty"`
Volumes []string `json:"Volumes,omitempty"`
WorkingDir string `json:"WorkingDir,omitempty"`
}
func (OCIArchiveStageOptions) isStageOptions() {}
type OCIArchiveStageInputs struct {
Base *OCIArchiveStageInput `json:"base"`
}
func (OCIArchiveStageInputs) isStageInputs() {}
type OCIArchiveStageInput struct {
inputCommon
References OCIArchiveStageReferences `json:"references"`
}
func (OCIArchiveStageInput) isStageInput() {}
type OCIArchiveStageReferences []string
func (OCIArchiveStageReferences) isReferences() {}
// A new OCIArchiveStage to to assemble an OCI image archive
func NewOCIArchiveStage(options *OCIArchiveStageOptions, inputs *OCIArchiveStageInputs) *Stage {
return &Stage{
Type: "org.osbuild.oci-archive",
Options: options,
Inputs: inputs,
}
}

View file

@ -0,0 +1,35 @@
// Package osbuild provides primitives for representing and (un)marshalling
// OSBuild (schema v1) types.
package osbuild2
// A Manifest represents an OSBuild source and pipeline manifest
type Manifest struct {
Version string `json:"version"`
Pipelines []Pipeline `json:"pipelines"`
Sources Sources `json:"sources"`
}
// A Pipeline represents an OSBuild pipeline
type Pipeline struct {
Name string `json:"name,omitempty"`
// The build environment which can run this pipeline
Build string `json:"build,omitempty"`
Runner string `json:"runner,omitempty"`
// Sequence of stages that produce the filesystem tree, which is the
// payload of the produced image.
Stages []*Stage `json:"stages,omitempty"`
}
// SetBuild sets the pipeline and runner for generating the build environment
// for a pipeline.
func (p *Pipeline) SetBuild(build string) {
p.Build = build
}
// AddStage appends a stage to the list of stages of a pipeline. The stages
// will be executed in the order they are appended.
func (p *Pipeline) AddStage(stage *Stage) {
p.Stages = append(p.Stages, stage)
}

View file

@ -0,0 +1,28 @@
// Package osbuild provides primitives for representing and (un)marshalling
// OSBuild types.
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPipeline_AddStage(t *testing.T) {
expectedPipeline := &Pipeline{
Build: "name:build",
Stages: []*Stage{
{
Type: "org.osbuild.rpm",
},
},
}
actualPipeline := &Pipeline{
Build: "name:build",
}
actualPipeline.AddStage(&Stage{
Type: "org.osbuild.rpm",
})
assert.Equal(t, expectedPipeline, actualPipeline)
assert.Equal(t, 1, len(actualPipeline.Stages))
}

View file

@ -0,0 +1,41 @@
package osbuild2
type OSTreeCommitStageOptions struct {
// OStree ref to create for the commit
Ref string `json:"ref"`
// Set the version of the OS as commit metadata
OSVersion string `json:"os_version,omitempty"`
// Commit ID of the parent commit
Parent string `json:"parent,omitempty"`
}
func (OSTreeCommitStageOptions) isStageOptions() {}
type OSTreeCommitStageInput struct {
inputCommon
References OSTreeCommitStageReferences `json:"references"`
}
func (OSTreeCommitStageInput) isStageInput() {}
type OSTreeCommitStageInputs struct {
Tree *OSTreeCommitStageInput `json:"tree"`
}
func (OSTreeCommitStageInputs) isStageInputs() {}
type OSTreeCommitStageReferences []string
func (OSTreeCommitStageReferences) isReferences() {}
// The OSTreeCommitStage (org.osbuild.ostree.commit) describes how to assemble
// a tree into an OSTree commit.
func NewOSTreeCommitStage(options *OSTreeCommitStageOptions, inputs *OSTreeCommitStageInputs) *Stage {
return &Stage{
Type: "org.osbuild.ostree.commit",
Options: options,
Inputs: inputs,
}
}

View file

@ -0,0 +1,29 @@
package osbuild2
type InitMode string
const (
ModeBare InitMode = "bare"
ModeBareUser InitMode = "bare-user"
ModeBareUserOnly InitMode = "bare-user-only"
ModeArchvie InitMode = "archive"
)
// Options for the org.osbuild.ostree.init stage.
type OSTreeInitStageOptions struct {
// The Mode in which to initialise the repo
Mode InitMode `json:"mode,omitempty"`
// Location in which to create the repo
Path string `json:"path,omitempty"`
}
func (OSTreeInitStageOptions) isStageOptions() {}
// A new org.osbuild.ostree.init stage to create an OSTree repository
func NewOSTreeInitStage(options *OSTreeInitStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.ostree.init",
Options: options,
}
}

View file

@ -0,0 +1,15 @@
package osbuild2
// Inputs for ostree commits
type OSTreeInput struct {
inputCommon
}
func (OSTreeInput) isInput() {}
func NewOSTreeInput() *OSTreeInput {
input := new(OSTreeInput)
input.Type = "org.osbuild.ostree"
input.Origin = "org.osbuild.source"
return input
}

View file

@ -0,0 +1,16 @@
package osbuild2
type OSTreePrepTreeStageOptions struct {
EtcGroupMembers []string `json:"etc_group_members,omitempty"`
}
func (OSTreePrepTreeStageOptions) isStageOptions() {}
// The OSTree PrepTree (org.osbuild.ostree.preptree) stage transforms the
// tree to an ostree layout.
func NewOSTreePrepTreeStage(options *OSTreePrepTreeStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.ostree.preptree",
Options: options,
}
}

View file

@ -0,0 +1,39 @@
package osbuild2
// Options for the org.osbuild.ostree.pull stage.
type OSTreePullStageOptions struct {
// Location of the ostree repo
Repo string `json:"repo"`
}
func (OSTreePullStageOptions) isStageOptions() {}
// A new org.osbuild.ostree.pull stage to pull OSTree commits into an existing repo
func NewOSTreePullStage(options *OSTreePullStageOptions, inputs Inputs) *Stage {
return &Stage{
Type: "org.osbuild.ostree.pull",
Inputs: inputs,
Options: options,
}
}
type OSTreePullStageInput struct {
inputCommon
References OSTreePullStageReferences `json:"references"`
}
func (OSTreePullStageInput) isStageInput() {}
type OSTreePullStageInputs struct {
Commits *OSTreePullStageInput `json:"commits"`
}
func (OSTreePullStageInputs) isStageInputs() {}
type OSTreePullStageReferences map[string]OSTreePullStageReference
func (OSTreePullStageReferences) isReferences() {}
type OSTreePullStageReference struct {
Ref string `json:"ref"`
}

View file

@ -0,0 +1,19 @@
package osbuild2
// The commits to fetch indexed their checksum
type OSTreeSource struct {
Items map[string]OSTreeSourceItem `json:"items"`
}
func (OSTreeSource) isSource() {}
type OSTreeSourceItem struct {
Remote OSTreeRemote `json:"remote"`
}
type OSTreeRemote struct {
// URL of the repository.
URL string `json:"url"`
// GPG keys to verify the commits
GPGKeys []string `json:"secrets,omitempty"`
}

View file

@ -0,0 +1,22 @@
package osbuild2
import (
"encoding/json"
)
type PipelineResult []StageResult
type StageResult struct {
ID string `json:"id"`
Type string `json:"type"`
Output string `json:"output"`
Success bool `json:"success,omitempty"`
Error string `json:"string,omitempty"`
}
type Result struct {
Type string `json:"type"`
Success bool `json:"success"`
Error json.RawMessage `json:"error"`
Log map[string]PipelineResult `json:"log"`
}

View file

@ -0,0 +1 @@
package osbuild2

View file

@ -0,0 +1,34 @@
package osbuild2
// RHSMStageOptions describes configuration of the RHSM stage.
//
// The RHSM stage allows configuration of Red Hat Subscription Manager (RHSM)
// related components. Currently it allows only configuration of the enablement
// state of DNF plugins used by the Subscription Manager.
type RHSMStageOptions struct {
DnfPlugins *RHSMStageOptionsDnfPlugins `json:"dnf-plugins,omitempty"`
}
func (RHSMStageOptions) isStageOptions() {}
// RHSMStageOptionsDnfPlugins describes configuration of all RHSM DNF plugins
type RHSMStageOptionsDnfPlugins struct {
ProductID *RHSMStageOptionsDnfPlugin `json:"product-id,omitempty"`
SubscriptionManager *RHSMStageOptionsDnfPlugin `json:"subscription-manager,omitempty"`
}
// RHSMStageOptionsDnfPlugin describes configuration of a specific RHSM DNF
// plugin
//
// Only the enablement state of a DNF plugin can be currenlty set.
type RHSMStageOptionsDnfPlugin struct {
Enabled bool `json:"enabled"`
}
// NewRHSMStage creates a new RHSM stage
func NewRHSMStage(options *RHSMStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.rhsm",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewRhsmStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.rhsm",
Options: &RHSMStageOptions{},
}
actualStage := NewRHSMStage(&RHSMStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,53 @@
package osbuild2
type RPMStageOptions struct {
// Array of GPG key contents to import
GPGKeys []string `json:"gpgkeys,omitempty"`
// Prevent dracut from running
DisableDracut bool `json:"disable_dracut,omitempty"`
Exclude *Exclude `json:"exclude,omitempty"`
}
type Exclude struct {
// Do not install documentation.
Docs bool `json:"docs,omitempty"`
}
// RPMPackage represents one RPM, as referenced by its content hash
// (checksum). The files source must indicate where to fetch the given
// RPM. If CheckGPG is `true` the RPM must be signed with one of the
// GPGKeys given in the RPMStageOptions.
type RPMPackage struct {
Checksum string `json:"checksum"`
CheckGPG bool `json:"check_gpg,omitempty"`
}
func (RPMStageOptions) isStageOptions() {}
type RPMStageInputs struct {
Packages *RPMStageInput `json:"packages"`
}
func (RPMStageInputs) isStageInputs() {}
type RPMStageInput struct {
inputCommon
References RPMStageReferences `json:"references"`
}
func (RPMStageInput) isStageInput() {}
type RPMStageReferences []string
func (RPMStageReferences) isReferences() {}
// NewRPMStage creates a new RPM stage.
func NewRPMStage(options *RPMStageOptions, inputs *RPMStageInputs) *Stage {
return &Stage{
Type: "org.osbuild.rpm",
Inputs: inputs,
Options: options,
}
}

View file

@ -0,0 +1,17 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewRPMStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.rpm",
Options: &RPMStageOptions{},
Inputs: &RPMStageInputs{},
}
actualStage := NewRPMStage(&RPMStageOptions{}, &RPMStageInputs{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,24 @@
package osbuild2
// The ScriptStageOptions specifies a custom script to run in the image
type ScriptStageOptions struct {
Script string `json:"script"`
}
func (ScriptStageOptions) isStageOptions() {}
// NewScriptStageOptions creates a new script stage options object, with
// the mandatory fields set.
func NewScriptStageOptions(script string) *ScriptStageOptions {
return &ScriptStageOptions{
Script: script,
}
}
// NewScriptStage creates a new Script Stage object.
func NewScriptStage(options *ScriptStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.script",
Options: options,
}
}

View file

@ -0,0 +1,24 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewScriptStageOptions(t *testing.T) {
expectedOptions := &ScriptStageOptions{
Script: "/root/test.sh",
}
actualOptions := NewScriptStageOptions("/root/test.sh")
assert.Equal(t, expectedOptions, actualOptions)
}
func TestNewScriptStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.script",
Options: &ScriptStageOptions{},
}
actualStage := NewScriptStage(&ScriptStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,28 @@
package osbuild2
// The SELinuxStageOptions describe how to apply selinux labels.
//
// A file contexts configuration file is sepcified that describes
// the filesystem labels to apply to the image.
type SELinuxStageOptions struct {
FileContexts string `json:"file_contexts"`
Labels map[string]string `json:"labels,omitempty"`
}
func (SELinuxStageOptions) isStageOptions() {}
// NewSELinuxStageOptions creates a new SELinuxStaeOptions object, with
// the mandatory fields set.
func NewSELinuxStageOptions(fileContexts string) *SELinuxStageOptions {
return &SELinuxStageOptions{
FileContexts: fileContexts,
}
}
// NewSELinuxStage creates a new SELinux Stage object.
func NewSELinuxStage(options *SELinuxStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.selinux",
Options: options,
}
}

View file

@ -0,0 +1,24 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSELinuxStageOptions(t *testing.T) {
expectedOptions := &SELinuxStageOptions{
FileContexts: "etc/selinux/targeted/contexts/files/file_contexts",
}
actualOptions := NewSELinuxStageOptions("etc/selinux/targeted/contexts/files/file_contexts")
assert.Equal(t, expectedOptions, actualOptions)
}
func TestNewSELinuxStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.selinux",
Options: &SELinuxStageOptions{},
}
actualStage := NewSELinuxStage(&SELinuxStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,49 @@
package osbuild2
import (
"encoding/json"
"errors"
)
// A Sources map contains all the sources made available to an osbuild run
type Sources map[string]Source
// Source specifies the operations of a given source-type.
type Source interface {
isSource()
}
type SourceOptions interface {
isSourceOptions()
}
type rawSources map[string]json.RawMessage
// UnmarshalJSON unmarshals JSON into a Source object. Each type of source has
// a custom unmarshaller for its options, selected based on the source name.
func (sources *Sources) UnmarshalJSON(data []byte) error {
var rawSources rawSources
err := json.Unmarshal(data, &rawSources)
if err != nil {
return err
}
*sources = make(map[string]Source)
for name, rawSource := range rawSources {
var source Source
switch name {
case "org.osbuild.curl":
source = new(CurlSource)
case "org.osbuild.ostree":
source = new(OSTreeSource)
default:
return errors.New("unexpected source name: " + name)
}
err = json.Unmarshal(rawSource, source)
if err != nil {
return err
}
(*sources)[name] = source
}
return nil
}

View file

@ -0,0 +1,115 @@
package osbuild2
import (
"bytes"
"encoding/json"
"reflect"
"testing"
)
func TestSource_UnmarshalJSON(t *testing.T) {
type fields struct {
Type string
Source Source
}
type args struct {
data []byte
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "invalid json",
args: args{
data: []byte(`{"name":"org.osbuild.foo","options":{"bar":null}`),
},
wantErr: true,
},
{
name: "unknown source",
args: args{
data: []byte(`{"name":"org.osbuild.foo","options":{"bar":null}}`),
},
wantErr: true,
},
{
name: "missing options",
args: args{
data: []byte(`{"name":"org.osbuild.curl"}`),
},
wantErr: true,
},
{
name: "missing name",
args: args{
data: []byte(`{"foo":null,"options":{"bar":null}}`),
},
wantErr: true,
},
{
name: "curl-empty",
fields: fields{
Type: "org.osbuild.curl",
Source: &CurlSource{Items: map[string]CurlSourceItem{}},
},
args: args{
data: []byte(`{"org.osbuild.curl":{"items":{}}}`),
},
},
{
name: "curl-with-secrets",
fields: fields{
Type: "org.osbuild.curl",
Source: &CurlSource{
Items: map[string]CurlSourceItem{
"checksum1": URLWithSecrets{URL: "url1", Secrets: &URLSecrets{Name: "org.osbuild.rhsm"}},
"checksum2": URLWithSecrets{URL: "url2", Secrets: &URLSecrets{Name: "whatever"}},
}},
},
args: args{
data: []byte(`{"org.osbuild.curl":{"items":{"checksum1":{"url":"url1","secrets":{"name":"org.osbuild.rhsm"}},"checksum2":{"url":"url2","secrets":{"name":"whatever"}}}}}`),
},
},
{
name: "curl-url-only",
fields: fields{
Type: "org.osbuild.curl",
Source: &CurlSource{
Items: map[string]CurlSourceItem{
"checksum1": URL("url1"),
"checksum2": URL("url2"),
}},
},
args: args{
data: []byte(`{"org.osbuild.curl":{"items":{"checksum1":"url1","checksum2":"url2"}}}`),
},
},
}
for idx, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sources := &Sources{
tt.fields.Type: tt.fields.Source,
}
var gotSources Sources
if err := gotSources.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
t.Errorf("Sources.UnmarshalJSON() error = %v, wantErr %v [idx: %d]", err, tt.wantErr, idx)
}
if tt.wantErr {
return
}
gotBytes, err := json.Marshal(sources)
if err != nil {
t.Errorf("Could not marshal source: %v [idx: %d]", err, idx)
}
if !bytes.Equal(gotBytes, tt.args.data) {
t.Errorf("Expected '%v', got '%v' [idx: %d]", string(tt.args.data), string(gotBytes), idx)
}
if !reflect.DeepEqual(&gotSources, sources) {
t.Errorf("got '%v', expected '%v' [idx:%d]", &gotSources, sources, idx)
}
})
}
}

131
internal/osbuild2/stage.go Normal file
View file

@ -0,0 +1,131 @@
package osbuild2
import (
"encoding/json"
"fmt"
)
// Single stage of a pipeline executing one step
type Stage struct {
// Well-known name in reverse domain-name notation, uniquely identifying
// the stage type.
Type string `json:"type"`
// Stage-type specific options fully determining the operations of the
Inputs Inputs `json:"inputs,omitempty"`
Options StageOptions `json:"options,omitempty"`
}
// Collection of Inputs for a Stage
type Inputs interface {
isStageInputs()
}
// Single Input for a Stage
type Input interface {
isInput()
}
// Fields shared between all Input types (should be embedded in each instance)
type inputCommon struct {
Type string `json:"type"`
// Origin should be either 'org.osbuild.source' or 'org.osbuild.pipeline'
Origin string `json:"origin"`
// References References `json:"references"`
}
type StageInput interface {
isStageInput()
}
type References interface {
isReferences()
}
// StageOptions specify the operations of a given stage-type.
type StageOptions interface {
isStageOptions()
}
type InputOptions interface {
}
type rawStage struct {
Type string `json:"type"`
Options json.RawMessage `json:"options"`
Inputs json.RawMessage `json:"inputs"`
}
// UnmarshalJSON unmarshals JSON into a Stage object. Each type of stage has
// a custom unmarshaller for its options, selected based on the stage name.
func (stage *Stage) UnmarshalJSON(data []byte) error {
var rawStage rawStage
if err := json.Unmarshal(data, &rawStage); err != nil {
return err
}
var options StageOptions
var inputs Inputs
switch rawStage.Type {
case "org.osbuild.fix-bls":
options = new(FixBLSStageOptions)
case "org.osbuild.fstab":
options = new(FSTabStageOptions)
case "org.osbuild.grub2":
options = new(GRUB2StageOptions)
case "org.osbuild.locale":
options = new(LocaleStageOptions)
case "org.osbuild.selinux":
options = new(SELinuxStageOptions)
case "org.osbuild.hostname":
options = new(HostnameStageOptions)
case "org.osbuild.users":
options = new(UsersStageOptions)
case "org.osbuild.groups":
options = new(GroupsStageOptions)
case "org.osbuild.timezone":
options = new(TimezoneStageOptions)
case "org.osbuild.chrony":
options = new(ChronyStageOptions)
case "org.osbuild.keymap":
options = new(KeymapStageOptions)
case "org.osbuild.firewall":
options = new(FirewallStageOptions)
case "org.osbuild.rhsm":
options = new(RHSMStageOptions)
case "org.osbuild.systemd":
options = new(SystemdStageOptions)
case "org.osbuild.script":
options = new(ScriptStageOptions)
case "org.osbuild.rpm":
options = new(RPMStageOptions)
inputs = new(RPMStageInputs)
case "org.osbuild.oci-archive":
options = new(OCIArchiveStageOptions)
inputs = new(OCIArchiveStageInputs)
case "org.osbuild.ostree.commit":
options = new(OSTreeCommitStageOptions)
inputs = new(OSTreeCommitStageInputs)
case "org.osbuild.ostree.pull":
options = new(OSTreePullStageOptions)
inputs = new(OSTreePullStageInputs)
case "org.osbuild.ostree.preptree":
options = new(RPMOSTreePrepTreeStageOptions)
default:
return fmt.Errorf("unexpected stage type: %s", rawStage.Type)
}
if err := json.Unmarshal(rawStage.Options, options); err != nil {
return err
}
if inputs != nil && rawStage.Inputs != nil {
if err := json.Unmarshal(rawStage.Inputs, inputs); err != nil {
return err
}
}
stage.Type = rawStage.Type
stage.Options = options
stage.Inputs = inputs
return nil
}

View file

@ -0,0 +1,383 @@
package osbuild2
import (
"bytes"
"encoding/json"
"reflect"
"testing"
"github.com/google/uuid"
)
func TestStage_UnmarshalJSON(t *testing.T) {
nullUUID := uuid.MustParse("00000000-0000-0000-0000-000000000000")
type fields struct {
Type string
Options StageOptions
}
type args struct {
data []byte
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "invalid json",
args: args{
data: []byte(`{"type":"org.osbuild.foo","options":{"bar":null}}`),
},
wantErr: true,
},
{
name: "unknown stage",
args: args{
data: []byte(`{"type":"org.osbuild.foo","options":{"bar":null}}`),
},
wantErr: true,
},
{
name: "missing options",
args: args{
data: []byte(`{"type":"org.osbuild.locale"}`),
},
wantErr: true,
},
{
name: "missing name",
args: args{
data: []byte(`{"foo":null,"options":{"bar":null}}`),
},
wantErr: true,
},
{
name: "chrony",
fields: fields{
Type: "org.osbuild.chrony",
Options: &ChronyStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.chrony","options":{"timeservers":null}}`),
},
},
{
name: "firewall",
fields: fields{
Type: "org.osbuild.firewall",
Options: &FirewallStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.firewall","options":{}}`),
},
},
{
name: "fix-bls",
fields: fields{
Type: "org.osbuild.fix-bls",
Options: &FixBLSStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.fix-bls","options":{}}`),
},
},
{
name: "fstab",
fields: fields{
Type: "org.osbuild.fstab",
Options: &FSTabStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.fstab","options":{"filesystems":null}}`),
},
},
{
name: "groups",
fields: fields{
Type: "org.osbuild.groups",
Options: &GroupsStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.groups","options":{"groups":null}}`),
},
},
{
name: "grub2",
fields: fields{
Type: "org.osbuild.grub2",
Options: &GRUB2StageOptions{
RootFilesystemUUID: nullUUID,
},
},
args: args{
data: []byte(`{"type":"org.osbuild.grub2","options":{"root_fs_uuid":"00000000-0000-0000-0000-000000000000"}}`),
},
},
{
name: "grub2-uefi",
fields: fields{
Type: "org.osbuild.grub2",
Options: &GRUB2StageOptions{
RootFilesystemUUID: nullUUID,
UEFI: &GRUB2UEFI{
Vendor: "vendor",
},
},
},
args: args{
data: []byte(`{"type":"org.osbuild.grub2","options":{"root_fs_uuid":"00000000-0000-0000-0000-000000000000","uefi":{"vendor":"vendor"}}}`),
},
},
{
name: "grub2-separate-boot",
fields: fields{
Type: "org.osbuild.grub2",
Options: &GRUB2StageOptions{
RootFilesystemUUID: nullUUID,
BootFilesystemUUID: &nullUUID,
},
},
args: args{
data: []byte(`{"type":"org.osbuild.grub2","options":{"root_fs_uuid":"00000000-0000-0000-0000-000000000000","boot_fs_uuid":"00000000-0000-0000-0000-000000000000"}}`),
},
},
{
name: "hostname",
fields: fields{
Type: "org.osbuild.hostname",
Options: &HostnameStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.hostname","options":{"hostname":""}}`),
},
},
{
name: "keymap",
fields: fields{
Type: "org.osbuild.keymap",
Options: &KeymapStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.keymap","options":{"keymap":""}}`),
},
},
{
name: "locale",
fields: fields{
Type: "org.osbuild.locale",
Options: &LocaleStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.locale","options":{"language":""}}`),
},
},
{
name: "rhsm-empty",
fields: fields{
Type: "org.osbuild.rhsm",
Options: &RHSMStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.rhsm","options":{}}`),
},
},
{
name: "rhsm",
fields: fields{
Type: "org.osbuild.rhsm",
Options: &RHSMStageOptions{
DnfPlugins: &RHSMStageOptionsDnfPlugins{
ProductID: &RHSMStageOptionsDnfPlugin{
Enabled: false,
},
SubscriptionManager: &RHSMStageOptionsDnfPlugin{
Enabled: false,
},
},
},
},
args: args{
data: []byte(`{"type":"org.osbuild.rhsm","options":{"dnf-plugins":{"product-id":{"enabled":false},"subscription-manager":{"enabled":false}}}}`),
},
},
{
name: "script",
fields: fields{
Type: "org.osbuild.script",
Options: &ScriptStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.script","options":{"script":""}}`),
},
},
{
name: "selinux",
fields: fields{
Type: "org.osbuild.selinux",
Options: &SELinuxStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.selinux","options":{"file_contexts":""}}`),
},
},
{
name: "systemd",
fields: fields{
Type: "org.osbuild.systemd",
Options: &SystemdStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.systemd","options":{}}`),
},
},
{
name: "systemd-enabled",
fields: fields{
Type: "org.osbuild.systemd",
Options: &SystemdStageOptions{
EnabledServices: []string{"foo.service"},
},
},
args: args{
data: []byte(`{"type":"org.osbuild.systemd","options":{"enabled_services":["foo.service"]}}`),
},
},
{
name: "timezone",
fields: fields{
Type: "org.osbuild.timezone",
Options: &TimezoneStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.timezone","options":{"zone":""}}`),
},
},
{
name: "users",
fields: fields{
Type: "org.osbuild.users",
Options: &UsersStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.users","options":{"users":null}}`),
},
},
}
for idx, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stage := &Stage{
Type: tt.fields.Type,
Options: tt.fields.Options,
}
var gotStage Stage
if err := gotStage.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
t.Errorf("Stage.UnmarshalJSON() error = %v, wantErr %v [idx: %d]", err, tt.wantErr, idx)
}
if tt.wantErr {
return
}
gotBytes, err := json.Marshal(stage)
if err != nil {
t.Errorf("Could not marshal stage: %v", err)
}
if !bytes.Equal(gotBytes, tt.args.data) {
t.Errorf("Expected `%v`, got `%v` [idx: %d]", string(tt.args.data), string(gotBytes), idx)
}
if !reflect.DeepEqual(&gotStage, stage) {
t.Errorf("got {%v, %v}, expected {%v, %v} [%d]", gotStage.Type, gotStage.Options, stage.Type, stage.Options, idx)
}
})
}
}
// Test new stages that have Inputs (osbuild v2 schema)
func TestStageV2_UnmarshalJSON(t *testing.T) {
type fields struct {
Type string
Options StageOptions
Inputs Inputs
}
type args struct {
data []byte
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "rpm-empty",
fields: fields{
Type: "org.osbuild.rpm",
Options: &RPMStageOptions{},
Inputs: &RPMStageInputs{},
},
args: args{
data: []byte(`{"type":"org.osbuild.rpm","inputs":{"packages":null},"options":{}}`),
},
},
{
name: "rpm",
fields: fields{
Type: "org.osbuild.rpm",
Inputs: &RPMStageInputs{
Packages: &RPMStageInput{
References: RPMStageReferences{
"checksum1",
"checksum2",
},
},
},
Options: &RPMStageOptions{
GPGKeys: []string{"key1", "key2"},
},
},
args: args{
data: []byte(`{"type":"org.osbuild.rpm","inputs":{"packages":{"type":"","origin":"","references":["checksum1","checksum2"]}},"options":{"gpgkeys":["key1","key2"]}}`),
},
},
{
name: "ostree-preptree",
fields: fields{
Type: "org.osbuild.ostree.preptree",
Options: &OSTreePrepTreeStageOptions{
EtcGroupMembers: []string{
"wheel",
},
},
},
args: args{
data: []byte(`{"type":"org.osbuild.ostree.preptree","options":{"etc_group_members":["wheel"]}}`),
},
},
}
for idx, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stage := &Stage{
Type: tt.fields.Type,
Options: tt.fields.Options,
Inputs: tt.fields.Inputs,
}
var gotStage Stage
if err := gotStage.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
println("data: ", string(tt.args.data))
t.Errorf("Stage.UnmarshalJSON() error = %v, wantErr %v [idx: %d]", err, tt.wantErr, idx)
}
if tt.wantErr {
return
}
gotBytes, err := json.Marshal(stage)
if err != nil {
t.Errorf("Could not marshal stage: %v", err)
}
if !bytes.Equal(gotBytes, tt.args.data) {
t.Errorf("Expected `%v`, got `%v` [idx: %d]", string(tt.args.data), string(gotBytes), idx)
}
if !reflect.DeepEqual(&gotStage, stage) {
t.Errorf("got {%v, %v, %v}, expected {%v, %v, %v} [%d]", gotStage.Type, gotStage.Options, gotStage.Inputs, stage.Type, stage.Options, stage.Inputs, idx)
}
})
}
}

View file

@ -0,0 +1,25 @@
package osbuild2
type SysconfigStageOptions struct {
Kernel SysconfigKernelOptions `json:"kernel,omitempty"`
Network SysconfigNetworkOptions `json:"network,omitempty"`
}
type SysconfigNetworkOptions struct {
Networking bool `json:"networking,omitempty"`
NoZeroConf bool `json:"no_zero_conf,omitempty"`
}
type SysconfigKernelOptions struct {
UpdateDefault bool `json:"update_default,omitempty"`
DefaultKernel string `json:"default_kernel,omitempty"`
}
func (SysconfigStageOptions) isStageOptions() {}
func NewSysconfigStage(options *SysconfigStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.sysconfig",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSysconfigStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.sysconfig",
Options: &SysconfigStageOptions{},
}
actualStage := NewSysconfigStage(&SysconfigStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,16 @@
package osbuild2
type SystemdStageOptions struct {
EnabledServices []string `json:"enabled_services,omitempty"`
DisabledServices []string `json:"disabled_services,omitempty"`
DefaultTarget string `json:"default_target,omitempty"`
}
func (SystemdStageOptions) isStageOptions() {}
func NewSystemdStage(options *SystemdStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.systemd",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSystemdStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.systemd",
Options: &SystemdStageOptions{},
}
actualStage := NewSystemdStage(&SystemdStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,14 @@
package osbuild2
type TimezoneStageOptions struct {
Zone string `json:"zone"`
}
func (TimezoneStageOptions) isStageOptions() {}
func NewTimezoneStage(options *TimezoneStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.timezone",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewTimezoneStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.timezone",
Options: &TimezoneStageOptions{},
}
actualStage := NewTimezoneStage(&TimezoneStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,15 @@
package osbuild2
// Tree inputs
type TreeInput struct {
inputCommon
}
func (TreeInput) isInput() {}
func NewTreeInput() *TreeInput {
input := new(TreeInput)
input.Type = "org.osbuild.tree"
input.Origin = "org.osbuild.pipeline"
return input
}

View file

@ -0,0 +1,25 @@
package osbuild2
type UsersStageOptions struct {
Users map[string]UsersStageOptionsUser `json:"users"`
}
func (UsersStageOptions) isStageOptions() {}
type UsersStageOptionsUser struct {
UID *int `json:"uid,omitempty"`
GID *int `json:"gid,omitempty"`
Groups []string `json:"groups,omitempty"`
Description *string `json:"description,omitempty"`
Home *string `json:"home,omitempty"`
Shell *string `json:"shell,omitempty"`
Password *string `json:"password,omitempty"`
Key *string `json:"key,omitempty"`
}
func NewUsersStage(options *UsersStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.users",
Options: options,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewUsersStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.users",
Options: &UsersStageOptions{},
}
actualStage := NewUsersStage(&UsersStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}

View file

@ -0,0 +1,25 @@
package osbuild2
// The ZiplStageOptions describe how to create zipl stage
//
// The only configuration option available is a boot timeout and it is optional
type ZiplStageOptions struct {
Timeout int `json:"timeout,omitempty"`
}
func (ZiplStageOptions) isStageOptions() {}
// NewZiplStageOptions creates a new ZiplStageOptions object with no timeout
func NewZiplStageOptions() *ZiplStageOptions {
return &ZiplStageOptions{
Timeout: 0,
}
}
// NewZiplStage creates a new zipl Stage object.
func NewZiplStage(options *ZiplStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.zipl",
Options: options,
}
}

View file

@ -0,0 +1,24 @@
package osbuild2
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewZiplStageOptions(t *testing.T) {
expectedOptions := &ZiplStageOptions{
Timeout: 0,
}
actualOptions := NewZiplStageOptions()
assert.Equal(t, expectedOptions, actualOptions)
}
func TestNewZiplStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.zipl",
Options: &ZiplStageOptions{},
}
actualStage := NewZiplStage(&ZiplStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}