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
46 changes: 9 additions & 37 deletions Schutzfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{
"common": {
"rngseed": 2026030200,
"bootc-image-builder": {
"ref": "quay.io/centos-bootc/bootc-image-builder@sha256:9893e7209e5f449b86ababfd2ee02a58cca2e5990f77b06c3539227531fc8120"
"rngseed": 2026051400,
"dependencies": {
"bootc-image-builder": {
"ref": "quay.io/centos-bootc/bootc-image-builder@sha256:9893e7209e5f449b86ababfd2ee02a58cca2e5990f77b06c3539227531fc8120"
},
"osbuild": {
"commit": "6a00632cbaaa211abf6ec43e46ddd14774745054"
}
},
"gitlab-ci-runner": "aws/fedora-42",
"gitlab-ci-runner-for": {
Expand All @@ -20,26 +25,7 @@
}
}
},
"centos-9": {
"dependencies": {
"osbuild": {
"commit": "6a00632cbaaa211abf6ec43e46ddd14774745054"
}
}
},
"centos-10": {
"dependencies": {
"osbuild": {
"commit": "6a00632cbaaa211abf6ec43e46ddd14774745054"
}
}
},
"fedora-42": {
"dependencies": {
"osbuild": {
"commit": "6a00632cbaaa211abf6ec43e46ddd14774745054"
}
},
"repos": [
{
"file": "/etc/yum.repos.d/fedora.repo",
Expand Down Expand Up @@ -69,19 +55,5 @@
]
}
]
},
"fedora-43": {
"dependencies": {
"osbuild": {
"commit": "6a00632cbaaa211abf6ec43e46ddd14774745054"
}
}
},
"fedora-44": {
"dependencies": {
"osbuild": {
"commit": "6a00632cbaaa211abf6ec43e46ddd14774745054"
}
}
}
}
}
128 changes: 99 additions & 29 deletions cmd/build/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
"github.com/osbuild/images/internal/buildconfig"
"github.com/osbuild/images/internal/cmdutil"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/bootc"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/generic"
"github.com/osbuild/images/pkg/distrofactory"
"github.com/osbuild/images/pkg/manifestgen"
"github.com/osbuild/images/pkg/osbuild"
Expand Down Expand Up @@ -43,12 +46,32 @@ func run() error {
flag.StringVar(&imgTypeName, "type", "", "image type name (required)")
flag.StringVar(&configFile, "config", "", "build config file (required)")

// bootc args
var bootcRef, bootcBuildRef string
var bootcRemote bool
flag.StringVar(&bootcRef, "bootc-ref", "", "bootc container image ref (e.g., localhost/bootc-foundry/stream10-qcow2:latest)")
flag.StringVar(&bootcBuildRef, "bootc-build-ref", "", "separate build container image ref")
flag.BoolVar(&bootcRemote, "bootc-remote", false, "use org.osbuild.skopeo sources instead of containers-storage")

flag.Parse()

if distroName == "" || imgTypeName == "" || configFile == "" {
if imgTypeName == "" || configFile == "" {
flag.Usage()
os.Exit(1)
}
if distroName == "" && bootcRef == "" {
fmt.Fprintf(os.Stderr, "error: either -distro or -bootc-ref is required\n")
flag.Usage()
os.Exit(1)
}
if distroName != "" && bootcRef != "" {
fmt.Fprintf(os.Stderr, "error: -distro and -bootc-ref are mutually exclusive\n")
flag.Usage()
os.Exit(1)
}
if bootcRef != "" && repositories != "test/data/repositories" {
fmt.Fprintf(os.Stderr, "warning: -repositories is ignored when -bootc-ref is used\n")
}

// NOTE: Check the minimum osbuild version before doing anything else.
// Building the manifest would fail, but we need to depsolve the packages
Expand All @@ -59,7 +82,6 @@ func run() error {
return err
}

distroFac := distrofactory.NewDefault()
config, err := buildconfig.New(configFile, nil)
if err != nil {
return err
Expand All @@ -69,51 +91,90 @@ func run() error {
return fmt.Errorf("failed to create target directory: %w", err)
}

distribution := distroFac.GetDistro(distroName)
if distribution == nil {
return fmt.Errorf("invalid or unsupported distribution: %q", distroName)
}
var distribution distro.Distro
if bootcRef != "" {
bootcInfo, err := bootc.ResolveBootcInfo(bootcRef)
if err != nil {
return fmt.Errorf("failed to resolve bootc container info: %w", err)
}

bootcDistro, err := generic.NewBootc("bootc", bootcInfo)
if err != nil {
return fmt.Errorf("failed to create bootc distro: %w", err)
}

if bootcBuildRef != "" {
buildInfo, err := bootc.ResolveBootcBuildInfo(bootcBuildRef)
if err != nil {
return fmt.Errorf("failed to resolve bootc build container info: %w", err)
}
if err := bootcDistro.SetBuildContainer(buildInfo); err != nil {
return fmt.Errorf("failed to set build container: %w", err)
}
}

if archName == "" {
archName = arch.Current().String()
distribution = bootcDistro

if archName == "" {
// NOTE: bootcInfo.Arch contains the Docker/OCI arch name (e.g. "amd64"),
// which must be normalized to the standard name used by the images library
// (e.g. "x86_64") to match the BootcDistro's internal arch map keys.
a, err := arch.FromString(bootcInfo.Arch)
if err != nil {
return fmt.Errorf("unsupported container architecture %q: %w", bootcInfo.Arch, err)
}
archName = a.String()
}
} else {
distroFac := distrofactory.NewDefault()
distribution = distroFac.GetDistro(distroName)
if distribution == nil {
return fmt.Errorf("invalid or unsupported distribution: %q", distroName)
}
if archName == "" {
archName = arch.Current().String()
}
}

archi, err := distribution.GetArch(archName)
if err != nil {
return fmt.Errorf("invalid arch name %q for distro %q: %w", archName, distroName, err)
return fmt.Errorf("invalid arch name %q for distro %q: %w", archName, distribution.Name(), err)
}

buildName := fmt.Sprintf("%s-%s-%s-%s", u(distroName), u(archName), u(imgTypeName), u(config.Name))
buildName := fmt.Sprintf("%s-%s-%s-%s", u(distribution.Name()), u(archName), u(imgTypeName), u(config.Name))
buildDir := filepath.Join(outputDir, buildName)
if err := os.MkdirAll(buildDir, 0777); err != nil {
return fmt.Errorf("failed to create target directory: %w", err)
}

imgType, err := archi.GetImageType(imgTypeName)
if err != nil {
return fmt.Errorf("invalid image type %q for distro %q and arch %q: %w", imgTypeName, distroName, archName, err)
return fmt.Errorf("invalid image type %q for distro %q and arch %q: %w", imgTypeName, distribution.Name(), archName, err)
}

// NOTE: we always put the repositories to be used into the allRepos slice, instead of passing the
// RepoRegistry to the manifestgen. The reason is that the manifestgen API is too clunky to easily
// extend the repos list with custom repositories.
var allRepos []rpmmd.RepoConfig
if st, err := os.Stat(repositories); err == nil && !st.IsDir() {
// anything that is not a dir is tried to be loaded as a file
// to allow "-repositories <arbitrarily-named-file>.json"
repoConfig, err := rpmmd.LoadRepositoriesFromFile(repositories)
if err != nil {
return fmt.Errorf("failed to load repositories from %q: %w", repositories, err)
}
allRepos = repoConfig[archName]
} else {
reporeg, err := reporegistry.New([]string{repositories}, nil)
if err != nil {
return fmt.Errorf("failed to load repositories from %q: %w", repositories, err)
}
allRepos, err = reporeg.ReposByImageTypeName(distribution.Name(), archName, imgTypeName)
if err != nil {
return fmt.Errorf(
"failed to get repositories for %s/%s/%s: %w", distribution.Name(), archName, imgTypeName, err)
allRepos := []rpmmd.RepoConfig{}
if bootcRef == "" {
if st, err := os.Stat(repositories); err == nil && !st.IsDir() {
// anything that is not a dir is tried to be loaded as a file
// to allow "-repositories <arbitrarily-named-file>.json"
repoConfig, err := rpmmd.LoadRepositoriesFromFile(repositories)
if err != nil {
return fmt.Errorf("failed to load repositories from %q: %w", repositories, err)
}
allRepos = repoConfig[archName]
} else {
reporeg, err := reporegistry.New([]string{repositories}, nil)
if err != nil {
return fmt.Errorf("failed to load repositories from %q: %w", repositories, err)
}
allRepos, err = reporeg.ReposByImageTypeName(distribution.Name(), archName, imgTypeName)
if err != nil {
return fmt.Errorf(
"failed to get repositories for %s/%s/%s: %w", distribution.Name(), archName, imgTypeName, err)
}
}
}
seedArg, err := cmdutil.SeedArgFor(config, distribution.Name(), archName)
Expand Down Expand Up @@ -144,6 +205,15 @@ func run() error {
config.Blueprint = &blueprint.Blueprint{}
}

if bootcRef != "" {
if config.Options.Bootc == nil {
config.Options.Bootc = &distro.BootcImageOptions{}
}
if bootcRemote {
config.Options.Bootc.UseRemoteContainerSource = true
}
}

mg, err := manifestgen.New(nil, &manifestOpts)
if err != nil {
return fmt.Errorf("[ERROR] manifest generator creation failed: %w", err)
Expand Down
20 changes: 20 additions & 0 deletions cmd/check-host-config/check/bootc_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package check

import (
"github.com/osbuild/images/internal/buildconfig"
)

func init() {
RegisterCheck(Metadata{
Name: "bootc-status",
RequiresBootc: true,
}, bootcStatusCheck)
}

func bootcStatusCheck(meta *Metadata, config *buildconfig.BuildConfig) error {
stdout, stderr, _, err := ExecString("sudo", "bootc", "status")
if err != nil {
return Fail("bootc status failed:", err, "\nstdout:", stdout, "\nstderr:", stderr)
}
return Pass()
}
62 changes: 62 additions & 0 deletions cmd/check-host-config/check/bootc_status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package check_test

import (
"errors"
"testing"

check "github.com/osbuild/images/cmd/check-host-config/check"
"github.com/osbuild/images/internal/buildconfig"
"github.com/osbuild/images/pkg/distro"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestBootcStatusCheck(t *testing.T) {
tests := []struct {
name string
config *buildconfig.BuildConfig
mockExec map[string]ExecResult
wantErr error
}{
{
name: "pass when bootc status succeeds",
config: &buildconfig.BuildConfig{
Options: distro.ImageOptions{
Bootc: &distro.BootcImageOptions{},
},
},
mockExec: map[string]ExecResult{
"sudo bootc status": {Stdout: []byte("running")},
},
},
{
name: "fail when bootc status fails",
config: &buildconfig.BuildConfig{
Options: distro.ImageOptions{
Bootc: &distro.BootcImageOptions{},
},
},
mockExec: map[string]ExecResult{
"sudo bootc status": {Err: errors.New("bootc not found"), Code: 1},
},
wantErr: check.ErrCheckFailed,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
installMockExec(t, tt.mockExec)

chk, found := check.FindCheckByName("bootc-status")
require.True(t, found, "bootc-status check not found")

err := chk.Func(chk.Meta, tt.config)
if tt.wantErr != nil {
require.Error(t, err)
assert.True(t, errors.Is(err, tt.wantErr))
} else {
require.NoError(t, err)
}
})
}
}
1 change: 1 addition & 0 deletions cmd/check-host-config/check/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Metadata struct {
Name string // Name of the check (used for lookup and logging)
RequiresBlueprint bool // Ensure Blueprint is not nil, skip the check otherwise
RequiresCustomizations bool // Ensure Customizations is not nil, skip the check otherwise
RequiresBootc bool // Ensure Options.Bootc is not nil, skip the check otherwise
TempDisabled string // Set to non-empty string with URL to issue tracker to disable the check temporarily
RunOn []string // List of OS IDs to run the check on (prefix with `!` to exclude)
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/check-host-config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func runChecks(checks []check.RegisteredCheck, config *buildconfig.BuildConfig,
err = check.Skip("no blueprint")
case meta.RequiresCustomizations && (config == nil || config.Blueprint == nil || config.Blueprint.Customizations == nil):
err = check.Skip("no customizations")
case meta.RequiresBootc && (config == nil || config.Options.Bootc == nil):
err = check.Skip("not a bootc image")
default:
err = chk.Func(meta, config)
}
Expand Down
Loading
Loading