main: add --extra-artifacts=manifest

This commit adds support for `--extra-artifacts=manifest`. If
that is given as part of the build an extra artifacts called
`<img-name>.osbuild-manifest.json` will be created in the
output directory.

Closes: https://github.com/osbuild/image-builder-cli/issues/42
This commit is contained in:
Michael Vogt 2025-01-20 12:12:10 +01:00 committed by Simon de Vlieger
parent c4357b3bfa
commit 0580eb1106
5 changed files with 63 additions and 6 deletions

View file

@ -64,6 +64,10 @@ $ sudo image-builder build qcow2 --distro centos-9
this will create a directory `centos-9-qcow2-x86_64` under which the this will create a directory `centos-9-qcow2-x86_64` under which the
output is stored. output is stored.
With the `--extra-artifacts=manifest` an
[osbuild](https://github.com/osbuild/osbuild) manifest will be
placed in the output directory too.
### Blueprints ### Blueprints
Blueprints are supported, first create a `config.toml` and put e.g. Blueprints are supported, first create a `config.toml` and put e.g.

View file

@ -1,6 +1,10 @@
package main package main
import ( import (
"fmt"
"os"
"path/filepath"
"github.com/osbuild/images/pkg/imagefilter" "github.com/osbuild/images/pkg/imagefilter"
"github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/osbuild"
) )
@ -8,6 +12,8 @@ import (
type buildOptions struct { type buildOptions struct {
OutputDir string OutputDir string
StoreDir string StoreDir string
WriteManifest bool
} }
func buildImage(res *imagefilter.Result, osbuildManifest []byte, opts *buildOptions) error { func buildImage(res *imagefilter.Result, osbuildManifest []byte, opts *buildOptions) error {
@ -18,7 +24,13 @@ func buildImage(res *imagefilter.Result, osbuildManifest []byte, opts *buildOpti
// XXX: support output filename via commandline (c.f. // XXX: support output filename via commandline (c.f.
// https://github.com/osbuild/images/pull/1039) // https://github.com/osbuild/images/pull/1039)
if opts.OutputDir == "" { if opts.OutputDir == "" {
opts.OutputDir = outputDirFor(res) opts.OutputDir = outputNameFor(res)
}
if opts.WriteManifest {
p := filepath.Join(opts.OutputDir, fmt.Sprintf("%s.osbuild-manifest.json", outputNameFor(res)))
if err := os.WriteFile(p, osbuildManifest, 0644); err != nil {
return err
}
} }
// XXX: support stremaing via images/pkg/osbuild/monitor.go // XXX: support stremaing via images/pkg/osbuild/monitor.go

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"slices"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -23,7 +24,7 @@ var (
) )
// generate the default output directory name for the given image // generate the default output directory name for the given image
func outputDirFor(img *imagefilter.Result) string { func outputNameFor(img *imagefilter.Result) string {
return fmt.Sprintf("%s-%s-%s", img.Distro.Name(), img.ImgType.Name(), img.Arch.Name()) return fmt.Sprintf("%s-%s-%s", img.Distro.Name(), img.ImgType.Name(), img.Arch.Name())
} }
@ -158,6 +159,10 @@ func cmdBuild(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
extraArtifacts, err := cmd.Flags().GetStringArray("extra-artifacts")
if err != nil {
return err
}
var mf bytes.Buffer var mf bytes.Buffer
// XXX: check env here, i.e. if user is root and osbuild is installed // XXX: check env here, i.e. if user is root and osbuild is installed
@ -172,8 +177,9 @@ func cmdBuild(cmd *cobra.Command, args []string) error {
} }
buildOpts := &buildOptions{ buildOpts := &buildOptions{
OutputDir: outputDir, OutputDir: outputDir,
StoreDir: storeDir, StoreDir: storeDir,
WriteManifest: slices.Contains(extraArtifacts, "manifest"),
} }
return buildImage(res, mf.Bytes(), buildOpts) return buildImage(res, mf.Bytes(), buildOpts)
} }
@ -195,7 +201,7 @@ operating sytsems like centos and RHEL with easy customizations support.`,
} }
rootCmd.PersistentFlags().String("datadir", "", `Override the default data direcotry for e.g. custom repositories/*.json data`) rootCmd.PersistentFlags().String("datadir", "", `Override the default data direcotry for e.g. custom repositories/*.json data`)
rootCmd.PersistentFlags().String("output-dir", "", `Put output into the specified direcotry`) rootCmd.PersistentFlags().String("output-dir", "", `Put output into the specified direcotry`)
rootCmd.PersistentFlags().StringArray("extra-artifacts", nil, `Export extra artifacts to the output dir (e.g. "sbom")`) rootCmd.PersistentFlags().StringArray("extra-artifacts", nil, `Export extra artifacts to the output dir (supported: "sbom","manifest", can be given multiple times)`)
rootCmd.SetOut(osStdout) rootCmd.SetOut(osStdout)
rootCmd.SetErr(osStderr) rootCmd.SetErr(osStderr)

View file

@ -394,6 +394,41 @@ func TestBuildIntegrationSwitchOutputDir(t *testing.T) {
assert.Equal(t, "some-output-dir", osbuildCall[outputDirPos+1]) assert.Equal(t, "some-output-dir", osbuildCall[outputDirPos+1])
} }
func TestBuildIntegrationExtraArtifactsManifest(t *testing.T) {
if testing.Short() {
t.Skip("manifest generation takes a while")
}
if !hasDepsolveDnf() {
t.Skip("no osbuild-depsolve-dnf binary found")
}
restore := main.MockNewRepoRegistry(testrepos.New)
defer restore()
outputDir := t.TempDir()
restore = main.MockOsArgs([]string{
"build",
"qcow2",
"--distro", "centos-9",
"--store", outputDir,
"--extra-artifacts", "manifest",
"--output-dir", outputDir,
})
defer restore()
script := `cat - > "$0".stdin`
fakeOsbuildCmd := testutil.MockCommand(t, "osbuild", script)
defer fakeOsbuildCmd.Restore()
err := main.Run()
assert.NoError(t, err)
manifest, err := filepath.Glob(filepath.Join(outputDir, "*.osbuild-manifest.json"))
assert.Equal(t, len(manifest), 1)
assert.Equal(t, filepath.Join(outputDir, "centos-9-qcow2-x86_64.osbuild-manifest.json"), manifest[0])
}
func TestBuildIntegrationErrors(t *testing.T) { func TestBuildIntegrationErrors(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("manifest generation takes a while") t.Skip("manifest generation takes a while")

View file

@ -49,7 +49,7 @@ func generateManifest(dataDir string, img *imagefilter.Result, output io.Writer,
if slices.Contains(opts.ExtraArtifacts, "sbom") { if slices.Contains(opts.ExtraArtifacts, "sbom") {
outputDir := opts.OutputDir outputDir := opts.OutputDir
if outputDir == "" { if outputDir == "" {
outputDir = outputDirFor(img) outputDir = outputNameFor(img)
} }
if err := os.MkdirAll(outputDir, 0755); err != nil { if err := os.MkdirAll(outputDir, 0755); err != nil {
return err return err