bib: detect missing qemu-user early

This commit checks early if cross architecture building support via
`qemu-user-static` (or similar tooling) is missing and errors in
a more user friendly way.

Note that there is no integration test right now because testing
this for real requires mutating the very global state of
`echo 0 > /proc/sys/fs/binfmt_misc/qemu-aarch64`
which would make the test non-parallelizable and even risks
failing other cross-arch tests running on the same host (because
binfmt-misc is not namespaced (yet)).
This commit is contained in:
Michael Vogt 2024-04-08 15:17:38 +02:00 committed by Simon de Vlieger
parent b3ef264353
commit 4fa4ad34a0
3 changed files with 100 additions and 1 deletions

View file

@ -0,0 +1,3 @@
package setup
var ValidateCanRunTargetArch = validateCanRunTargetArch

View file

@ -3,10 +3,14 @@ package setup
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"golang.org/x/sys/unix"
"github.com/sirupsen/logrus"
"github.com/osbuild/bootc-image-builder/bib/internal/podmanutil"
"github.com/osbuild/bootc-image-builder/bib/internal/util"
)
@ -76,7 +80,7 @@ func EnsureEnvironment(storePath string) error {
// Validate checks that the environment is supported (e.g. caller set up the
// container correctly)
func Validate() error {
func Validate(targetArch string) error {
isRootless, err := podmanutil.IsRootless()
if err != nil {
return fmt.Errorf("checking rootless: %w", err)
@ -95,6 +99,11 @@ func Validate() error {
return fmt.Errorf("this command requires a privileged container")
}
// Try to run the cross arch binary
if err := validateCanRunTargetArch(targetArch); err != nil {
return fmt.Errorf("cannot run binary in target arch: %w", err)
}
return nil
}
@ -115,3 +124,27 @@ func ValidateHasContainerStorageMounted() error {
}
return nil
}
func validateCanRunTargetArch(targetArch string) error {
if targetArch == runtime.GOARCH || targetArch == "" {
return nil
}
canaryCmd := fmt.Sprintf("bib-canary-%s", targetArch)
if _, err := exec.LookPath(canaryCmd); err != nil {
// we could error here but in principle with a working qemu-user
// any arch should work so let's just warn. the common case
// (arm64/amd64) is covered properly
logrus.Warningf("cannot check architecture support for %v: no canary binary found", targetArch)
return nil
}
output, err := exec.Command(canaryCmd).CombinedOutput()
if err != nil {
return fmt.Errorf("cannot run canary binary for %q, do you have 'qemu-user-static' installed?\n%s", targetArch, err)
}
if string(output) != "ok\n" {
return fmt.Errorf("internal error: unexpected output from cross-architecture canary: %q", string(output))
}
return nil
}

View file

@ -0,0 +1,63 @@
package setup_test
import (
"bytes"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/osbuild/bootc-image-builder/bib/internal/setup"
)
func TestValidateCanRunTargetArchTrivial(t *testing.T) {
for _, arch := range []string{runtime.GOARCH, ""} {
err := setup.ValidateCanRunTargetArch(arch)
assert.NoError(t, err)
}
}
func TestValidateCanRunTargetArchUnsupportedCanary(t *testing.T) {
var logbuf bytes.Buffer
logrus.SetOutput(&logbuf)
err := setup.ValidateCanRunTargetArch("unsupported-arch")
assert.NoError(t, err)
assert.Contains(t, logbuf.String(), `level=warning msg="cannot check architecture support for unsupported-arch: no canary binary found"`)
}
func makeFakeCanary(t *testing.T, content string) {
tmpdir := t.TempDir()
t.Setenv("PATH", os.Getenv("PATH")+":"+tmpdir)
err := os.WriteFile(filepath.Join(tmpdir, "bib-canary-fakearch"), []byte(content), 0755)
assert.NoError(t, err)
}
func TestValidateCanRunTargetArchHappy(t *testing.T) {
var logbuf bytes.Buffer
logrus.SetOutput(&logbuf)
makeFakeCanary(t, "#!/bin/sh\necho ok")
err := setup.ValidateCanRunTargetArch("fakearch")
assert.NoError(t, err)
assert.Equal(t, "", logbuf.String())
}
func TestValidateCanRunTargetArchExecFormatError(t *testing.T) {
makeFakeCanary(t, "")
err := setup.ValidateCanRunTargetArch("fakearch")
assert.ErrorContains(t, err, `cannot run canary binary for "fakearch", do you have 'qemu-user-static' installed?`)
assert.ErrorContains(t, err, `: exec format error`)
}
func TestValidateCanRunTargetArchUnexpectedOutput(t *testing.T) {
makeFakeCanary(t, "#!/bin/sh\necho xxx")
err := setup.ValidateCanRunTargetArch("fakearch")
assert.ErrorContains(t, err, `internal error: unexpected output`)
}