Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions data/distrodefs/fedora/imagetypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,7 @@
- "customizations.disk"
- "customizations.files"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.firewall"
Expand Down Expand Up @@ -1118,6 +1119,7 @@
- "customizations.files"
- "customizations.fips"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand Down Expand Up @@ -2101,9 +2103,11 @@ image_types:
- "containers"
- "customizations.installer"
- "customizations.cacerts"
- "customizations.firstboot"
- "customizations.directories"
- "customizations.files"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand Down
9 changes: 9 additions & 0 deletions data/distrodefs/rhel-10/imagetypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@
- "customizations.dnf"
- "customizations.files"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.firewall"
Expand Down Expand Up @@ -834,6 +835,7 @@
- "customizations.files"
- "customizations.fips"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand All @@ -855,9 +857,11 @@
- "groups"
- "enabled_modules"
- "containers"
- "customizations.cacerts"
- "customizations.directories"
- "customizations.files"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand Down Expand Up @@ -1311,6 +1315,7 @@ image_types:
- "customizations.dnf"
- "customizations.files"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand Down Expand Up @@ -1735,10 +1740,12 @@ image_types:
- "groups"
- "enabled_modules"
- "containers"
- "customizations.cacerts"
- "customizations.dnf"
- "customizations.directories"
- "customizations.files"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand Down Expand Up @@ -1918,6 +1925,7 @@ image_types:
- "customizations.directories"
- "customizations.files"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand Down Expand Up @@ -2185,6 +2193,7 @@ image_types:
- "customizations.dnf"
- "customizations.files"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.firewall"
Expand Down
1 change: 1 addition & 0 deletions data/distrodefs/rhel-7/imagetypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
- "customizations.disk"
- "customizations.files"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.firewall"
Expand Down
5 changes: 5 additions & 0 deletions data/distrodefs/rhel-8/imagetypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,7 @@
- "customizations.dnf"
- "customizations.files"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.firewall"
Expand All @@ -1289,11 +1290,13 @@
- "groups"
- "enabled_modules"
- "containers"
- "customizations.cacerts"
- "customizations.dnf"
- "customizations.directories"
- "customizations.files"
- "customizations.fips"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand All @@ -1308,11 +1311,13 @@
# options supported by ostree disk (deployment) image types
supported_options_ostree_disk: &supported_options_ostree_disk
- "distro"
- "customizations.cacerts"
- "customizations.files"
- "customizations.directories"
- "customizations.disk"
- "customizations.dnf"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.user"
Expand Down
11 changes: 11 additions & 0 deletions data/distrodefs/rhel-9/imagetypes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,7 @@
- "customizations.dnf"
- "customizations.files"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.firewall"
Expand All @@ -1299,11 +1300,13 @@
- "groups"
- "enabled_modules"
- "containers"
- "customizations.cacerts"
- "customizations.dnf"
- "customizations.directories"
- "customizations.files"
- "customizations.fips"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand All @@ -1318,11 +1321,13 @@
# options supported by ostree disk (deployment) image types
supported_options_ostree_disk: &supported_options_ostree_disk
- "distro"
- "customizations.cacerts"
- "customizations.files"
- "customizations.directories"
- "customizations.disk"
- "customizations.dnf"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.user"
Expand All @@ -1346,6 +1351,7 @@
- "customizations.files"
- "customizations.fips"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand All @@ -1367,9 +1373,11 @@
- "groups"
- "enabled_modules"
- "containers"
- "customizations.cacerts"
- "customizations.directories"
- "customizations.files"
- "customizations.firewall"
- "customizations.firstboot"
- "customizations.user"
- "customizations.sshkey"
- "customizations.group"
Expand Down Expand Up @@ -3066,9 +3074,11 @@ image_types:
blueprint:
supported_options:
- "distro"
- "customizations.cacerts"
- "customizations.dnf"
- "customizations.installation_device"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.disk"
- "customizations.fdo"
- "customizations.ignition"
Expand Down Expand Up @@ -3260,6 +3270,7 @@ image_types:
- "customizations.dnf"
- "customizations.files"
- "customizations.filesystem"
- "customizations.firstboot"
- "customizations.partitioning_mode"
- "customizations.fips"
- "customizations.firewall"
Expand Down
167 changes: 167 additions & 0 deletions pkg/customizations/firstboot/firstboot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package firstboot

import (
"errors"
"fmt"
"path/filepath"
"regexp"
"slices"

"github.com/osbuild/blueprint/pkg/blueprint"
"github.com/osbuild/images/pkg/shutil"
)

type FirstbootOptions struct {
Scripts []Script
}

type Script struct {
Filename string
Contents string
IgnoreFailure bool
Certs []string
}

// FirstbootCommonOptions contains common fields for all firstboot options.
type FirstbootCommonOptions struct {
// Optional firstboot name. Must be unique within the blueprint and only
// alphanumeric characters with dashes and underscores are allowed.
Name string

// Ignore errors when executing the firstboot script and continue with
// execution of the following firstboot scripts, if any. By default,
// firstboot scripts are executed in order and if one of them fails, the
// execution stops immediately.
IgnoreFailure bool
}

// CustomFirstbootOptions contains fields specific to custom firstboot
// options.
type CustomFirstbootOptions struct {
FirstbootCommonOptions

// Strings without shebang will be interpreted as shell scripts, otherwise
// the script will be executed using the shebang interpreter. Required if
// type is set to "custom".
Contents string
}

// SatelliteFirstbootOptions contains fields specific to satellite firstboot
// options.
type SatelliteFirstbootOptions struct {
FirstbootCommonOptions

// Optional CA certificate to enroll into the system before executing the
// firstboot script.
CACerts []string

// Registration command as generated by the Satellite server. Required, if
// type is set to "satellite".
Command string
}

// AAPFirstbootOptions contains fields specific to AAP firstboot options.
type AAPFirstbootOptions struct {
FirstbootCommonOptions

// Optional CA certificate to enroll into the system before executing the
// firstboot script.
CACerts []string

// Job template URL as generated by the AAP server. Required if type is set
// to "aap". Example URLs are
// https://aap.example.com/api/controller/v2/job_templates/9/callback/ or
// https://aap.example.com/api/v2/job_templates/9/callback/ depending on the
// AAP version.
JobTemplateURL string

// The host config key. Required if type is set to "aap".
HostConfigKey string
}

// FirstbootOption is a union of all supported firstboot options.
type FirstbootOption interface {
isFirstbootOption()
}

func (CustomFirstbootOptions) isFirstbootOption() {}
func (SatelliteFirstbootOptions) isFirstbootOption() {}
func (AAPFirstbootOptions) isFirstbootOption() {}

var ErrFirstbootAlreadySet = errors.New("firstboot customization already set")

var reservedRegexp = regexp.MustCompile(`^(custom|satellite|aap)-\d+$`)

// FirstbootOptionsFromBP converts a blueprint FirstbootCustomization to
// FirstbootOptions. Validation is done in the blueprint package, so this function
// assumes the input is valid, however, JSON unmarshalling errors are possible.
func FirstbootOptionsFromBP(bpFirstboot blueprint.FirstbootCustomization) (*FirstbootOptions, error) {
fo := &FirstbootOptions{}
var satDone, aapDone bool
var ci int
var alreadyUsed []string

nameFunc := func(inputName, prefix string) string {
// Use number-based name if name was not provided, is a path, already used,
// or matches reserved pattern.
if inputName == "" || !filepath.IsLocal(inputName) ||
slices.Contains(alreadyUsed, inputName) || reservedRegexp.MatchString(inputName) {
ci++
return fmt.Sprintf("osbuild-first-%s-%d", prefix, ci)
}

// keep the naming convention consistent with the existing "osbuild-first-boot"
alreadyUsed = append(alreadyUsed, inputName)
return fmt.Sprintf("osbuild-first-%s", inputName)
}

for _, fbsc := range bpFirstboot.Scripts {
cust, sat, aap, err := fbsc.SelectUnion()
if err != nil {
return nil, err
}

if cust != nil {
fo.Scripts = append(fo.Scripts, Script{
Filename: nameFunc(cust.Name, "custom"),
Contents: cust.Contents,
IgnoreFailure: cust.IgnoreFailure,
})
}

if sat != nil {
if satDone {
return nil, fmt.Errorf("%w: satellite", ErrFirstbootAlreadySet)
}
satDone = true

fo.Scripts = append(fo.Scripts, Script{
Filename: nameFunc(sat.Name, "satellite"),
Contents: sat.Command,
IgnoreFailure: sat.IgnoreFailure,
Certs: sat.CACerts,
})
}

if aap != nil {
if aapDone {
return nil, fmt.Errorf("%w: aap", ErrFirstbootAlreadySet)
}
aapDone = true

contents := fmt.Sprintf("#!/usr/bin/bash\ncurl -i --data %s %s\n",
shutil.Quote("host_config_key="+aap.HostConfigKey),
shutil.Quote(aap.JobTemplateURL),
)

fo.Scripts = append(fo.Scripts, Script{
Filename: nameFunc(aap.Name, "aap"),
Contents: contents,
IgnoreFailure: aap.IgnoreFailure,
Certs: aap.CACerts,
})
}
}

return fo, nil
}
Loading
Loading