main: tweak handling of --output-name to avoid adding double extensions

This commit tweaks the handling of the `--output-name` option so
that is a name with the same extension as the image is passed that
is just silently ignored. Its a common issue that first time
users run:
```console
$ image-builder build --output-name foo.qcow2 qcow2
```
which currently leads to a foo.qcow2.qcow2. With this commit
the expected "foo.qcow2" will appear.
This commit is contained in:
Michael Vogt 2025-03-20 11:12:33 +01:00
parent d4c31389a9
commit d00e76ced1
4 changed files with 46 additions and 4 deletions

View file

@ -16,6 +16,7 @@ var (
FindDistro = findDistro
DescribeImage = describeImage
ProgressFromCmd = progressFromCmd
BasenameFor = basenameFor
)
func MockOsArgs(new []string) (restore func()) {

View file

@ -8,6 +8,7 @@ import (
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
@ -32,6 +33,18 @@ var (
// for the given imageType. This can be user overriden via userBasename.
func basenameFor(img *imagefilter.Result, userBasename string) string {
if userBasename != "" {
// If the user provided a basename that already has the
// image extension just strip that off. I.e. when
// we get "foo.qcow2" for a qcow out basename is just
// "foo". This is mostly for convenience for our users.
//
// This code assumes that all our ImgType filesnames have
// $name.$ext.$extraExt (e.g. disk.qcow2 or disk.raw.xz)
l := strings.SplitN(img.ImgType.Filename(), ".", 2)
if len(l) > 1 && l[1] != "" {
imgExt := fmt.Sprintf(".%s", l[1])
userBasename = strings.TrimSuffix(userBasename, imgExt)
}
return userBasename
}
return fmt.Sprintf("%s-%s-%s", img.Distro.Name(), img.ImgType.Name(), img.Arch.Name())

View file

@ -892,7 +892,9 @@ func TestBuildIntegrationOutputFilename(t *testing.T) {
"--distro", "centos-9",
"--cache", tmpdir,
"--output-dir", outputDir,
"--output-name=foo.n.0",
// XXX: also test --output-name="foo.n.0" here which should
// have exactly the same result (once the depsolving is mocked)
"--output-name=foo.n.0.qcow2",
"--with-manifest",
"--with-sbom",
"--with-buildlog",
@ -921,3 +923,31 @@ func TestBuildIntegrationOutputFilename(t *testing.T) {
assert.NoError(t, err, fmt.Sprintf("file %q missing from %v", expected, files))
}
}
func TestBasenameFor(t *testing.T) {
restore := main.MockNewRepoRegistry(testrepos.New)
defer restore()
for _, tc := range []struct {
imgTypeName string
basename string
expected string
}{
// no user provided output name
{"qcow2", "", "centos-9-qcow2-x86_64"},
{"minimal-raw", "", "centos-9-minimal-raw-x86_64"},
// simple
{"qcow2", "foo", "foo"},
{"qcow2", "foo.n.0", "foo.n.0"},
// with extension
{"qcow2", "foo.n.0.qcow2", "foo.n.0"},
{"minimal-raw", "foo.n.0.raw.xz", "foo.n.0"},
// with the "wrong" extension, we just ignore that and trust
// the user (what else could we do?)
{"qcow2", "foo.n.0.raw", "foo.n.0.raw"},
} {
res, err := main.GetOneImage("centos-9", tc.imgTypeName, "x86_64", nil)
require.NoError(t, err)
assert.Equal(t, tc.expected, main.BasenameFor(res, tc.basename))
}
}

View file

@ -59,9 +59,7 @@ func generateManifest(dataDir string, extraRepos []string, img *imagefilter.Resu
if opts.WithSBOM {
outputDir := basenameFor(img, opts.OutputDir)
manifestGenOpts.SBOMWriter = func(filename string, content io.Reader, docType sbom.StandardType) error {
if opts.OutputFilename != "" {
filename = fmt.Sprintf("%s.%s", opts.OutputFilename, strings.SplitN(filename, ".", 2)[1])
}
filename = fmt.Sprintf("%s.%s", basenameFor(img, opts.OutputFilename), strings.SplitN(filename, ".", 2)[1])
return sbomWriter(outputDir, filename, content)
}
}