main: add build --with-buildlog

This commit adds a new `--with-buildlog` option that will automatically
create a buildlog in the output directory.
This commit is contained in:
Michael Vogt 2025-01-27 17:00:41 +01:00 committed by Ondřej Budai
parent 55d3b4916a
commit 2e741a70ad
3 changed files with 116 additions and 19 deletions

View file

@ -14,6 +14,7 @@ type buildOptions struct {
StoreDir string
WriteManifest bool
WriteBuildlog bool
}
func buildImage(pbar progress.ProgressBar, res *imagefilter.Result, osbuildManifest []byte, opts *buildOptions) error {
@ -31,5 +32,19 @@ func buildImage(pbar progress.ProgressBar, res *imagefilter.Result, osbuildManif
}
}
return progress.RunOSBuild(pbar, osbuildManifest, opts.StoreDir, opts.OutputDir, res.ImgType.Exports(), nil)
osbuildOpts := &progress.OSBuildOptions{
StoreDir: opts.StoreDir,
OutputDir: opts.OutputDir,
}
if opts.WriteBuildlog {
p := filepath.Join(opts.OutputDir, fmt.Sprintf("%s.buildlog", outputNameFor(res)))
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
osbuildOpts.BuildLog = f
}
return progress.RunOSBuild(pbar, osbuildManifest, res.ImgType.Exports(), osbuildOpts)
}

View file

@ -207,6 +207,11 @@ func cmdBuild(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
withBuildlog, err := cmd.Flags().GetBool("with-buildlog")
if err != nil {
return err
}
pbar, err := progressFromCmd(cmd)
if err != nil {
return err
@ -257,6 +262,7 @@ func cmdBuild(cmd *cobra.Command, args []string) error {
OutputDir: outputDir,
StoreDir: cacheDir,
WriteManifest: withManifest,
WriteBuildlog: withBuildlog,
}
pbar.SetPulseMsgf("Image building step")
if err := buildImage(pbar, res, mf.Bytes(), buildOpts); err != nil {
@ -379,6 +385,7 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support.
}
buildCmd.Flags().AddFlagSet(manifestCmd.Flags())
buildCmd.Flags().Bool("with-manifest", false, `export osbuild manifest`)
buildCmd.Flags().Bool("with-buildlog", false, `export osbuild buildlog`)
// XXX: add --rpmmd cache too and put under /var/cache/image-builder/dnf
buildCmd.Flags().String("cache", "/var/cache/image-builder/store", `osbuild directory to cache intermediate build artifacts"`)
// XXX: add "--verbose" here, similar to how bib is doing this

View file

@ -384,6 +384,9 @@ func TestBuildIntegrationArgs(t *testing.T) {
}, {
[]string{"--with-manifest"},
[]string{"centos-9-qcow2-x86_64.osbuild-manifest.json"},
}, {
[]string{"--with-buildlog"},
[]string{"centos-9-qcow2-x86_64.buildlog"},
}, {
[]string{"--with-sbom"},
[]string{"centos-9-qcow2-x86_64.buildroot-build.spdx.json",
@ -416,7 +419,7 @@ func TestBuildIntegrationArgs(t *testing.T) {
defer fakeOsbuildCmd.Restore()
err := main.Run()
assert.NoError(t, err)
require.NoError(t, err)
// ensure output dir override works
osbuildCall := fakeOsbuildCmd.Calls()[0]
@ -441,6 +444,7 @@ cat - > "$0".stdin
echo "error on stdout"
>&2 echo "error on stderr"
sleep 0.1
>&3 echo '{"message": "osbuild-stage-output"}'
exit 1
`
@ -471,12 +475,61 @@ func TestBuildIntegrationErrorsProgressVerbose(t *testing.T) {
stdout, stderr := testutil.CaptureStdio(t, func() {
err = main.Run()
})
assert.EqualError(t, err, "running osbuild failed: exit status 1")
assert.EqualError(t, err, "error running osbuild: exit status 1")
assert.Contains(t, stdout, "error on stdout\n")
assert.Contains(t, stderr, "error on stderr\n")
}
func TestBuildIntegrationErrorsProgressVerboseWithBuildlog(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",
"--progress=verbose",
"--with-buildlog",
"--output-dir", outputDir,
})
defer restore()
failingOsbuild := `#!/bin/sh
cat - > "$0".stdin
echo "error on stdout"
>&2 echo "error on stderr"
exit 1
`
fakeOsbuildCmd := testutil.MockCommand(t, "osbuild", failingOsbuild)
defer fakeOsbuildCmd.Restore()
var err error
stdout, _ := testutil.CaptureStdio(t, func() {
err = main.Run()
})
assert.EqualError(t, err, "error running osbuild: exit status 1")
// when the buildlog is used we do not get the direct output of
// osbuild on stderr, to avoid races everything goes via stdout
assert.Contains(t, stdout, "error on stdout\n")
assert.Contains(t, stdout, "error on stderr\n")
buildLog, err := os.ReadFile(filepath.Join(outputDir, "centos-9-qcow2-x86_64.buildlog"))
assert.NoError(t, err)
assert.Equal(t, string(buildLog), `error on stdout
error on stderr
`)
}
func TestBuildIntegrationErrorsProgressTerm(t *testing.T) {
if testing.Short() {
t.Skip("manifest generation takes a while")
@ -488,30 +541,52 @@ func TestBuildIntegrationErrorsProgressTerm(t *testing.T) {
restore := main.MockNewRepoRegistry(testrepos.New)
defer restore()
restore = main.MockOsArgs([]string{
"build",
"qcow2",
"--distro", "centos-9",
"--progress=term",
})
defer restore()
for _, withBuildlog := range []bool{false, true} {
t.Run(fmt.Sprintf("with buildlog %v", withBuildlog), func(t *testing.T) {
outputDir := t.TempDir()
cmd := []string{
"build",
"qcow2",
"--distro", "centos-9",
"--progress=term",
"--output-dir", outputDir,
}
if withBuildlog {
cmd = append(cmd, "--with-buildlog")
}
restore = main.MockOsArgs(cmd)
defer restore()
fakeOsbuildCmd := testutil.MockCommand(t, "osbuild", failingOsbuild)
defer fakeOsbuildCmd.Restore()
fakeOsbuildCmd := testutil.MockCommand(t, "osbuild", failingOsbuild)
defer fakeOsbuildCmd.Restore()
var err error
stdout, stderr := testutil.CaptureStdio(t, func() {
err = main.Run()
})
assert.EqualError(t, err, `error running osbuild: exit status 1
var err error
stdout, stderr := testutil.CaptureStdio(t, func() {
err = main.Run()
})
assert.EqualError(t, err, `error running osbuild: exit status 1
BuildLog:
osbuild-stage-output
Output:
error on stdout
error on stderr
`)
assert.NotContains(t, stdout, "error on stdout")
assert.NotContains(t, stderr, "error on stderr")
assert.NotContains(t, stdout, "error on stdout")
assert.NotContains(t, stderr, "error on stderr")
if withBuildlog {
buildLog, err := os.ReadFile(filepath.Join(outputDir, "centos-9-qcow2-x86_64.buildlog"))
assert.NoError(t, err)
assert.Equal(t, string(buildLog), `error on stdout
error on stderr
osbuild-stage-output
`)
} else {
_, err := os.Stat(filepath.Join(outputDir, "centos-9-qcow2-x86_64.buildlog"))
assert.True(t, os.IsNotExist(err))
}
})
}
}
func TestManifestIntegrationWithSBOMWithOutputDir(t *testing.T) {