import: move bib files to correct path
Moves the files imported from `bootc-image-builder` to the appropriate path under `pkg/` so they can be imported reverse. Also fix up the import paths that are in these files to refer to their new locations. Signed-off-by: Simon de Vlieger <supakeen@redhat.com>
This commit is contained in:
parent
441d408aee
commit
6d0927c2f9
6 changed files with 4 additions and 4 deletions
3
pkg/setup/export_test.go
Normal file
3
pkg/setup/export_test.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package setup
|
||||
|
||||
var ValidateCanRunTargetArch = validateCanRunTargetArch
|
||||
167
pkg/setup/setup.go
Normal file
167
pkg/setup/setup.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/osbuild/image-builder-cli/pkg/podmanutil"
|
||||
"github.com/osbuild/image-builder-cli/pkg/util"
|
||||
)
|
||||
|
||||
// EnsureEnvironment mutates external filesystem state as necessary
|
||||
// to run in a container environment. This function is idempotent.
|
||||
func EnsureEnvironment(storePath string) error {
|
||||
osbuildPath := "/usr/bin/osbuild"
|
||||
if util.IsMountpoint(osbuildPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Forcibly label the store to ensure we're not grabbing container labels
|
||||
rootType := "system_u:object_r:root_t:s0"
|
||||
// This papers over the lack of ensuring correct labels for the /ostree root
|
||||
// in the existing pipeline
|
||||
if err := util.RunCmdSync("chcon", rootType, storePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// A hardcoded security label from Fedora derivatives for osbuild
|
||||
// TODO: Avoid hardcoding this by using either host policy lookup
|
||||
// Or eventually depend on privileged containers just having this capability.
|
||||
//
|
||||
// We need this in order to get `install_t` that has `CAP_MAC_ADMIN` for creating SELinux
|
||||
// labels unknown to the host.
|
||||
//
|
||||
// Note that the transition to `install_t` must happen at this point. Osbuild stages run in `bwrap` that creates
|
||||
// a nosuid, no_new_privs environment. In such an environment, we cannot transition from `unconfined_t` to `install_t`,
|
||||
// because we would get more privileges.
|
||||
installType := "system_u:object_r:install_exec_t:s0"
|
||||
// Where we dump temporary files; this must be an overlayfs as we cannot
|
||||
// write security contexts on overlayfs.
|
||||
runTmp := "/run/osbuild/"
|
||||
|
||||
if err := os.MkdirAll(runTmp, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if !util.IsMountpoint(runTmp) {
|
||||
if err := util.RunCmdSync("mount", "-t", "tmpfs", "tmpfs", runTmp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
destPath := filepath.Join(runTmp, "osbuild")
|
||||
if err := util.RunCmdSync("cp", "-p", "/usr/bin/osbuild", destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.RunCmdSync("chcon", installType, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure we have devfs inside the container to get dynamic loop
|
||||
// loop devices inside the container.
|
||||
if err := util.RunCmdSync("mount", "-t", "devtmpfs", "devtmpfs", "/dev"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a bind mount into our target location; we can't copy it because
|
||||
// again we have to perserve the SELinux label.
|
||||
if err := util.RunCmdSync("mount", "--bind", destPath, osbuildPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// NOTE: Don't add new code here, do it before the bind mount which acts as the final success indicator
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks that the environment is supported (e.g. caller set up the
|
||||
// container correctly)
|
||||
func Validate(targetArch string) error {
|
||||
isRootless, err := podmanutil.IsRootless()
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking rootless: %w", err)
|
||||
}
|
||||
if isRootless {
|
||||
return fmt.Errorf("this command must be run in rootful (not rootless) podman")
|
||||
}
|
||||
|
||||
// Having /sys be writable is an easy to check proxy for privileges; more effective
|
||||
// is really looking for CAP_SYS_ADMIN, but that involves more Go libraries.
|
||||
var stvfsbuf unix.Statfs_t
|
||||
if err := unix.Statfs("/sys", &stvfsbuf); err != nil {
|
||||
return fmt.Errorf("failed to stat /sys: %w", err)
|
||||
}
|
||||
if (stvfsbuf.Flags & unix.ST_RDONLY) > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// ValidateHasContainerStorageMounted checks that the hostcontainer storage
|
||||
// is mounted inside the container
|
||||
func ValidateHasContainerStorageMounted() error {
|
||||
// Just look for the overlay backend, which we expect by default.
|
||||
// In theory, one could be using a different backend, but we don't
|
||||
// really need to worry about this right now. If it turns out
|
||||
// we do need to care, then we can probably handle this by
|
||||
// just trying to query the image.
|
||||
overlayPath := "/var/lib/containers/storage/overlay"
|
||||
if _, err := os.Stat(overlayPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("cannot find %q (missing -v /var/lib/containers/storage:/var/lib/containers/storage mount?)", overlayPath)
|
||||
}
|
||||
return fmt.Errorf("failed to stat %q: %w", overlayPath, err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func ValidateHasContainerTags(imgref string) error {
|
||||
output, err := exec.Command("podman", "image", "inspect", imgref, "--format", "{{.Labels}}").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to inspect the image: %w
|
||||
bootc-image-builder no longer pulls images, make sure to pull it before running bootc-image-builder:
|
||||
sudo podman pull %s`, util.OutputErr(err), imgref)
|
||||
}
|
||||
|
||||
tags := string(output)
|
||||
if !strings.Contains(tags, "containers.bootc:1") {
|
||||
return fmt.Errorf("image %s is not a bootc image", imgref)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
113
pkg/setup/setup_test.go
Normal file
113
pkg/setup/setup_test.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package setup_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/osbuild/image-builder-cli/pkg/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 makeFakeBinary(t *testing.T, binary, content string) {
|
||||
tmpdir := t.TempDir()
|
||||
t.Setenv("PATH", tmpdir+":"+os.Getenv("PATH"))
|
||||
err := os.WriteFile(filepath.Join(tmpdir, binary), []byte(content), 0o755)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func makeFakeCanary(t *testing.T, content string) {
|
||||
makeFakeBinary(t, "bib-canary-fakearch", content)
|
||||
}
|
||||
|
||||
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`)
|
||||
}
|
||||
|
||||
var (
|
||||
fakePodmanOutputCentosBootc = `map[containers.bootc:1 io.buildah.version:1.29.1 org.opencontainers.image.version:stream9.20240319.0 ostree.bootable:true ostree.commit:97d619eae2a5474a9c363c78e3ad6caec14acba54a0b077c7cb69d00a4f800a5 ostree.final-diffid:sha256:12787d84fa137cd5649a9005efe98ec9d05ea46245fdc50aecb7dd007f2035b1 ostree.linux:5.14.0-430.el9.x86_64 redhat.compose-id:CentOS-Stream-9-20240304.d.0 redhat.id:centos redhat.version-id:9 rpmostree.inputhash:a5c67fd4e9465e47e01922171c6ab8edf261d2d381e590b5cd7fed81ea8d4dbe]`
|
||||
|
||||
fakePodmanOutputCentos = `map[io.buildah.version:1.33.7 org.label-schema.build-date:20240618 org.label-schema.license:GPLv2 org.label-schema.name:CentOS Stream 9 Base Image org.label-schema.schema-version:1.0 org.label-schema.vendor:CentOS]`
|
||||
|
||||
emptyPodmanOutput = `map[]`
|
||||
)
|
||||
|
||||
func TestValidateTags(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
imageref string
|
||||
fakeOutput string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
"quay.io/centos-bootc/centos-bootc:stream9",
|
||||
fakePodmanOutputCentosBootc,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"quay.io/centos/centos:stream9",
|
||||
fakePodmanOutputCentos,
|
||||
"image quay.io/centos/centos:stream9 is not a bootc image",
|
||||
},
|
||||
{
|
||||
"fake/image",
|
||||
emptyPodmanOutput,
|
||||
"image fake/image is not a bootc image",
|
||||
},
|
||||
} {
|
||||
podmanArgsFile := filepath.Join(t.TempDir(), "args.txt")
|
||||
fakePodman := fmt.Sprintf(`#!/bin/sh -e
|
||||
echo "$@" > '%s'
|
||||
echo '%s'
|
||||
`, podmanArgsFile, tc.fakeOutput)
|
||||
makeFakeBinary(t, "podman", fakePodman)
|
||||
err := setup.ValidateHasContainerTags(tc.imageref)
|
||||
if tc.expectedErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.EqualError(t, err, tc.expectedErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue