debian-forge-cli/cmd/image-builder/upload_test.go
Michael Vogt 6169260dcd image-builder: update upload API to match latest images
This commit updates the upload API to match the latest
version in the images library.
2025-08-07 10:29:39 +00:00

272 lines
7.3 KiB
Go

package main_test
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/cloud"
"github.com/osbuild/images/pkg/cloud/awscloud"
"github.com/osbuild/images/pkg/platform"
main "github.com/osbuild/image-builder-cli/cmd/image-builder"
"github.com/osbuild/image-builder-cli/internal/testutil"
)
type fakeAwsUploader struct {
checkCalls int
uploadAndRegisterRead bytes.Buffer
uploadAndRegisterCalls int
uploadAndRegisterErr error
}
var _ = cloud.Uploader(&fakeAwsUploader{})
func (fa *fakeAwsUploader) Check(status io.Writer) error {
fa.checkCalls++
return nil
}
func (fa *fakeAwsUploader) UploadAndRegister(r io.Reader, status io.Writer) error {
fa.uploadAndRegisterCalls++
_, err := io.Copy(&fa.uploadAndRegisterRead, r)
if err != nil {
panic(err)
}
return fa.uploadAndRegisterErr
}
func TestUploadWithAWSMock(t *testing.T) {
fakeDiskContent := "fake-raw-img"
var regionName, bucketName, amiName string
var uploadOpts *awscloud.UploaderOptions
for _, tc := range []struct {
fakeDiskName string
targetArchArg string
expectedUploadArch string
expectedWarning string
}{
// simple case: explicit target arch, no warning
{"fake-disk.img", "aarch64", "aarch64", ""},
// no target arch, detectable from filename: add note
{"centos-9-ami-aarch64.img", "", "aarch64", `Note: using architecture "aarch64" based on image filename (use --arch to override)` + "\n"},
// no target arch, not detectable form filename: we warn and expect host arch
{"fake-disk.img", "", arch.Current().String(), fmt.Sprintf("WARNING: no upload architecture specified, using %q (use --arch to override)\n", arch.Current().String())},
} {
fakeImageFilePath := filepath.Join(t.TempDir(), tc.fakeDiskName)
err := os.WriteFile(fakeImageFilePath, []byte(fakeDiskContent), 0644)
assert.NoError(t, err)
var fa fakeAwsUploader
restore := main.MockAwscloudNewUploader(func(region string, bucket string, ami string, opts *awscloud.UploaderOptions) (cloud.Uploader, error) {
regionName = region
bucketName = bucket
amiName = ami
uploadOpts = opts
return &fa, nil
})
defer restore()
var fakeStdout, fakeStderr bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()
restore = main.MockOsStderr(&fakeStderr)
defer restore()
restore = main.MockOsArgs([]string{
"upload",
"--to=aws",
"--aws-region=aws-region-1",
"--aws-bucket=aws-bucket-2",
"--aws-ami-name=aws-ami-3",
"--arch=" + tc.targetArchArg,
fakeImageFilePath,
})
defer restore()
err = main.Run()
require.NoError(t, err)
assert.Equal(t, regionName, "aws-region-1")
assert.Equal(t, bucketName, "aws-bucket-2")
assert.Equal(t, amiName, "aws-ami-3")
expectedBootMode := platform.BOOT_HYBRID
targetArch, err := arch.FromString(tc.expectedUploadArch)
assert.NoError(t, err)
assert.Equal(t, &awscloud.UploaderOptions{TargetArch: targetArch, BootMode: &expectedBootMode}, uploadOpts)
assert.Equal(t, 0, fa.checkCalls)
assert.Equal(t, 1, fa.uploadAndRegisterCalls)
assert.Equal(t, fakeDiskContent, fa.uploadAndRegisterRead.String())
// progress was rendered
assert.Contains(t, fakeStdout.String(), "--] 100.00%")
// warning was passed
assert.Equal(t, fakeStderr.String(), tc.expectedWarning)
}
}
func TestUploadCmdlineErrors(t *testing.T) {
var fakeStderr bytes.Buffer
restore := main.MockOsStderr(&fakeStderr)
defer restore()
for _, tc := range []struct {
cmdline []string
expectedErr string
}{
{
nil,
`missing --to parameter, try --to=aws`,
}, {
[]string{"--to=aws"},
`missing all upload configuration: ["--aws-ami-name" "--aws-bucket" "--aws-region"]`,
},
{
[]string{"--to=aws", "--aws-ami-name=1"},
`missing upload configuration: ["--aws-bucket" "--aws-region"]`,
},
{
[]string{"--to=aws", "--aws-ami-name=1", "--aws-bucket=2"},
`missing upload configuration: ["--aws-region"]`,
},
} {
t.Run(strings.Join(tc.cmdline, ","), func(t *testing.T) {
cmd := append([]string{"upload"}, tc.cmdline...)
cmd = append(cmd, "/path/to/some/image")
restore := main.MockOsArgs(cmd)
defer restore()
err := main.Run()
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestBuildAndUploadWithAWSMock(t *testing.T) {
if testing.Short() {
t.Skip("manifest generation takes a while")
}
if !hasDepsolveDnf() {
t.Skip("no osbuild-depsolve-dnf binary found")
}
var regionName, bucketName, amiName string
var fa fakeAwsUploader
var uploadOpts *awscloud.UploaderOptions
restore := main.MockAwscloudNewUploader(func(region string, bucket string, ami string, opts *awscloud.UploaderOptions) (cloud.Uploader, error) {
regionName = region
bucketName = bucket
amiName = ami
uploadOpts = opts
return &fa, nil
})
defer restore()
outputDir := t.TempDir()
fakeOsbuildScript := makeFakeOsbuildScript()
testutil.MockCommand(t, "osbuild", fakeOsbuildScript)
var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()
restore = main.MockOsArgs([]string{
"build",
"--output-dir", outputDir,
"--aws-region=aws-region-1",
"--aws-bucket=aws-bucket-2",
"--aws-ami-name=aws-ami-3",
"ami",
"--distro=centos-9",
})
defer restore()
err := main.Run()
require.NoError(t, err)
assert.Equal(t, regionName, "aws-region-1")
assert.Equal(t, bucketName, "aws-bucket-2")
assert.Equal(t, amiName, "aws-ami-3")
expectedBootMode := platform.BOOT_HYBRID
assert.Equal(t, &awscloud.UploaderOptions{BootMode: &expectedBootMode, TargetArch: arch.Current()}, uploadOpts)
assert.Equal(t, 1, fa.checkCalls)
assert.Equal(t, 1, fa.uploadAndRegisterCalls)
assert.Equal(t, "fake-img-raw\n", fa.uploadAndRegisterRead.String())
}
func TestBuildAmiButNotUpload(t *testing.T) {
if testing.Short() {
t.Skip("manifest generation takes a while")
}
if !hasDepsolveDnf() {
t.Skip("no osbuild-depsolve-dnf binary found")
}
fa := fakeAwsUploader{
uploadAndRegisterErr: fmt.Errorf("upload should not be called"),
}
restore := main.MockAwscloudNewUploader(func(region string, bucket string, ami string, opts *awscloud.UploaderOptions) (cloud.Uploader, error) {
return &fa, nil
})
defer restore()
outputDir := t.TempDir()
fakeOsbuildScript := makeFakeOsbuildScript()
testutil.MockCommand(t, "osbuild", fakeOsbuildScript)
var fakeStdout bytes.Buffer
restore = main.MockOsStdout(&fakeStdout)
defer restore()
restore = main.MockOsArgs([]string{
"build",
"--output-dir", outputDir,
"ami",
"--distro=centos-9",
})
defer restore()
err := main.Run()
require.NoError(t, err)
assert.Equal(t, 0, fa.uploadAndRegisterCalls)
}
func TestBuildAndUploadWithAWSPartialCmdlineErrors(t *testing.T) {
if testing.Short() {
t.Skip("manifest generation takes a while")
}
if !hasDepsolveDnf() {
t.Skip("no osbuild-depsolve-dnf binary found")
}
outputDir := t.TempDir()
fakeOsbuildScript := makeFakeOsbuildScript()
testutil.MockCommand(t, "osbuild", fakeOsbuildScript)
restore := main.MockOsArgs([]string{
"build",
"--output-dir", outputDir,
// note that --aws-{ami-name,bucket} is missing
"--aws-region=aws-region-1",
"ami",
"--distro=centos-9",
})
defer restore()
err := main.Run()
assert.EqualError(t, err, `missing upload configuration: ["--aws-ami-name" "--aws-bucket"]`)
}