debian-forge-cli/bib/internal/setup/setup.go
Gianluca Zuccarelli 441d408aee bib: deprecate the --local flag
Pulling container images before building would break in the case of
authenticated images on podman machine, since the auth file lives on the
host and not podman machine and won't know about it.

This commit deprecates the `--local` flag and warns users when it is
passed to the CLI so that this won't break things for anyone who might
already be using the flag. This change means that the user will have to
ensure that the container is pulled to the local container store before
initiating the build.

Co-authored-by: Ondřej Budai <obudai@redhat.com>
2025-03-31 18:19:33 +00:00

167 lines
5.7 KiB
Go

package setup
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"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"
)
// 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
}