Skip to content

Commit

Permalink
cmd: add new describe-image command
Browse files Browse the repository at this point in the history
This commit adds a new `describe-image` comamnd that contains
the details about the given image type. The output is yaml as
it is both nicely human readable and also machine readable.

The output looks like this:
```yaml
$ ./image-builder describe-image rhel-9.1 tar
distro: rhel-9.1
type: tar
arch: x86_64
os_vesion: "9.1"
bootmode: none
partition_type: ""
default_filename: root.tar.xz
packages:
  include:
    - policycoreutils
    - selinux-policy-targeted
    - selinux-policy-targeted
  exclude:
    - rng-tools
```

Thanks to Ondrej Budai for the idea and the example.
  • Loading branch information
mvo5 committed Dec 19, 2024
1 parent 058e3d6 commit 1664311
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 3 deletions.
89 changes: 89 additions & 0 deletions cmd/image-builder/describeimg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"io"
"slices"

"gopkg.in/yaml.v3"

"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/imagefilter"
)

// Use yaml output by default because it is both nicely human and
// machine readable and parts of our image defintions will be written
// in yaml too. This means this should be a possible input a
// "flattended" image definiton.
type describeImgYAML struct {
Distro string `yaml:"distro"`
Type string `yaml:"type"`
Arch string `yaml:"arch"`

// XXX: think about ordering (as this is what the user will see)
OsVersion string `yaml:"os_vesion"`

Bootmode string `yaml:"bootmode"`
PartitionType string `yaml:"partition_type"`
DefaultFilename string `yaml:"default_filename"`

// XXX: add pipelines here? maybe at least exports?
Packages *packagesYAML `yaml:"packages"`
}

type packagesYAML struct {
Include []string `yaml:"include"`
Exclude []string `yaml:"exclude"`
}

func packageSetsFor(imgType distro.ImageType) (inc, exc []string, err error) {
var bp blueprint.Blueprint
manifest, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if err != nil {
return nil, nil, err
}

// XXX: or should this just do what osbuild-package-sets does
// and inlcude what pipeline needs the package set too?
for pipelineName, pkgSets := range manifest.GetPackageSetChains() {
// XXX: or shouldn't we exclude the build pipeline here?
if pipelineName == "build" {
continue
}
for _, pkgSet := range pkgSets {
inc = append(inc, pkgSet.Include...)
exc = append(exc, pkgSet.Exclude...)
}
}
slices.Sort(inc)
slices.Sort(exc)
return inc, exc, nil
}

// XXX: should this live in images instead?
func describeImage(img *imagefilter.Result, out io.Writer) error {
// see
// https://github.com/osbuild/images/pull/1019#discussion_r1832376568
// for what is available on an image (without depsolve or partitioning)
inc, exc, err := packageSetsFor(img.ImgType)
if err != nil {
return err
}

outYaml := &describeImgYAML{
Distro: img.Distro.Name(),
OsVersion: img.Distro.OsVersion(),
Arch: img.Arch.Name(),
Type: img.ImgType.Name(),
Bootmode: img.ImgType.BootMode().String(),
PartitionType: img.ImgType.PartitionType().String(),
DefaultFilename: img.ImgType.Filename(),
Packages: &packagesYAML{
Include: inc,
Exclude: exc,
},
}
enc := yaml.NewEncoder(out)
enc.SetIndent(2)
return enc.Encode(outYaml)
}
41 changes: 41 additions & 0 deletions cmd/image-builder/describeimg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main_test

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"

testrepos "github.com/osbuild/images/test/data/repositories"

"github.com/osbuild/image-builder-cli/cmd/image-builder"
)

func TestDescribeImage(t *testing.T) {
restore := main.MockNewRepoRegistry(testrepos.New)
defer restore()

res, err := main.GetOneImage("", "centos-9", "tar", "x86_64")
assert.NoError(t, err)

var buf bytes.Buffer
err = main.DescribeImage(res, &buf)
assert.NoError(t, err)

expectedOutput := `distro: centos-9
type: tar
arch: x86_64
os_vesion: 9-stream
bootmode: none
partition_type: ""
default_filename: root.tar.xz
packages:
include:
- policycoreutils
- selinux-policy-targeted
- selinux-policy-targeted
exclude:
- rng-tools
`
assert.Equal(t, expectedOutput, buf.String())
}
7 changes: 4 additions & 3 deletions cmd/image-builder/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
)

var (
GetOneImage = getOneImage
Run = run
FindDistro = findDistro
GetOneImage = getOneImage
Run = run
FindDistro = findDistro
DescribeImage = describeImage
)

func MockOsArgs(new []string) (restore func()) {
Expand Down
35 changes: 35 additions & 0 deletions cmd/image-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,29 @@ func cmdBuild(cmd *cobra.Command, args []string) error {
return buildImage(res, mf.Bytes(), storeDir)
}

func cmdDescribeImg(cmd *cobra.Command, args []string) error {
// XXX: boilderplate identical to cmdManifest() above
dataDir, err := cmd.Flags().GetString("datadir")
if err != nil {
return err
}
archStr, err := cmd.Flags().GetString("arch")
if err != nil {
return err
}
if archStr == "" {
archStr = arch.Current().String()
}
distroStr := args[0]
imgTypeStr := args[1]
res, err := getOneImage(dataDir, distroStr, imgTypeStr, archStr)
if err != nil {
return err
}

return describeImage(res, osStdout)
}

func run() error {
// images logs a bunch of stuff to Debug/Info that is distracting
// the user (at least by default, like what repos being loaded)
Expand Down Expand Up @@ -161,6 +184,18 @@ operating sytsems like centos and RHEL with easy customizations support.`,
buildCmd.Flags().String("store", ".store", `osbuild store directory to cache intermediata build artifacts"`)
rootCmd.AddCommand(buildCmd)

// XXX: add --format=json too?
describeImgCmd := &cobra.Command{
Use: "describe-image <distro> <image-type>",
Short: "describe the given distro/image-type, e.g. centos-9 qcow2",
RunE: cmdDescribeImg,
SilenceUsage: true,
Args: cobra.ExactArgs(2),
Hidden: true,
}
describeImgCmd.Flags().String("arch", "", `use the different architecture`)
rootCmd.AddCommand(describeImgCmd)

return rootCmd.Execute()
}

Expand Down
22 changes: 22 additions & 0 deletions cmd/image-builder/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,25 @@ exit 1
// osbuild-exec.go)
assert.Equal(t, "error on stderr\n", fakeStderr.String())
}

func TestDescribeImageSmoke(t *testing.T) {
restore := main.MockNewRepoRegistry(testrepos.New)
defer restore()

restore = main.MockOsArgs([]string{
"describe-image",
"centos-9", "qcow2",
})
defer restore()

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

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

assert.Contains(t, fakeStdout.String(), `distro: centos-9
type: qcow2
arch: x86_64`)
}

0 comments on commit 1664311

Please sign in to comment.