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 StoreDir string
WriteManifest bool WriteManifest bool
WriteBuildlog bool
} }
func buildImage(pbar progress.ProgressBar, res *imagefilter.Result, osbuildManifest []byte, opts *buildOptions) error { 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 { if err != nil {
return err return err
} }
withBuildlog, err := cmd.Flags().GetBool("with-buildlog")
if err != nil {
return err
}
pbar, err := progressFromCmd(cmd) pbar, err := progressFromCmd(cmd)
if err != nil { if err != nil {
return err return err
@ -257,6 +262,7 @@ func cmdBuild(cmd *cobra.Command, args []string) error {
OutputDir: outputDir, OutputDir: outputDir,
StoreDir: cacheDir, StoreDir: cacheDir,
WriteManifest: withManifest, WriteManifest: withManifest,
WriteBuildlog: withBuildlog,
} }
pbar.SetPulseMsgf("Image building step") pbar.SetPulseMsgf("Image building step")
if err := buildImage(pbar, res, mf.Bytes(), buildOpts); err != nil { 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().AddFlagSet(manifestCmd.Flags())
buildCmd.Flags().Bool("with-manifest", false, `export osbuild manifest`) 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 // 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"`) 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 // 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{"--with-manifest"},
[]string{"centos-9-qcow2-x86_64.osbuild-manifest.json"}, []string{"centos-9-qcow2-x86_64.osbuild-manifest.json"},
}, {
[]string{"--with-buildlog"},
[]string{"centos-9-qcow2-x86_64.buildlog"},
}, { }, {
[]string{"--with-sbom"}, []string{"--with-sbom"},
[]string{"centos-9-qcow2-x86_64.buildroot-build.spdx.json", []string{"centos-9-qcow2-x86_64.buildroot-build.spdx.json",
@ -416,7 +419,7 @@ func TestBuildIntegrationArgs(t *testing.T) {
defer fakeOsbuildCmd.Restore() defer fakeOsbuildCmd.Restore()
err := main.Run() err := main.Run()
assert.NoError(t, err) require.NoError(t, err)
// ensure output dir override works // ensure output dir override works
osbuildCall := fakeOsbuildCmd.Calls()[0] osbuildCall := fakeOsbuildCmd.Calls()[0]
@ -441,6 +444,7 @@ cat - > "$0".stdin
echo "error on stdout" echo "error on stdout"
>&2 echo "error on stderr" >&2 echo "error on stderr"
sleep 0.1
>&3 echo '{"message": "osbuild-stage-output"}' >&3 echo '{"message": "osbuild-stage-output"}'
exit 1 exit 1
` `
@ -471,12 +475,61 @@ func TestBuildIntegrationErrorsProgressVerbose(t *testing.T) {
stdout, stderr := testutil.CaptureStdio(t, func() { stdout, stderr := testutil.CaptureStdio(t, func() {
err = main.Run() 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, stdout, "error on stdout\n")
assert.Contains(t, stderr, "error on stderr\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) { func TestBuildIntegrationErrorsProgressTerm(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("manifest generation takes a while") t.Skip("manifest generation takes a while")
@ -488,30 +541,52 @@ func TestBuildIntegrationErrorsProgressTerm(t *testing.T) {
restore := main.MockNewRepoRegistry(testrepos.New) restore := main.MockNewRepoRegistry(testrepos.New)
defer restore() defer restore()
restore = main.MockOsArgs([]string{ for _, withBuildlog := range []bool{false, true} {
"build", t.Run(fmt.Sprintf("with buildlog %v", withBuildlog), func(t *testing.T) {
"qcow2", outputDir := t.TempDir()
"--distro", "centos-9", cmd := []string{
"--progress=term", "build",
}) "qcow2",
defer restore() "--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) fakeOsbuildCmd := testutil.MockCommand(t, "osbuild", failingOsbuild)
defer fakeOsbuildCmd.Restore() defer fakeOsbuildCmd.Restore()
var err error var err error
stdout, stderr := testutil.CaptureStdio(t, func() { stdout, stderr := testutil.CaptureStdio(t, func() {
err = main.Run() err = main.Run()
}) })
assert.EqualError(t, err, `error running osbuild: exit status 1 assert.EqualError(t, err, `error running osbuild: exit status 1
BuildLog: BuildLog:
osbuild-stage-output osbuild-stage-output
Output: Output:
error on stdout error on stdout
error on stderr error on stderr
`) `)
assert.NotContains(t, stdout, "error on stdout") assert.NotContains(t, stdout, "error on stdout")
assert.NotContains(t, stderr, "error on stderr") 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) { func TestManifestIntegrationWithSBOMWithOutputDir(t *testing.T) {