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
141 changes: 141 additions & 0 deletions cmd/image-builder/bootc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package main_test

import (
"bytes"
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

main "github.com/osbuild/image-builder-cli/cmd/image-builder"
"github.com/osbuild/images/pkg/bootc"
)

func mockBootcInfo() *bootc.Info {
return &bootc.Info{
Imgref: "quay.io/test/bootc:latest",
ImageID: "sha256:abc123",
Arch: "x86_64",
}
}

func TestBootcInspectDefaultYAML(t *testing.T) {
restore := main.MockBootcResolveInfo(func(ref string) (*bootc.Info, error) {
assert.Equal(t, "quay.io/test/bootc:latest", ref)
return mockBootcInfo(), nil
})
defer restore()

restore = main.MockOsArgs([]string{"bootc", "inspect", "--ref=quay.io/test/bootc:latest"})
defer restore()

var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()

err := main.Run()
require.NoError(t, err)

output := fakeStdout.String()
assert.Contains(t, output, "imgref: quay.io/test/bootc:latest")
assert.Contains(t, output, "imageid: sha256:abc123")
assert.Contains(t, output, "arch: x86_64")
}

func TestBootcInspectExplicitYAML(t *testing.T) {
restore := main.MockBootcResolveInfo(func(ref string) (*bootc.Info, error) {
return mockBootcInfo(), nil
})
defer restore()

restore = main.MockOsArgs([]string{"bootc", "inspect", "--ref=quay.io/test/bootc:latest", "--format=yaml"})
defer restore()

var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()

err := main.Run()
require.NoError(t, err)

output := fakeStdout.String()
assert.Contains(t, output, "imgref: quay.io/test/bootc:latest")
}

func TestBootcInspectJSON(t *testing.T) {
restore := main.MockBootcResolveInfo(func(ref string) (*bootc.Info, error) {
return mockBootcInfo(), nil
})
defer restore()

restore = main.MockOsArgs([]string{"bootc", "inspect", "--ref=quay.io/test/bootc:latest", "--format=json"})
defer restore()

var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()

err := main.Run()
require.NoError(t, err)

var parsed map[string]interface{}
err = json.Unmarshal(fakeStdout.Bytes(), &parsed)
require.NoError(t, err, "output must be valid JSON")
assert.Equal(t, "quay.io/test/bootc:latest", parsed["Imgref"])
assert.Equal(t, "sha256:abc123", parsed["ImageID"])
assert.Equal(t, "x86_64", parsed["Arch"])
}

func TestBootcInspectUnsupportedFormat(t *testing.T) {
restore := main.MockBootcResolveInfo(func(ref string) (*bootc.Info, error) {
return mockBootcInfo(), nil
})
defer restore()

restore = main.MockOsArgs([]string{"bootc", "inspect", "--ref=quay.io/test/bootc:latest", "--format=xml"})
defer restore()

var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()

err := main.Run()
require.EqualError(t, err, `unsupported format "xml", supported formats: yaml, json`)
}

func TestBootcInspectResolveError(t *testing.T) {
restore := main.MockBootcResolveInfo(func(ref string) (*bootc.Info, error) {
return nil, fmt.Errorf("cannot resolve %q", ref)
})
defer restore()

restore = main.MockOsArgs([]string{"bootc", "inspect", "--ref=quay.io/bad/ref:latest"})
defer restore()

var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()

err := main.Run()
require.EqualError(t, err, `cannot resolve "quay.io/bad/ref:latest"`)
}

func TestBootcInspectMissingRef(t *testing.T) {
restore := main.MockBootcResolveInfo(func(ref string) (*bootc.Info, error) {
return mockBootcInfo(), nil
})
defer restore()

restore = main.MockOsArgs([]string{"bootc", "inspect"})
defer restore()

var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()

err := main.Run()
require.Error(t, err)
assert.Contains(t, err.Error(), "ref")
}
9 changes: 9 additions & 0 deletions cmd/image-builder/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"

"github.com/osbuild/images/pkg/bootc"
"github.com/osbuild/images/pkg/cloud"
"github.com/osbuild/images/pkg/cloud/awscloud"
"github.com/osbuild/images/pkg/manifestgen"
Expand Down Expand Up @@ -77,6 +78,14 @@ func MockAwscloudNewUploader(f func(string, string, string, *awscloud.UploaderOp
}
}

func MockBootcResolveInfo(f func(string) (*bootc.Info, error)) (restore func()) {
saved := bootcResolveInfo
bootcResolveInfo = f
return func() {
bootcResolveInfo = saved
}
}

func MockManifestgenDepsolver(new manifestgen.DepsolveFunc) (restore func()) {
saved := manifestgenDepsolver
manifestgenDepsolver = new
Expand Down
65 changes: 63 additions & 2 deletions cmd/image-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"go.yaml.in/yaml/v3"

"github.com/osbuild/image-builder-cli/pkg/progress"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/bootc"
Expand All @@ -34,8 +36,9 @@ import (
)

var (
osStdout io.Writer = os.Stdout
osStderr io.Writer = os.Stderr
osStdout io.Writer = os.Stdout
osStderr io.Writer = os.Stderr
bootcResolveInfo = bootc.ResolveBootcInfo
)

// cacheDirForUid returns the cache directory for the given uid.
Expand Down Expand Up @@ -100,6 +103,45 @@ func cmdVersion(cmd *cobra.Command, args []string) error {
return nil
}

func cmdBootcInspect(cmd *cobra.Command, args []string) error {
ref, err := cmd.Flags().GetString("ref")
if err != nil {
return err
}

info, err := bootcResolveInfo(ref)
if err != nil {
return err
}

format, err := cmd.Flags().GetString("format")
if err != nil {
return err
}

switch format {
case "", "yaml":
output, err := yaml.Marshal(&info)
if err != nil {
return err
}

fmt.Fprint(cmd.OutOrStdout(), string(output))
case "json":
output, err := json.Marshal(&info)
if err != nil {
return err
}

fmt.Fprint(cmd.OutOrStdout(), string(output))
default:
return fmt.Errorf("unsupported format %q, supported formats: yaml, json", format)
}

return nil
}


func cmdListImages(cmd *cobra.Command, args []string) error {
filter, err := cmd.Flags().GetStringArray("filter")
if err != nil {
Expand Down Expand Up @@ -638,6 +680,25 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support.
rootCmd.SetOut(osStdout)
rootCmd.SetErr(osStderr)

bootcCommand := &cobra.Command{
Use: "bootc",
Short: "bootc-related commands",
Args: cobra.NoArgs,
}

bootcInspectCommand := &cobra.Command{
Use: "inspect",
Short: "Show data gathered by `image-builder` for a container",
RunE: cmdBootcInspect,
Args: cobra.NoArgs,
}
bootcInspectCommand.Flags().String("ref", "", `bootc container ref`)
_ = bootcInspectCommand.MarkFlagRequired("ref")
bootcInspectCommand.Flags().String("format", "", "Output in a specific format (yaml, json)")
bootcCommand.AddCommand(bootcInspectCommand)

rootCmd.AddCommand(bootcCommand)

listCmd := &cobra.Command{
Use: "list",
Short: "List buildable images, use --filter to limit further",
Expand Down
26 changes: 26 additions & 0 deletions doc/01-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,32 @@ $ image-builder manifest --arch aarch64 minimal-raw-xz
# ... output ...
```

## `image-builder bootc`

The `bootc` subcommand groups helpers for working with bootable containers.

### `inspect`

The `bootc inspect` command shows the data that `image-builder` gathers from a bootable container. This is useful for debugging and understanding how `image-builder` interprets a container before building an image from it.

> [!WARNING]
> *The inspect subcommand exposes internal information. We do not consider this format to be stable though we might stabilize with a public interface in the future.*

The `--ref` flag is required and specifies the container reference to inspect. The container must be available in the container storage of the user running the command.

```console
$ sudo podman pull quay.io/centos-bootc/centos:stream10
$ image-builder bootc inspect --ref quay.io/centos-bootc/centos:stream10
# ... yaml output ...
```

The output format can be changed with `--format`. Available formats are `yaml` (default) and `json`:

```console
$ image-builder bootc inspect --ref quay.io/centos-bootc/centos:stream10 --format=json
# ... json output ...
```

## `image-builder version`

The `version` command prints version information about the `image-builder` binary including its dependencies.
Expand Down
Loading