Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: add new describe-image command #9

Merged
merged 1 commit into from
Jan 31, 2025
Merged
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
107 changes: 107 additions & 0 deletions cmd/image-builder/describeimg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package main

import (
"fmt"
"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"`

BuildPipelines []string `yaml:"build_pipelines"`
PayloadPipelines []string `yaml:"payload_pipelines"`
Packages map[string]*packagesYAML `yaml:"packages"`
}

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

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

res := make(map[string]*packagesYAML)

for pipelineName, pkgSets := range manifest.GetPackageSetChains() {
incM := map[string]bool{}
excM := map[string]bool{}
for _, pkgSet := range pkgSets {
for _, s := range pkgSet.Include {
incM[s] = true
}
for _, s := range pkgSet.Exclude {
excM[s] = true
}
}
inc := make([]string, 0, len(incM))
exc := make([]string, 0, len(excM))
for name := range incM {
inc = append(inc, name)
}
for name := range excM {
exc = append(exc, name)
}
slices.Sort(inc)
slices.Sort(exc)

res[pipelineName] = &packagesYAML{
Include: inc,
Exclude: exc,
}
}
return res, 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)
pkgSets, 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(),
BuildPipelines: img.ImgType.BuildPipelines(),
PayloadPipelines: img.ImgType.PayloadPipelines(),
Packages: pkgSets,
}
// deliberately break the yaml until the feature is stable
fmt.Fprint(out, "@WARNING - the output format is not stable yet and may change\n")
enc := yaml.NewEncoder(out)
enc.SetIndent(2)
return enc.Encode(outYaml)
}
60 changes: 60 additions & 0 deletions cmd/image-builder/describeimg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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 := `@WARNING - the output format is not stable yet and may change
distro: centos-9
type: tar
arch: x86_64
os_vesion: 9-stream
bootmode: none
partition_type: ""
default_filename: root.tar.xz
build_pipelines:
- build
payload_pipelines:
- os
- archive
packages:
build:
include:
- coreutils
- glibc
- platform-python
- policycoreutils
- python3
- rpm
- selinux-policy-targeted
- systemd
- tar
- xz
exclude: []
os:
include:
- policycoreutils
- 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
40 changes: 40 additions & 0 deletions cmd/image-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,32 @@ func cmdBuild(cmd *cobra.Command, args []string) error {
return buildImage(pbar, res, mf.Bytes(), buildOpts)
}

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
}
distroStr, err := cmd.Flags().GetString("distro")
if err != nil {
return err
}
archStr, err := cmd.Flags().GetString("arch")
if err != nil {
return err
}
if archStr == "" {
archStr = arch.Current().String()
}
imgTypeStr := args[0]
res, err := getOneImage(dataDir, distroStr, imgTypeStr, archStr)
if err != nil {
return err
}

return describeImage(res, osStdout)
}

func run() error {
// images generates a lot of noisy logs a bunch of stuff to
// Debug/Info that is distracting the user (at least by
Expand Down Expand Up @@ -271,6 +297,20 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support.
buildCmd.Flags().String("progress", "auto", "type of progress bar to use (e.g. verbose,term)")
rootCmd.AddCommand(buildCmd)

// XXX: add --format=json too?
thozza marked this conversation as resolved.
Show resolved Hide resolved
describeImgCmd := &cobra.Command{
Use: "describe-image <image-type>",
Short: "Describe the given image-type, e.g. qcow2 (tip: combine with --distro,--arch)",
RunE: cmdDescribeImg,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
achilleas-k marked this conversation as resolved.
Show resolved Hide resolved
Hidden: true,
}
describeImgCmd.Flags().String("arch", "", `use the different architecture`)
describeImgCmd.Flags().String("distro", "", `build manifest for a different distroname (e.g. centos-9)`)

rootCmd.AddCommand(describeImgCmd)

return rootCmd.Execute()
}

Expand Down
24 changes: 24 additions & 0 deletions cmd/image-builder/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,27 @@ func TestManifestIntegrationWithSBOMWithOutputDir(t *testing.T) {
assert.Equal(t, filepath.Join(outputDir, "centos-9-qcow2-x86_64.buildroot-build.spdx.json"), sboms[0])
assert.Equal(t, filepath.Join(outputDir, "centos-9-qcow2-x86_64.image-os.spdx.json"), sboms[1])
}

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

restore = main.MockOsArgs([]string{
"describe-image",
"qcow2",
"--distro=centos-9",
"--arch=x86_64",
})
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`)
}
Loading