From 7cad7edc1e79c4827e4bc00cb1700ac48e7971ad Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Wed, 22 Apr 2026 16:58:12 +0200 Subject: [PATCH 01/21] pkg/shell: Preserve error types in shell command execution The existing RunContextWithExitCode() wraps all errors from exec.Command in generic "failed to invoke" messages, which prevents callers from distinguishing between actual error types. Add RunContextWithExitCode2() and RunWithExitCode2() that return errors with their original types intact, including for ExitError. This allows callers to use errors.Is() and errors.As() to handle specific failure modes. For example, detecting a missing skopeo binary (exec.ErrNotFound) or an ENOEXEC error from inside non native containers, when an emulator is not set correctly. These new functions are meant to replace its original versions in the future. https://github.com/containers/toolbox/pull/1780 Signed-off-by: Dalibor Kricka --- src/pkg/shell/shell.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/pkg/shell/shell.go b/src/pkg/shell/shell.go index 00fa36997..6c9cfcacd 100644 --- a/src/pkg/shell/shell.go +++ b/src/pkg/shell/shell.go @@ -81,8 +81,49 @@ func RunContextWithExitCode(ctx context.Context, return 0, nil } +func RunContextWithExitCode2(ctx context.Context, + name string, + stdin io.Reader, + stdout, stderr io.Writer, + arg ...string) (int, error) { + + logLevel := logrus.GetLevel() + if stderr == nil && logLevel >= logrus.DebugLevel { + stderr = os.Stderr + } + + cmd := exec.CommandContext(ctx, name, arg...) + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + + if err := cmd.Run(); err != nil { + exitCode := 1 + + if ctxErr := ctx.Err(); ctxErr != nil { + return 1, ctxErr + } + + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + exitCode = exitErr.ExitCode() + return exitCode, err + } + + return exitCode, err + } + + return 0, nil +} + func RunWithExitCode(name string, stdin io.Reader, stdout, stderr io.Writer, arg ...string) (int, error) { ctx := context.Background() exitCode, err := RunContextWithExitCode(ctx, name, stdin, stdout, stderr, arg...) return exitCode, err } + +func RunWithExitCode2(name string, stdin io.Reader, stdout, stderr io.Writer, arg ...string) (int, error) { + ctx := context.Background() + exitCode, err := RunContextWithExitCode2(ctx, name, stdin, stdout, stderr, arg...) + return exitCode, err +} From fae8f4a0614dc549fe832e343d9bc8ebc43f72a4 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Wed, 22 Apr 2026 23:21:55 +0200 Subject: [PATCH 02/21] cmd/create: Extract spinner setup into helper functions In /src/cmd/create.go, the same pattern of spinner creation and nil-safe stopping is repeated. Extract this into startSpinner() and stopSpinner() helper functions so that future callers can use spinners without duplicating the code. Replace the existing inline spinner code in createContainer() and pullImage() with calls to these new helpers. https://github.com/containers/toolbox/pull/1781 Signed-off-by: Dalibor Kricka --- src/cmd/create.go | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/cmd/create.go b/src/cmd/create.go index bdd86be37..13345289d 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -486,19 +486,15 @@ func createContainer(container, image, release, authFile string, showCommandToEn logrus.Debugf("%s", arg) } - s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout)) - if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel { - s.Prefix = fmt.Sprintf("Creating container %s: ", container) - s.Start() - defer s.Stop() - } + s := startSpinner(fmt.Sprintf("Creating container %s: ", container)) + defer stopSpinner(s) if err := shell.Run("podman", nil, nil, nil, createArgs...); err != nil { return fmt.Errorf("failed to create container %s", container) } // The spinner must be stopped before showing the 'enter' hint below. - s.Stop() + stopSpinner(s) if showCommandToEnter { fmt.Printf("Created container: %s\n", container) @@ -735,12 +731,8 @@ func pullImage(image, release, authFile string) (bool, error) { logrus.Debugf("Pulling image %s", imageFull) - if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel { - s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout)) - s.Prefix = fmt.Sprintf("Pulling %s: ", imageFull) - s.Start() - defer s.Stop() - } + s := startSpinner(fmt.Sprintf("Pulling %s: ", imageFull)) + defer stopSpinner(s) if err := podman.Pull(imageFull, authFile); err != nil { var builder strings.Builder @@ -963,6 +955,22 @@ func showPromptForDownload(imageFull string) bool { return shouldPullImage } +func startSpinner(message string) *spinner.Spinner { + if logLevel := logrus.GetLevel(); logLevel < logrus.DebugLevel { + s := spinner.New(spinner.CharSets[9], 500*time.Millisecond, spinner.WithWriterFile(os.Stdout)) + s.Prefix = message + s.Start() + return s + } + return nil +} + +func stopSpinner(s *spinner.Spinner) { + if s != nil { + s.Stop() + } +} + // systemdNeedsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped func systemdNeedsEscape(i int, b byte) bool { // Escape everything that is not a-z-A-Z-0-9 From ddebd00b7edb62abb32f17ba81fdec387b1f5e18 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Wed, 22 Apr 2026 23:30:29 +0200 Subject: [PATCH 03/21] pkg/utils: Add IsSupportedDistroImage for image to supported distro matching Add IsSupportedDistroImage(), which iterates over all supported distros and checks if the image basename matches any of them. This will be used by the architecture resolution code to decide whether to parse architecture suffixes from image tags, as this should be done only for natively supported images [1]. [1] Toolbx supported distributions: https://containertoolbx.org/distros/ https://github.com/containers/toolbox/pull/1781 Signed-off-by: Dalibor Kricka --- src/pkg/utils/utils.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go index f3de23b1b..c88f8ef67 100644 --- a/src/pkg/utils/utils.go +++ b/src/pkg/utils/utils.go @@ -664,6 +664,20 @@ func IsP11KitClientPresent() (bool, error) { return false, err } +func IsSupportedDistroImage(image string) bool { + basename := ImageReferenceGetBasename(image) + if basename == "" { + return false + } + + for _, distroObj := range supportedDistros { + if distroObj.ImageBasename == basename { + return true + } + } + return false +} + func SetUpConfiguration() error { logrus.Debug("Setting up configuration") From 211ab9471775dbe50790ed98f6b238e69f20a3b0 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 08:12:19 +0200 Subject: [PATCH 04/21] pkg/architecture: Define core architecture types and constants Introduce the architecture package that represents the core of the Toolbx cross-architecture support, which is based on user-mode emulation using QEMU and binfmt_misc. The Architecture struct collects all per-architecture data (ELF magic/mask, OCI and binfmt naming, aliases, binfmt registration parameters) into a single map. Architectures present in the supportedArchitectures map represent the set of supported architectures within Toolbx. Define architecture ID constants NotSpecified, Aarch64, Ppc64le, and X86_64, along with their supportedArchitectures entries. Add core query functions: - ParseArgArchValue() for resolving user-supplied architecture strings - GetArchNameBinfmt() and GetArchNameOCI() for name lookups (one architecture can have multiple valid names [1]) - HasContainerNativeArch() for comparing against the host - ImageReferenceGetArchFromTag() for extracting architecture from image tag suffixes like "42-aarch64" for architecture detection Expose the HostArchID package variable, which is set in the init() function, so the variable can be accessed in the early init() state from every inheritor that utilizes the architecture package (HostArchID serves as a default value for initContainer --arch flag), and the Config struct for preserving the architecture ID and the QEMU emulator path, through the call chain. [1] https://itsfoss.com/arm-aarch64-x86_64/ https://github.com/containers/toolbox/pull/1782 Signed-off-by: Dalibor Kricka --- src/pkg/architecture/architecture.go | 165 +++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/pkg/architecture/architecture.go diff --git a/src/pkg/architecture/architecture.go b/src/pkg/architecture/architecture.go new file mode 100644 index 000000000..24f732230 --- /dev/null +++ b/src/pkg/architecture/architecture.go @@ -0,0 +1,165 @@ +/* + * Copyright © 2019 – 2026 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package architecture + +import ( + "fmt" + "runtime" + "strings" + + "github.com/containers/toolbox/pkg/utils" + "github.com/sirupsen/logrus" +) + +type Architecture struct { + ID int + NameBinfmt string + NameOCI string + Aliases []string + ELFMagic []byte + ELFMask []byte + + BinfmtFlags string + BinfmtName string + BinfmtMagicType string + BinfmtOffset string +} + +type Config struct { + ID int + QemuEmulatorPath string +} + +const ( + NotSpecified = iota + Aarch64 + Ppc64le + X86_64 +) + +var supportedArchitectures = map[int]Architecture{ + Aarch64: { + ID: Aarch64, + NameBinfmt: "aarch64", + NameOCI: "arm64", + Aliases: []string{"aarch64", "arm64"}, + ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xb7, 0x00}, + ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff}, + }, + Ppc64le: { + ID: Ppc64le, + NameBinfmt: "ppc64le", + NameOCI: "ppc64le", + Aliases: []string{"ppc64le"}, + ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x15, 0x00}, + ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x00}, + }, + X86_64: { + ID: X86_64, + NameBinfmt: "x86_64", + NameOCI: "amd64", + Aliases: []string{"x86_64", "amd64"}, + ELFMagic: []byte{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00}, + ELFMask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff}, + }, +} + +var ( + HostArchID int + supportedArgArchValues map[string]int +) + +func init() { + supportedArgArchValues = make(map[string]int) + for archID, arch := range supportedArchitectures { + for _, alias := range arch.Aliases { + supportedArgArchValues[alias] = archID + } + } + + HostArchID, _ = ParseArgArchValue(runtime.GOARCH) +} + +func GetArchConfigDefault() Config { + return Config{ + ID: HostArchID, + QemuEmulatorPath: "", + } +} + +func getArchitecture(archID int) (Architecture, bool) { + arch, exists := supportedArchitectures[archID] + return arch, exists +} + +func getArchNameBinfmt(arch int) string { + if arch == NotSpecified { + logrus.Warnf("Getting arch name for not specified architecture") + return "arch_not_specified" + } + if archObj, exists := supportedArchitectures[arch]; exists { + return archObj.NameBinfmt + } + return "" +} + +func GetArchNameOCI(arch int) string { + if arch == NotSpecified { + logrus.Warnf("Getting arch name for not specified architecture") + return "arch_not_specified" + } + if archObj, exists := supportedArchitectures[arch]; exists { + return archObj.NameOCI + } + return "" +} + +func HasContainerNativeArch(archID int) bool { + return archID == HostArchID +} + +func ImageReferenceGetArchFromTag(image string) int { + tag := utils.ImageReferenceGetTag(image) + + if tag == "" { + return NotSpecified + } + + i := strings.LastIndexByte(tag, '-') + if i == -1 { + return NotSpecified + } + + archInTag := tag[i+1:] + + for archID, arch := range supportedArchitectures { + if arch.NameBinfmt == archInTag || arch.NameOCI == archInTag { + return archID + } + } + + return NotSpecified +} + +func ParseArgArchValue(value string) (int, error) { + archID, exists := supportedArgArchValues[value] + if !exists { + return NotSpecified, fmt.Errorf("architecture '%s' is not supported by Toolbx", value) + } + + return archID, nil +} From a0a4ede111d194d263a9219ca7ae9864a6e50748 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 08:37:54 +0200 Subject: [PATCH 05/21] pkg/architecture: Add sandboxed binfmt_misc registration support Cross-architecture containers need QEMU binfmt_misc handlers registered within the container so that non-native architecture binaries can be executed via the host's kernel. Add the Registration struct that models a binfmt_misc registration entry, including name, magic type, offset, ELF magic/mask bytes, interpreter path, and flags. Add functions: - MountBinfmtMisc() to mount the sanboxed binfmt_misc filesystem inside a container, which enables setting the C flag in binfmt_misc registration without affecting the host system. The C flag presents a threat of privilege escalation when registered on the host, that why we want to have it isolated [1] - getDefaultRegistration() to fill a Registration struct containing all necessary binfmt_misc information taken from the architecture.supportedArchitectures data - RegisterBinfmtMisc() to write the registration string to /proc/sys/fs/binfmt_misc/register, which makes the non-native binary perception active - bytesToEscapedString() helper that formats byte slices into the \xHH-escaped string format required by the binfmt_misc register interface [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=21ca59b365c0 https://github.com/containers/toolbox/pull/1782 Signed-off-by: Dalibor Kricka --- src/pkg/architecture/binfmt_misc.go | 151 ++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/pkg/architecture/binfmt_misc.go diff --git a/src/pkg/architecture/binfmt_misc.go b/src/pkg/architecture/binfmt_misc.go new file mode 100644 index 000000000..3dc8eddbb --- /dev/null +++ b/src/pkg/architecture/binfmt_misc.go @@ -0,0 +1,151 @@ +/* + * Copyright © 2019 – 2026 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package architecture + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/toolbox/pkg/shell" + "github.com/sirupsen/logrus" +) + +type Registration struct { + Name string + MagicType string + Offset string + Magic []byte + Mask []byte + Interpreter string + Flags string +} + +const ( + defaultMagicType = "M" + defaultFlags = "FC" + defaultOffset = "0" + binfmtMiscPath = "/proc/sys/fs/binfmt_misc" +) + +func (r *Registration) buildRegistrationString() string { + return fmt.Sprintf(":%s:%s:%s:%s:%s:%s:%s", + r.Name, r.MagicType, r.Offset, + bytesToEscapedString(r.Magic), + bytesToEscapedString(r.Mask), + r.Interpreter, r.Flags) +} + +func (r *Registration) register() error { + logrus.Debugf("Registering binfmt_misc for %s", r.Name) + + regString := r.buildRegistrationString() + logrus.Debugf("Registration string: %s", regString) + + if err := os.WriteFile(filepath.Join(binfmtMiscPath, "register"), []byte(regString), 0200); err != nil { + return fmt.Errorf("failed to register binfmt_misc handler: %w", err) + } + return nil +} + +func bytesToEscapedString(bytes []byte) string { + var result strings.Builder + for _, b := range bytes { + result.WriteString(fmt.Sprintf("\\x%02x", b)) + } + return result.String() +} + +func getDefaultRegistration(archID int, interpreterPath string) *Registration { + arch, exists := getArchitecture(archID) + if !exists { + return nil + } + + var name string + flags := defaultFlags + magicType := defaultMagicType + offset := defaultOffset + + if arch.BinfmtName != "" { + name = arch.BinfmtName + } else { + name = "qemu-" + arch.NameBinfmt + } + + if arch.BinfmtFlags != "" { + flags = arch.BinfmtFlags + } + + if arch.BinfmtMagicType != "" { + magicType = arch.BinfmtMagicType + } + + if arch.BinfmtOffset != "" { + offset = arch.BinfmtOffset + } + + interpreter := interpreterPath + if !strings.HasPrefix(interpreterPath, "/run/host/") { + interpreter = filepath.Join("/run/host", interpreter) + } + + return &Registration{ + Name: name, + MagicType: magicType, + Offset: offset, + Magic: arch.ELFMagic, + Mask: arch.ELFMask, + Interpreter: interpreter, + Flags: flags, + } +} + +func MountBinfmtMisc() error { + args := []string{ + "binfmt_misc", + "-t", + "binfmt_misc", + binfmtMiscPath, + } + + var stdout bytes.Buffer + + if err := shell.Run("mount", nil, &stdout, nil, args...); err != nil { + return fmt.Errorf("failed to mount binfmt_misc: %w", err) + } + + logrus.Debugf("Result of mount command: %s", stdout.String()) + + return nil +} + +func RegisterBinfmtMisc(archID int, interpreterPath string) error { + reg := getDefaultRegistration(archID, interpreterPath) + if reg == nil { + logrus.Debugf("Unable to register binfmt_misc for architecture '%s'", GetArchNameOCI(archID)) + return fmt.Errorf("Toolbx does not support architecture '%s'", GetArchNameOCI(archID)) + } + + if err := reg.register(); err != nil { + return err + } + + return nil +} From 8fe8ef5d4245809c0750f42ce32d9c706a362759 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 13:32:40 +0200 Subject: [PATCH 06/21] pkg/architecture: Add architecture support validation Before creating or initializing a cross-architecture container, the system must be checked for the required QEMU emulator and binfmt_misc registration. This prevents users from creating or running non-native containers when their host system doesn't meet the requirements, and provides users with an informative error message referring to the problem. Add IsArchSupportedOnCreation(), which searches for a statically linked QEMU binary on the host using exec.LookPath() and verifies that a matching binfmt_misc registration exists. It returns the path to the QEMU binary for use during container creation, which is meant to be passed to the init-container and registered through sandboxed binfmt_misc within the container. Add IsArchSupportedOnInitialization() which performs similar checks from inside the container, looking at the interpreter path passed from the host and falling back to standard host-mounted locations under /run/host/usr/bin/. Add isStaticallyLinkedELF() helper that uses debug/elf to verify a binary is statically linked. Only a statically linked QEMU interpreter can be used, because a dynamically linked one would cause the kernel to attempt to resolve its host-native shared libraries (such as libc.so) within the container, resulting in an immediate crash. Add validateBinfmtRegistration(), which checks for the presence of qemu- entries in binfmt_misc (or qemu--static, since it can differ based on the system). https://github.com/containers/toolbox/pull/1783 Signed-off-by: Dalibor Kricka --- src/pkg/architecture/architecture.go | 128 +++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/src/pkg/architecture/architecture.go b/src/pkg/architecture/architecture.go index 24f732230..652ed224d 100644 --- a/src/pkg/architecture/architecture.go +++ b/src/pkg/architecture/architecture.go @@ -17,7 +17,11 @@ package architecture import ( + "debug/elf" + "errors" "fmt" + "os" + "os/exec" "runtime" "strings" @@ -155,6 +159,108 @@ func ImageReferenceGetArchFromTag(image string) int { return NotSpecified } +func IsArchSupportedOnCreation(archID int) (string, error) { + archName := getArchNameBinfmt(archID) + archNameDebug := GetArchNameOCI(archID) + logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug) + + qemuBinaryPossibleNames := []string{ + fmt.Sprintf("qemu-%s-static", archName), + fmt.Sprintf("qemu-%s", archName), + } + + foundQemuBinaryPath := "" + for _, qemuName := range qemuBinaryPossibleNames { + qemuBinaryPath, err := exec.LookPath(qemuName) + + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + continue + } + + return "", fmt.Errorf("failed to look up binary '%s': %w", qemuName, err) + } + + if isStaticallyLinkedELF(qemuBinaryPath) { + foundQemuBinaryPath = qemuBinaryPath + break + } + } + + if foundQemuBinaryPath == "" { + err := fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug) + return "", err + } + + if !validateBinfmtRegistration(archID, false) { + err := fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug) + return "", err + } + + return foundQemuBinaryPath, nil +} + +func IsArchSupportedOnInitialization(archID int, interpreterPath string) (string, error) { + archName := getArchNameBinfmt(archID) + archNameDebug := GetArchNameOCI(archID) + logrus.Debugf("Checking QEMU emulation support for architecture %s", archNameDebug) + + if isStaticallyLinkedELF(interpreterPath) { + if !validateBinfmtRegistration(archID, true) { + return "", fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug) + } + return interpreterPath, nil + } + + // Fallback: check standard locations on the host + logrus.Debugf("Interpreter at %s not found or not statically linked, checking fallback locations in '/run/host/usr/bin/'", interpreterPath) + fmt.Fprintf(os.Stderr, "Warning: QEMU emulator not found at expected path '%s', using fallback at '/run/host/usr/bin/'\n", interpreterPath) + + qemuBinaryPossiblePaths := []string{ + fmt.Sprintf("/run/host/usr/bin/qemu-%s-static", archName), + fmt.Sprintf("/run/host/usr/bin/qemu-%s", archName), + } + + for _, qemuPath := range qemuBinaryPossiblePaths { + if isStaticallyLinkedELF(qemuPath) { + logrus.Debugf("Found valid QEMU binary at %s", qemuPath) + + if !validateBinfmtRegistration(archID, true) { + return "", fmt.Errorf("The host system does not have the required support: No %s binfmt_misc registration found", archNameDebug) + } + return qemuPath, nil + } + } + + return "", fmt.Errorf("The host system does not have the required support: No %s statically linked QEMU emulator binary found", archNameDebug) +} + +func isStaticallyLinkedELF(filePath string) bool { + if !utils.PathExists(filePath) { + logrus.Debugf("File '%s' does not exist\n", filePath) + return false + } + + f, err := elf.Open(filePath) + if err != nil { + logrus.Debugf("File '%s' is not an ELF file\n", filePath) + return false + } + defer f.Close() + + // Check for PT_INTERP program header + for _, prog := range f.Progs { + if prog.Type == elf.PT_INTERP { + // Dynamically linked + logrus.Debugf("File '%s' is dynamically linked\n", filePath) + return false + } + } + + // Statically linked + return true +} + func ParseArgArchValue(value string) (int, error) { archID, exists := supportedArgArchValues[value] if !exists { @@ -163,3 +269,25 @@ func ParseArgArchValue(value string) (int, error) { return archID, nil } + +func validateBinfmtRegistration(archID int, withinContainer bool) bool { + archName := getArchNameBinfmt(archID) + inContainerPathPrefix := "" + + if withinContainer { + inContainerPathPrefix = "/run/host" + } + + qemuBinfmtPossiblePaths := []string{ + fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s", inContainerPathPrefix, archName), + fmt.Sprintf("%s/proc/sys/fs/binfmt_misc/qemu-%s-static", inContainerPathPrefix, archName), + } + + for _, binfmtPath := range qemuBinfmtPossiblePaths { + if utils.PathExists(binfmtPath) { + logrus.Debugf("Architecture %s is supported", archName) + return true + } + } + return false +} From 689883263f67f37677d637520acf870372cc34f5 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 15:21:13 +0200 Subject: [PATCH 07/21] pkg/skopeo: Extend Skopeo Image struct and add size computation methods Add Architecture and NameFull fields to the Skopeo Image struct so that callers can inspect the architecture of a remote image. Move the image size computation from the /cmd layer into GetSize() and GetSizeHuman() methods on Image, since the skopeo package owns the layer data. Add VerifyArchitectureMatch() method to Image that validates the image's architecture field against an expected architecture ID. The purpose of this function is to check whether the image architecture matches the demanded architecture before it is pulled. Specifically, this verification applies to the images that support only a single architecture (they are not part of a multi-platform manifest list), because the skopeo inspect proceeds successfully even when the value of a flag --override-arch does not match the actual image architecture (for a multi-architecture image the skopeo inspect with not-matching --override-arch would fail). Like this, the user can be prevented from incompatible images. https://github.com/containers/toolbox/pull/1784 Signed-off-by: Dalibor Kricka --- src/pkg/skopeo/skopeo.go | 57 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/pkg/skopeo/skopeo.go b/src/pkg/skopeo/skopeo.go index 8b15c7370..8454e6a80 100644 --- a/src/pkg/skopeo/skopeo.go +++ b/src/pkg/skopeo/skopeo.go @@ -20,15 +20,70 @@ import ( "bytes" "context" "encoding/json" + "errors" + "fmt" + "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/shell" + "github.com/docker/go-units" + "github.com/sirupsen/logrus" ) type Layer struct { Size json.Number } + type Image struct { - LayersData []Layer + Architecture string `json:"Architecture"` + LayersData []Layer + NameFull string +} + +func (image *Image) GetSize() (float64, error) { + var imageSizeFloat float64 + + if image.LayersData == nil { + return -1, errors.New("'skopeo inspect' did not have LayersData") + } + + for _, layer := range image.LayersData { + if layerSize, err := layer.Size.Float64(); err != nil { + return -1, err + } else { + imageSizeFloat += layerSize + } + } + + return imageSizeFloat, nil +} + +func (image *Image) GetSizeHuman() (string, error) { + imageSizeFloat, err := image.GetSize() + if err != nil { + return "", err + } + + imageSizeHuman := units.HumanSize(imageSizeFloat) + return imageSizeHuman, nil +} + +func (image *Image) VerifyArchitectureMatch(expectedArchID int) error { + expectedArchName := architecture.GetArchNameOCI(expectedArchID) + logrus.Debugf("Verifying image %s supports architecture %s", image.NameFull, expectedArchName) + + actualArchID, err := architecture.ParseArgArchValue(image.Architecture) + if err != nil { + return err + } + + if actualArchID != expectedArchID { + // Single-arch image mismatch + return fmt.Errorf("image %s is a single-architecture image for %s, but %s was requested", + image.NameFull, image.Architecture, expectedArchName) + } + + logrus.Debugf("Architecture verification passed: %s", expectedArchName) + return nil } func Inspect(ctx context.Context, target string) (*Image, error) { From fba0a72ddafeef0634efdbc16828f3debfb56bfe Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 15:57:10 +0200 Subject: [PATCH 08/21] pkg/skopeo: Add architecture-aware inspect and cross-arch copy Change Inspect() to accept archID and authfile parameters. When the requested architecture differs from the host's, --override-arch is passed to skopeo, which then inspects the correct manifest in a multi-arch image (if it exists for the given architecture, otherwise the inspection fails). It also uses RunContextWithExitCode2() so callers can detect a missing skopeo binary via errors.Is(err, exec.ErrNotFound), which is only a soft dependency of the Toolbx package, as it is not required for running native containers. Add CopyOverrideArch(), which uses 'skopeo copy --override-arch' to pull a specific architecture variant of a multi-arch image into Podman's local container storage. This is used instead of 'podman pull' because Podman does not support pulling a foreign architecture image into a locally addressable name. The way in which the cross-arch extension chooses the name for non-native images (and also containers) is described in the discussion at [1] [1] https://github.com/containers/podman/discussions/27780#discussioncomment-16662213 https://github.com/containers/toolbox/pull/1784 Signed-off-by: Dalibor Kricka --- src/cmd/create.go | 3 ++- src/pkg/skopeo/skopeo.go | 45 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/cmd/create.go b/src/cmd/create.go index 13345289d..09b444a61 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -26,6 +26,7 @@ import ( "time" "github.com/briandowns/spinner" + "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/podman" "github.com/containers/toolbox/pkg/shell" "github.com/containers/toolbox/pkg/skopeo" @@ -564,7 +565,7 @@ func getEnterCommand(container string) string { } func getImageSizeFromRegistry(ctx context.Context, imageFull string) (string, error) { - image, err := skopeo.Inspect(ctx, imageFull) + image, err := skopeo.Inspect(ctx, imageFull, architecture.HostArchID, "") if err != nil { return "", err } diff --git a/src/pkg/skopeo/skopeo.go b/src/pkg/skopeo/skopeo.go index 8454e6a80..7fd52be90 100644 --- a/src/pkg/skopeo/skopeo.go +++ b/src/pkg/skopeo/skopeo.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/shell" @@ -86,13 +87,49 @@ func (image *Image) VerifyArchitectureMatch(expectedArchID int) error { return nil } -func Inspect(ctx context.Context, target string) (*Image, error) { +func CopyOverrideArch(source, destination string, archID int, authfile string) error { + + destinationWithTransport := "containers-storage:" + destination + sourceWithTransport := "docker://" + source + args := []string{"copy", "--override-arch", architecture.GetArchNameOCI(archID)} + + if authfile != "" { + args = append(args, []string{"--src-authfile", authfile}...) + } + + args = append(args, sourceWithTransport, destinationWithTransport) + + if logrus.GetLevel() < logrus.DebugLevel { + if err := shell.Run("skopeo", nil, nil, nil, args...); err != nil { + return err + } + } else { + if err := shell.Run("skopeo", nil, os.Stderr, nil, args...); err != nil { + return err + } + } + + return nil +} + +func Inspect(ctx context.Context, target string, archID int, authfile string) (*Image, error) { var stdout bytes.Buffer targetWithTransport := "docker://" + target - args := []string{"inspect", "--format", "json", targetWithTransport} + args := []string{"inspect", "--format", "json"} + + if !architecture.HasContainerNativeArch(archID) { + archName := architecture.GetArchNameOCI(archID) + args = append(args, []string{"--override-arch", archName}...) + } - if err := shell.RunContext(ctx, "skopeo", nil, &stdout, nil, args...); err != nil { + if authfile != "" { + args = append(args, []string{"--authfile", authfile}...) + } + + args = append(args, targetWithTransport) + + if _, err := shell.RunContextWithExitCode2(ctx, "skopeo", nil, &stdout, nil, args...); err != nil { return nil, err } @@ -102,5 +139,7 @@ func Inspect(ctx context.Context, target string) (*Image, error) { return nil, err } + image.NameFull = target + return &image, nil } From fb20f4dee5587874de32b2ce56f649286291e4dc Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 23 Apr 2026 21:41:06 +0200 Subject: [PATCH 09/21] cmd/utils: Add architecture resolution from --arch flag, image tags, and names Add resolveArchitectureID(), which combines the --arch command-line flag with architecture detection from image tag suffixes (e.g., "fedora-toolbox:42-aarch64"). This detection applies only to images from distributions that Toolbx explicitly supports (see [1]), to avoid a false architecture approach on custom images where a dash-separated component might not represent an architecture, since there is no standard set regarding preserving architecture in the tag (see detailed explanation at [2]). When both sources specify an architecture, it validates that they do not conflict. Add resolveImageNameWithArchitectureSuffix(), which appends the OCI architecture name to supported distro image references when the target architecture differs from the host, to ensure the local Toolbx images naming convention [2]. Again, this applies only to supported distros. [1] https://containertoolbx.org/distros/ [2] https://github.com/containers/podman/discussions/27780#discussioncomment-16662213 https://github.com/containers/toolbox/pull/1786 Signed-off-by: Dalibor Kricka --- src/cmd/utils.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/cmd/utils.go b/src/cmd/utils.go index 191da4ef6..ac50a516a 100644 --- a/src/cmd/utils.go +++ b/src/cmd/utils.go @@ -31,6 +31,7 @@ import ( "strings" "syscall" + "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/shell" "github.com/containers/toolbox/pkg/utils" "github.com/sirupsen/logrus" @@ -261,6 +262,18 @@ func discardInputAsync(ctx context.Context) (<-chan int, <-chan error) { return retValCh, errCh } +func createErrorConflictingArchSpecs(archCLI, archTag int) error { + var builder strings.Builder + fmt.Fprintf(&builder, "conflicting architecture specifications\n") + fmt.Fprintf(&builder, "--arch=%s but image tag specifies %s\n", + architecture.GetArchNameOCI(archCLI), + architecture.GetArchNameOCI(archTag)) + fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) + + errMsg := builder.String() + return errors.New(errMsg) +} + func createErrorContainerNotFound(container string) error { var builder strings.Builder fmt.Fprintf(&builder, "container %s not found\n", container) @@ -483,6 +496,35 @@ func poll(pollFn pollFunc, eventFD int32, fds ...int32) error { } } +func resolveArchitectureID(arch string, image string) (int, error) { + archID := architecture.NotSpecified + if arch != "" { + archIDParsed, err := architecture.ParseArgArchValue(arch) + if err != nil { + return architecture.NotSpecified, err + } + archID = archIDParsed + } + + if image != "" && utils.IsSupportedDistroImage(image) { + archIDFromTag := architecture.ImageReferenceGetArchFromTag(image) + + if archID == architecture.NotSpecified && archIDFromTag != architecture.NotSpecified { + logrus.Debug("non-native architecture was detected in the image tag -> cross-architecture approach is going to be used") + + archID = archIDFromTag + } else if archID != archIDFromTag && archIDFromTag != architecture.NotSpecified { + return architecture.NotSpecified, createErrorConflictingArchSpecs(archID, archIDFromTag) + } + } + + if archID == architecture.NotSpecified { + archID = architecture.HostArchID + } + + return archID, nil +} + func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, releaseCLI string) ( string, string, string, error, ) { @@ -543,6 +585,21 @@ func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, return container, image, release, nil } +func resolveImageNameWithArchitectureSuffix(image string, archID int) string { + if architecture.HasContainerNativeArch(archID) { + return image + } + + archIDFromTag := architecture.ImageReferenceGetArchFromTag(image) + isSupportedDistroImage := utils.IsSupportedDistroImage(image) + + if isSupportedDistroImage && archIDFromTag == architecture.NotSpecified { + return image + "-" + architecture.GetArchNameOCI(archID) + } + + return image +} + // showManual tries to open the specified manual page using man on stdout func showManual(manual string) error { manBinary, err := exec.LookPath("man") From 78dace15faf3756d26017867d09e48e4d636207d Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Fri, 24 Apr 2026 07:38:55 +0200 Subject: [PATCH 10/21] cmd/utils: Update resolveContainerAndImageNames for cross-arch support Change resolveContainerAndImageNames() to accept an archID parameter. When the target architecture is non-native, and the container name was auto-generated (was not set by a user), append the architecture suffix to the container name (e.g., "fedora-toolbox-arm64") to distinguish it from native containers. Temporarily update the callers of resolveContainerAndImageNames() to pass in architecture.HostArchID to the updated signature, to maintain a default native behavior. Once implemented, the --arch argument in the callers will pass the actual architecture information. https://github.com/containers/toolbox/pull/1786 Signed-off-by: Dalibor Kricka --- src/cmd/create.go | 3 ++- src/cmd/enter.go | 4 +++- src/cmd/rootMigrationPath.go | 3 ++- src/cmd/run.go | 4 +++- src/cmd/utils.go | 15 ++++++++++++++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/cmd/create.go b/src/cmd/create.go index 09b444a61..d662d05ff 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -175,7 +175,8 @@ func create(cmd *cobra.Command, args []string) error { containerArg, createFlags.distro, createFlags.image, - createFlags.release) + createFlags.release, + architecture.HostArchID) if err != nil { return err diff --git a/src/cmd/enter.go b/src/cmd/enter.go index 9ba84d6ad..570803161 100644 --- a/src/cmd/enter.go +++ b/src/cmd/enter.go @@ -21,6 +21,7 @@ import ( "fmt" "os" + "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/utils" "github.com/spf13/cobra" ) @@ -108,7 +109,8 @@ func enter(cmd *cobra.Command, args []string) error { containerArg, enterFlags.distro, "", - enterFlags.release) + enterFlags.release, + architecture.HostArchID) if err != nil { return err diff --git a/src/cmd/rootMigrationPath.go b/src/cmd/rootMigrationPath.go index 33f970145..0464e4aed 100644 --- a/src/cmd/rootMigrationPath.go +++ b/src/cmd/rootMigrationPath.go @@ -24,6 +24,7 @@ import ( "os" "strings" + "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/utils" "github.com/spf13/cobra" ) @@ -56,7 +57,7 @@ func rootRunImpl(cmd *cobra.Command, args []string) error { return &exitError{exitCode, err} } - container, image, release, err := resolveContainerAndImageNames("", "", "", "", "") + container, image, release, err := resolveContainerAndImageNames("", "", "", "", "", architecture.HostArchID) if err != nil { return err } diff --git a/src/cmd/run.go b/src/cmd/run.go index ed421aa68..035288121 100644 --- a/src/cmd/run.go +++ b/src/cmd/run.go @@ -30,6 +30,7 @@ import ( "syscall" "time" + "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/nvidia" "github.com/containers/toolbox/pkg/podman" "github.com/containers/toolbox/pkg/shell" @@ -145,7 +146,8 @@ func run(cmd *cobra.Command, args []string) error { "--container", runFlags.distro, "", - runFlags.release) + runFlags.release, + architecture.HostArchID) if err != nil { return err diff --git a/src/cmd/utils.go b/src/cmd/utils.go index ac50a516a..df4f06a34 100644 --- a/src/cmd/utils.go +++ b/src/cmd/utils.go @@ -525,9 +525,11 @@ func resolveArchitectureID(arch string, image string) (int, error) { return archID, nil } -func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, releaseCLI string) ( +func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, releaseCLI string, archID int) ( string, string, string, error, ) { + containerWasEmpty := container == "" + container, image, release, err := utils.ResolveContainerAndImageNames(container, distroCLI, imageCLI, @@ -582,6 +584,17 @@ func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, } } + if containerWasEmpty && !architecture.HasContainerNativeArch(archID) { + archIDFromTag := architecture.ImageReferenceGetArchFromTag(image) + + if archIDFromTag == architecture.NotSpecified { + archName := architecture.GetArchNameOCI(archID) + if archName != "" { + container = container + "-" + archName + } + } + } + return container, image, release, nil } From a79eb9ddc4a7f12e7aef549b98fd693ff27cda95 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Fri, 24 Apr 2026 09:58:20 +0200 Subject: [PATCH 11/21] cmd/create: Integrate cross-architecture support into container creation Add the --arch flag to the 'create' command, allowing users to create Toolbx containers for architectures different from the host (e.g., 'toolbox create --arch arm64'). Utilize the architecture resolution pipeline in create() by using resolveArchitectureID() (added in [1]) to determine the target architecture from the --arch flag and image tags. Validate host support via IsArchSupportedOnCreation() (added in [2]), which checks for the required QEMU emulator and binfmt_misc registration. Pass architecture ID to resolveContainerAndImageNames() (updated in [1]) so that non-native containers get architecture-suffixed names. Update pullImage() to handle cross-architecture image pulling: when the target architecture is non-native, use skopeo.CopyOverrideArch() (added in [3]) instead of podman.Pull(), since Podman does not support pulling foreign architecture images into locally addressable names. The need for this is explained in a discussion in [4]. Add a 'toolbox-arch' label to created containers to record the target architecture in OCI format. Extract the image pull error formatting into createErrorImagePull() in utils.go to avoid duplication between the native and cross-arch pull paths. Update the createContainer() call in run.go to pass the default architecture config via GetArchConfigDefault(), maintaining the existing native-architecture behavior. [1] https://github.com/containers/toolbox/pull/1786 [2] https://github.com/containers/toolbox/pull/1783 [3] https://github.com/containers/toolbox/pull/1784 [4] https://github.com/containers/podman/discussions/27780 https://github.com/containers/toolbox/pull/1787 Signed-off-by: Dalibor Kricka --- src/cmd/create.go | 84 +++++++++++++++++++++++++++++++++++------------ src/cmd/run.go | 2 +- src/cmd/utils.go | 9 +++++ 3 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/cmd/create.go b/src/cmd/create.go index d662d05ff..7e29f3d5d 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -50,6 +50,7 @@ const ( var ( createFlags struct { + arch string authFile string container string distro string @@ -76,6 +77,12 @@ var createCmd = &cobra.Command{ func init() { flags := createCmd.Flags() + flags.StringVarP(&createFlags.arch, + "arch", + "a", + "", + "Create a Toolbx container for a different architecture than the host") + flags.StringVar(&createFlags.authFile, "authfile", "", @@ -171,25 +178,43 @@ func create(cmd *cobra.Command, args []string) error { containerArg = "--container" } + var archConfig architecture.Config + + archID, err := resolveArchitectureID(createFlags.arch, createFlags.image) + if err != nil { + return err + } + archConfig.ID = archID + + if !architecture.HasContainerNativeArch(archConfig.ID) { + archName := architecture.GetArchNameOCI(archConfig.ID) + qemuEmulatorPath, err := architecture.IsArchSupportedOnCreation(archID) + if err != nil { + errNotSupported := fmt.Errorf("Cannot create container for architecture %s\n%s", archName, err) + return errNotSupported + } + archConfig.QemuEmulatorPath = qemuEmulatorPath + } + container, image, release, err := resolveContainerAndImageNames(container, containerArg, createFlags.distro, createFlags.image, createFlags.release, - architecture.HostArchID) + archConfig.ID) if err != nil { return err } - if err := createContainer(container, image, release, createFlags.authFile, true); err != nil { + if err := createContainer(container, image, release, createFlags.authFile, archConfig, true); err != nil { return err } return nil } -func createContainer(container, image, release, authFile string, showCommandToEnter bool) error { +func createContainer(container, image, release, authFile string, archConfig architecture.Config, showCommandToEnter bool) error { if container == "" { panic("container not specified") } @@ -216,14 +241,19 @@ func createContainer(container, image, release, authFile string, showCommandToEn return errors.New(errMsg) } - pulled, err := pullImage(image, release, authFile) + pulled, couldBeNonnativeArch, err := pullImage(image, release, authFile, archConfig.ID) if err != nil { return err } + if !pulled { return nil } + if couldBeNonnativeArch { + image = resolveImageNameWithArchitectureSuffix(image, archConfig.ID) + } + imageFull, err := podman.GetFullyQualifiedImageFromRepoTags(image) if err != nil { var errImage *podman.ImageError @@ -448,6 +478,10 @@ func createContainer(container, image, release, authFile string, showCommandToEn "--label", "com.github.containers.toolbox=true", }...) + createArgs = append(createArgs, []string{ + "--label", "toolbox-arch=" + architecture.GetArchNameOCI(archConfig.ID), + }...) + createArgs = append(createArgs, devPtsMount...) createArgs = append(createArgs, []string{ @@ -663,11 +697,13 @@ func getServiceSocket(serviceName string, unitName string) (string, error) { return "", fmt.Errorf("failed to find a SOCK_STREAM socket for %s", unitName) } -func pullImage(image, release, authFile string) (bool, error) { +func pullImage(image, release, authFile string, archID int) (bool, bool, error) { + isNonNativeArch := !architecture.HasContainerNativeArch(archID) + if ok := utils.ImageReferenceCanBeID(image); ok { logrus.Debugf("Looking up image %s", image) if _, err := podman.ImageExists(image); err == nil { - return true, nil + return true, false, nil } } @@ -678,7 +714,7 @@ func pullImage(image, release, authFile string) (bool, error) { logrus.Debugf("Looking up image %s", imageLocal) if _, err := podman.ImageExists(imageLocal); err == nil { - return true, nil + return true, false, nil } } @@ -690,13 +726,15 @@ func pullImage(image, release, authFile string) (bool, error) { var err error imageFull, err = utils.GetFullyQualifiedImageFromDistros(image, release) if err != nil { - return false, fmt.Errorf("image %s not found in local storage and known registries", image) + return false, false, fmt.Errorf("image %s not found in local storage and known registries", image) } } - logrus.Debugf("Looking up image %s", imageFull) - if _, err := podman.ImageExists(imageFull); err == nil { - return true, nil + imageFullWithArch := resolveImageNameWithArchitectureSuffix(imageFull, archID) + + logrus.Debugf("Looking up image %s", imageFullWithArch) + if _, err := podman.ImageExists(imageFullWithArch); err == nil { + return true, isNonNativeArch, nil } domain := utils.ImageReferenceGetDomain(imageFull) @@ -721,14 +759,14 @@ func pullImage(image, release, authFile string) (bool, error) { fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase) errMsg := builder.String() - return false, errors.New(errMsg) + return false, false, errors.New(errMsg) } shouldPullImage = showPromptForDownload(imageFull) } if !shouldPullImage { - return false, nil + return false, false, nil } logrus.Debugf("Pulling image %s", imageFull) @@ -736,17 +774,21 @@ func pullImage(image, release, authFile string) (bool, error) { s := startSpinner(fmt.Sprintf("Pulling %s: ", imageFull)) defer stopSpinner(s) - if err := podman.Pull(imageFull, authFile); err != nil { - var builder strings.Builder - fmt.Fprintf(&builder, "failed to pull image %s\n", imageFull) - fmt.Fprintf(&builder, "If it was a private image, log in with: podman login %s\n", domain) - fmt.Fprintf(&builder, "Use '%s --verbose ...' for further details.", executableBase) + if !isNonNativeArch { + logrus.Debugf("'podman pull' is used for pulling image %s", imageFull) - errMsg := builder.String() - return false, errors.New(errMsg) + if err := podman.Pull(imageFull, authFile); err != nil { + return false, false, createErrorImagePull(imageFull, domain) + } + } else { + logrus.Debugf("'skopeo copy' is used for pulling non-native architecture image %s", imageFull) + + if err := skopeo.CopyOverrideArch(imageFull, imageFullWithArch, archID, authFile); err != nil { + return false, false, createErrorImagePull(imageFull, domain) + } } - return true, nil + return true, isNonNativeArch, nil } func createPromptForDownload(imageFull, imageSize string) string { diff --git a/src/cmd/run.go b/src/cmd/run.go index 035288121..4f915c63c 100644 --- a/src/cmd/run.go +++ b/src/cmd/run.go @@ -227,7 +227,7 @@ func runCommand(container string, return nil } - if err := createContainer(container, image, release, "", false); err != nil { + if err := createContainer(container, image, release, "", architecture.GetArchConfigDefault(), false); err != nil { return err } } else if containersCount == 1 && defaultContainer { diff --git a/src/cmd/utils.go b/src/cmd/utils.go index df4f06a34..9100a8378 100644 --- a/src/cmd/utils.go +++ b/src/cmd/utils.go @@ -294,6 +294,15 @@ func createErrorDistroWithoutRelease(distro string) error { return errors.New(errMsg) } +func createErrorImagePull(image, domain string) error { + var builder strings.Builder + fmt.Fprintf(&builder, "failed to pull image %s\n", image) + fmt.Fprintf(&builder, "If it was a private image, log in with: podman login %s\n", domain) + fmt.Fprintf(&builder, "Use '%s --verbose ...' for further details.", executableBase) + + return errors.New(builder.String()) +} + func createErrorInvalidContainer(containerArg string) error { var builder strings.Builder fmt.Fprintf(&builder, "invalid argument for '%s'\n", containerArg) From 6fc831d027b8c3bc9ec1d0a8ec90e4c7f5e20e0c Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Fri, 24 Apr 2026 10:50:34 +0200 Subject: [PATCH 12/21] cmd/create: Rework download prompt flow for cross-arch verification Rework the image download prompt flow to support architecture verification before pulling non-native images. The new implementation ensures that the image inspection completes for the non-native creation path before it is pulled, so the image's architecture can be verified. The previous implementation used promptForDownloadError as a control flow mechanism between the first and second download prompts. Replace this with the pullImageDecision enum (pullNo, pullYes, pullUnknown) for clearer three-state signaling. Replaced getImageSizeFromRegistryAsync() with getImageFromRegistryAsync(), which now returns the full skopeo.Image struct instead of just the image size string. It calls skopeo.Inspect() (updated in [1]), making image metadata available throughout the download prompt flow for both size display and architecture verification in a single inspect call. Use Image.GetSizeHuman() (added in [1]) for image size display in the second download prompt, replacing the local size computation. Update showPromptForDownloadFirst() to return (pullImageDecision, *skopeo.Image, error). For non-native architectures, when the user confirms the download, the function now waits for the skopeo inspect to complete (with a spinner) before returning, ensuring that architecture verification can happen before the pull begins. Update pullImage() to verify the image architecture before pulling non-native images by calling VerifyArchitectureMatch() (added in [1]) to catch incompatible single-architecture images. Handle the case where the inspect returns nil (multi-arch manifest has no matching variant) with an explicit error. Detect a missing skopeo binary via exec.ErrNotFound, which is only a soft dependency of the Toolbx package, as it is not required for running non-native containers, and report it through createErrorSkopeoNotFound() added in utils.go. [1] https://github.com/containers/toolbox/pull/1784 https://github.com/containers/toolbox/pull/1787 Signed-off-by: Dalibor Kricka --- src/cmd/create.go | 220 ++++++++++++++++++++++++++-------------------- src/cmd/utils.go | 8 ++ 2 files changed, 135 insertions(+), 93 deletions(-) diff --git a/src/cmd/create.go b/src/cmd/create.go index 7e29f3d5d..e1a8910cb 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "os" + "os/exec" "path/filepath" "strings" "time" @@ -32,22 +33,25 @@ import ( "github.com/containers/toolbox/pkg/skopeo" "github.com/containers/toolbox/pkg/term" "github.com/containers/toolbox/pkg/utils" - "github.com/docker/go-units" "github.com/godbus/dbus/v5" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) -type promptForDownloadError struct { - ImageSize string -} - const ( alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` num = `0123456789` alphanum = alpha + num ) +type pullImageDecision int + +const ( + pullNo pullImageDecision = iota // 0 - User declined or default + pullYes // 1 - User confirmed + pullUnknown // 2 - Need second prompt +) + var ( createFlags struct { arch string @@ -540,6 +544,13 @@ func createContainer(container, image, release, authFile string, archConfig arch return nil } +func boolToPullDecision(shouldPull bool) pullImageDecision { + if shouldPull { + return pullYes + } + return pullNo +} + func createHelp(cmd *cobra.Command, args []string) { if utils.IsInsideContainer() { if !utils.IsInsideToolboxContainer() { @@ -599,42 +610,19 @@ func getEnterCommand(container string) string { return enterCommand } -func getImageSizeFromRegistry(ctx context.Context, imageFull string) (string, error) { - image, err := skopeo.Inspect(ctx, imageFull, architecture.HostArchID, "") - if err != nil { - return "", err - } - - if image.LayersData == nil { - return "", errors.New("'skopeo inspect' did not have LayersData") - } - - var imageSizeFloat float64 - - for _, layer := range image.LayersData { - if layerSize, err := layer.Size.Float64(); err != nil { - return "", err - } else { - imageSizeFloat += layerSize - } - } - - imageSizeHuman := units.HumanSize(imageSizeFloat) - return imageSizeHuman, nil -} - -func getImageSizeFromRegistryAsync(ctx context.Context, imageFull string) (<-chan string, <-chan error) { - retValCh := make(chan string) +func getImageFromRegistryAsync(ctx context.Context, imageFull string, archID int, authFile string) (<-chan *skopeo.Image, <-chan error) { + retValCh := make(chan *skopeo.Image) errCh := make(chan error) go func() { - imageSize, err := getImageSizeFromRegistry(ctx, imageFull) + image, err := skopeo.Inspect(ctx, imageFull, archID, authFile) + if err != nil { errCh <- err return } - retValCh <- imageSize + retValCh <- image }() return retValCh, errCh @@ -745,10 +733,18 @@ func pullImage(image, release, authFile string, archID int) (bool, bool, error) promptForDownload := true var shouldPullImage bool + var imageInfo *skopeo.Image + var imageInspectErr error if rootFlags.assumeYes || domain == "localhost" { promptForDownload = false shouldPullImage = true + + if isNonNativeArch { + s := startSpinner("Fetching non-native architecture image info: ") + imageInfo, imageInspectErr = skopeo.Inspect(context.Background(), imageFull, archID, authFile) + stopSpinner(s) + } } if promptForDownload { @@ -762,13 +758,22 @@ func pullImage(image, release, authFile string, archID int) (bool, bool, error) return false, false, errors.New(errMsg) } - shouldPullImage = showPromptForDownload(imageFull) + shouldPullImage, imageInfo, imageInspectErr = showPromptForDownload(imageFull, archID, authFile) } if !shouldPullImage { return false, false, nil } + if imageInspectErr != nil && isNonNativeArch { + if errors.Is(imageInspectErr, exec.ErrNotFound) { + return false, false, createErrorSkopeoNotFound(imageFull, archID) + } + + // For now, log and continue (imageInfo will be nil) + logrus.Debugf("Failed to inspect image: %s", imageInspectErr) + } + logrus.Debugf("Pulling image %s", imageFull) s := startSpinner(fmt.Sprintf("Pulling %s: ", imageFull)) @@ -783,6 +788,17 @@ func pullImage(image, release, authFile string, archID int) (bool, bool, error) } else { logrus.Debugf("'skopeo copy' is used for pulling non-native architecture image %s", imageFull) + if imageInfo == nil { + // Multi-arch image mismatch + expectedArchName := architecture.GetArchNameOCI(archID) + return false, false, fmt.Errorf("failed to verify: image %s does not support architecture %s or the image does not exists at all", + imageFull, expectedArchName) + } + + if err := imageInfo.VerifyArchitectureMatch(archID); err != nil { + return false, false, err + } + if err := skopeo.CopyOverrideArch(imageFull, imageFullWithArch, archID, authFile); err != nil { return false, false, createErrorImagePull(imageFull, domain) } @@ -802,8 +818,9 @@ func createPromptForDownload(imageFull, imageSize string) string { return prompt } -func showPromptForDownloadFirst(imageFull string) (bool, error) { +func showPromptForDownloadFirst(imageFull string, archID int, authFile string) (pullImageDecision, *skopeo.Image, error) { prompt := createPromptForDownload(imageFull, " ... MB") + isNonnativeArch := !architecture.HasContainerNativeArch(archID) parentCtx := context.Background() askCtx, askCancel := context.WithCancelCause(parentCtx) @@ -811,48 +828,69 @@ func showPromptForDownloadFirst(imageFull string) (bool, error) { askCh, askErrCh := askForConfirmationAsync(askCtx, prompt, nil) - imageSizeCtx, imageSizeCancel := context.WithCancelCause(parentCtx) - defer imageSizeCancel(errors.New("clean-up")) + imageCtx, imageCancel := context.WithCancelCause(parentCtx) + defer imageCancel(errors.New("clean-up")) - imageSizeCh, imageSizeErrCh := getImageSizeFromRegistryAsync(imageSizeCtx, imageFull) + imageCh, imageErrCh := getImageFromRegistryAsync(imageCtx, imageFull, archID, authFile) - var imageSize string - var shouldPullImage bool + var image *skopeo.Image = nil + var shouldPullImage pullImageDecision = pullNo + var imageInspectErr error = nil select { case val := <-askCh: - shouldPullImage = val - cause := fmt.Errorf("%w: received confirmation without image size", context.Canceled) - imageSizeCancel(cause) + shouldPullImage = boolToPullDecision(val) + + if isNonnativeArch { + if shouldPullImage == pullNo { + return pullNo, nil, nil + } + + s := startSpinner("Fetching non-native architecture image info: ") + + select { + case img := <-imageCh: + stopSpinner(s) + image = img + return shouldPullImage, image, nil + case err := <-imageErrCh: + stopSpinner(s) + return shouldPullImage, nil, err + } + } else { + cause := fmt.Errorf("%w: received confirmation without image info", context.Canceled) + imageCancel(cause) + } case err := <-askErrCh: - shouldPullImage = false - cause := fmt.Errorf("failed to ask for confirmation without image size: %w", err) - imageSizeCancel(cause) - case val := <-imageSizeCh: - imageSize = val - cause := fmt.Errorf("%w: received image size", context.Canceled) + shouldPullImage = pullNo + cause := fmt.Errorf("failed to ask for confirmation without image info: %w", err) + imageCancel(cause) + case val := <-imageCh: + image = val + cause := fmt.Errorf("%w: received image info", context.Canceled) askCancel(cause) - case err := <-imageSizeErrCh: - cause := fmt.Errorf("failed to get image size: %w", err) + case err := <-imageErrCh: + imageInspectErr = err + cause := fmt.Errorf("failed to get image info: %w", err) askCancel(cause) } - if imageSizeCtx.Err() != nil && askCtx.Err() == nil { - cause := context.Cause(imageSizeCtx) - logrus.Debugf("Show prompt for download: image size canceled: %s", cause) - return shouldPullImage, nil + if imageCtx.Err() != nil && askCtx.Err() == nil { + cause := context.Cause(imageCtx) + logrus.Debugf("Show prompt for download: image info canceled: %s", cause) + return shouldPullImage, nil, nil } var done bool - if imageSizeCtx.Err() == nil && askCtx.Err() != nil { + if imageCtx.Err() == nil && askCtx.Err() != nil { select { case val := <-askCh: - logrus.Debugf("Show prompt for download: received pending confirmation without image size") - shouldPullImage = val + logrus.Debugf("Show prompt for download: received pending confirmation without image info") + shouldPullImage = boolToPullDecision(val) done = true case err := <-askErrCh: - logrus.Debugf("Show prompt for download: failed to ask for confirmation without image size: %s", + logrus.Debugf("Show prompt for download: failed to ask for confirmation without image info: %s", err) } } else { @@ -863,13 +901,13 @@ func showPromptForDownloadFirst(imageFull string) (bool, error) { logrus.Debugf("Show prompt for download: ask canceled: %s", cause) if done { - return shouldPullImage, nil + return shouldPullImage, image, imageInspectErr + } else { + return pullUnknown, image, imageInspectErr } - - return false, &promptForDownloadError{imageSize} } -func showPromptForDownloadSecond(imageFull string, errFirst *promptForDownloadError) bool { +func showPromptForDownloadSecond(imageFull, imageSize string) bool { oldState, err := term.GetState(os.Stdin) if err != nil { logrus.Debugf("Show prompt for download: failed to get terminal state: %s", err) @@ -895,12 +933,7 @@ func showPromptForDownloadSecond(imageFull string, errFirst *promptForDownloadEr discardCh, discardErrCh := discardInputAsync(discardCtx) - var prompt string - if errors.Is(errFirst, context.Canceled) { - prompt = createPromptForDownload(imageFull, errFirst.ImageSize) - } else { - prompt = createPromptForDownload(imageFull, "") - } + prompt := createPromptForDownload(imageFull, imageSize) fmt.Printf("\r") @@ -981,22 +1014,37 @@ func showPromptForDownloadSecond(imageFull string, errFirst *promptForDownloadEr return shouldPullImage } -func showPromptForDownload(imageFull string) bool { +func showPromptForDownload(imageFull string, archID int, authFile string) (bool, *skopeo.Image, error) { fmt.Println("Image required to create Toolbx container.") - shouldPullImage, err := showPromptForDownloadFirst(imageFull) - if err == nil { - return shouldPullImage + var shouldPullImageFirst pullImageDecision + var image *skopeo.Image + var imageInspectErr error + + shouldPullImageFirst, image, imageInspectErr = showPromptForDownloadFirst(imageFull, archID, authFile) + + switch shouldPullImageFirst { + case pullYes: + return true, image, imageInspectErr + case pullNo: + return false, image, imageInspectErr } - var errPromptForDownload *promptForDownloadError - if !errors.As(err, &errPromptForDownload) { - panicMsg := fmt.Sprintf("unexpected %T: %s", err, err) - panic(panicMsg) + var imageSize string + var shouldPullImageSecond bool + var getSizeErr error + + if image == nil { + imageSize = "n/a" + } else { + imageSize, getSizeErr = image.GetSizeHuman() + if getSizeErr != nil { + imageSize = "n/a" + } } - shouldPullImage = showPromptForDownloadSecond(imageFull, errPromptForDownload) - return shouldPullImage + shouldPullImageSecond = showPromptForDownloadSecond(imageFull, imageSize) + return shouldPullImageSecond, image, imageInspectErr } func startSpinner(message string) *spinner.Spinner { @@ -1042,17 +1090,3 @@ func systemdPathBusEscape(path string) string { } return string(n) } - -func (err *promptForDownloadError) Error() string { - innerErr := err.Unwrap() - errMsg := innerErr.Error() - return errMsg -} - -func (err *promptForDownloadError) Unwrap() error { - if err.ImageSize == "" { - return errors.New("failed to get image size") - } - - return context.Canceled -} diff --git a/src/cmd/utils.go b/src/cmd/utils.go index 9100a8378..c3762b48a 100644 --- a/src/cmd/utils.go +++ b/src/cmd/utils.go @@ -367,6 +367,14 @@ func createErrorProfileDNotFound() error { return errors.New(errMsg) } +func createErrorSkopeoNotFound(imageFull string, archID int) error { + archName := architecture.GetArchNameOCI(archID) + return fmt.Errorf( + "Cannot inspect image %s for architecture %s: skopeo is not installed.\n"+ + "Skopeo is required for creating non-native architecture containers.", + imageFull, archName) +} + func createErrorSudoersDNotFound() error { const sudoersD = "/etc/sudoers.d" From f8afb273a8799abbd3923a16d79bee2811a04d97 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Fri, 24 Apr 2026 12:16:18 +0200 Subject: [PATCH 13/21] cmd/initContainer: Set up QEMU emulation for cross-arch containers Add the --arch and --arch-emulator-path flags to the init-container command, passed from the create command when creating a cross-architecture container. The --arch flag defaults to the host architecture ID so that existing native containers continue to work without changes. When the container's architecture differs from the host, the init-container entry point configures QEMU emulation inside the container before any foreign-architecture binaries can run: 1. Validate QEMU emulation by running the 'true' command, which fails with ENOEXEC if the host's binfmt_misc registration is not working (detected via RunWithExitCode2() added in [1]), because it is necessary to have host emulation working to emulate the binfmt_misc registration in the following step. 2. Mount a fresh binfmt_misc filesystem inside the container via MountBinfmtMisc() (added in [2]) to create a sandboxed binfmt_misc registration with the C flag. 3. Validate architecture support via IsArchSupportedOnInitialization() (added in [3]), which verifies the QEMU interpreter at the host-mounted path under /run/host. 4. Register the QEMU interpreter with the C flag via RegisterBinfmtMisc() (added and explained in [2]) The binfmt_misc registration is performed inside the container rather than relying on the host's registration, as explained in [2]. Update showEntryPointLog() in run.go to propagate lines prefixed with 'Warning:' to stderr on the host, instead of treating them as errors. This is needed because the cross-architecture initialization may emit warnings that should be visible to the user but are not fatal. [1] https://github.com/containers/toolbox/pull/1780 [2] https://github.com/containers/toolbox/pull/1782 [3] https://github.com/containers/toolbox/pull/1783 https://github.com/containers/toolbox/pull/1788 Signed-off-by: Dalibor Kricka --- src/cmd/create.go | 2 ++ src/cmd/initContainer.go | 60 ++++++++++++++++++++++++++++++++++++++++ src/cmd/run.go | 11 ++++++-- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/cmd/create.go b/src/cmd/create.go index e1a8910cb..925b8384d 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -449,6 +449,8 @@ func createContainer(container, image, release, authFile string, archConfig arch entryPoint := []string{ "toolbox", "--log-level", "debug", "init-container", + "--arch", fmt.Sprintf("%d", archConfig.ID), + "--arch-emulator-path", archConfig.QemuEmulatorPath, "--gid", currentUser.Gid, "--home", currentUserHomeDir, "--shell", userShell, diff --git a/src/cmd/initContainer.go b/src/cmd/initContainer.go index b3a7bd983..74f3b7fbf 100644 --- a/src/cmd/initContainer.go +++ b/src/cmd/initContainer.go @@ -28,6 +28,7 @@ import ( "syscall" "time" + "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/shell" "github.com/containers/toolbox/pkg/utils" "github.com/fsnotify/fsnotify" @@ -41,6 +42,8 @@ import ( var ( initContainerFlags struct { + archID int + archInterp string gid int home string homeLink bool @@ -85,6 +88,16 @@ var initContainerCmd = &cobra.Command{ func init() { flags := initContainerCmd.Flags() + flags.IntVar(&initContainerFlags.archID, + "arch", + architecture.HostArchID, + "Specify the Toolbx container's architecture ID.") + + flags.StringVar(&initContainerFlags.archInterp, + "arch-emulator-path", + "", + "Register an emulator using binfmt_misc with PATH as the interpreter for a non-native architecture container.") + flags.IntVar(&initContainerFlags.gid, "gid", 0, @@ -257,6 +270,31 @@ func initContainer(cmd *cobra.Command, args []string) error { } } + if !architecture.HasContainerNativeArch(initContainerFlags.archID) { + archName := architecture.GetArchNameOCI(initContainerFlags.archID) + interpreterPath := "/run/host" + initContainerFlags.archInterp + + if err := validateCrossArchEmulation(initContainerFlags.archID); err != nil { + return err + } + + logrus.Debugf("Mounting binfmt_misc file system in container for architecture %s", archName) + if err := architecture.MountBinfmtMisc(); err != nil { + return err + } + + resolvedInterpreterPath, err := architecture.IsArchSupportedOnInitialization(initContainerFlags.archID, interpreterPath) + if err != nil { + errNotSupported := fmt.Errorf("Cannot run container for architecture %s:\n%s", archName, err) + return errNotSupported + } + + logrus.Debugf("Registering QEMU emulator for architecture %s in binfmt_misc", archName) + if err := architecture.RegisterBinfmtMisc(initContainerFlags.archID, resolvedInterpreterPath); err != nil { + return err + } + } + for _, mount := range initContainerMounts { if err := mountBind(mount.containerPath, mount.source, mount.flags); err != nil { return err @@ -1223,6 +1261,28 @@ func updateTimeZoneFromLocalTime() error { return nil } +func validateCrossArchEmulation(archID int) error { + archName := architecture.GetArchNameOCI(archID) + logrus.Debugf("Testing QEMU emulation for architecture %s", archName) + + _, err := shell.RunWithExitCode2("true", nil, nil, nil) + + if err != nil { + if errors.Is(err, syscall.ENOEXEC) { + return fmt.Errorf( + "QEMU emulation for architecture %s is not working\n"+ + "Please verify that:\n"+ + " 1. QEMU user-mode emulation is installed on the host system: qemu-user-static package\n"+ + " 2. binfmt_misc is properly configured on the host system", + archName) + } + return fmt.Errorf("failed to test QEMU emulation for architecture %s: %w", archName, err) + } + + logrus.Debugf("Test of QEMU emulation for architecture %s has succeeded", archName) + return nil +} + func writeTimeZone(timeZone string) error { const etcTimeZone = "/etc/timezone" diff --git a/src/cmd/run.go b/src/cmd/run.go index 4f915c63c..2e9d63dc2 100644 --- a/src/cmd/run.go +++ b/src/cmd/run.go @@ -964,8 +964,15 @@ func showEntryPointLog(line string) error { } if !logLevelFound { - errMsg, _ := strings.CutPrefix(line, "Error: ") - return &entryPointError{errMsg} + // Messages sent to stderr with a 'Warning:' prefix in the entry point + // are propagated to stderr on the host + if strings.HasPrefix(line, "Warning:") { + fmt.Fprintf(os.Stderr, "%s\n", line) + return nil + } else { + errMsg, _ := strings.CutPrefix(line, "Error: ") + return &entryPointError{errMsg} + } } logger := logrus.StandardLogger() From 90e5e6681fa93bd4354adea2bbd0b756996e69bc Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Fri, 24 Apr 2026 22:04:39 +0200 Subject: [PATCH 14/21] cmd/enter: Add --arch flag for container architecture selection Add the --arch / -a flag to the enter command, allowing users to enter cross-architecture containers by specifying the target architecture (e.g., toolbox enter --arch arm64). Can be used with flags --distro and --release, just as for container creation. The flag value is resolved through resolveArchitectureID() (added in [1]) and passed to resolveContainerAndImageNames() (updated for cross-arch in [1]) so that it resolves to the architecture-suffixed container name. [1] https://github.com/containers/toolbox/pull/1786 https://github.com/containers/toolbox/pull/1789 Signed-off-by: Dalibor Kricka --- src/cmd/enter.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/cmd/enter.go b/src/cmd/enter.go index 570803161..851868769 100644 --- a/src/cmd/enter.go +++ b/src/cmd/enter.go @@ -21,13 +21,13 @@ import ( "fmt" "os" - "github.com/containers/toolbox/pkg/architecture" "github.com/containers/toolbox/pkg/utils" "github.com/spf13/cobra" ) var ( enterFlags struct { + arch string container string distro string release string @@ -44,6 +44,12 @@ var enterCmd = &cobra.Command{ func init() { flags := enterCmd.Flags() + flags.StringVarP(&enterFlags.arch, + "arch", + "a", + "", + "Enter a Toolbx container for a different architecture than the host") + flags.StringVarP(&enterFlags.container, "container", "c", @@ -105,12 +111,17 @@ func enter(cmd *cobra.Command, args []string) error { defaultContainer = false } + archID, err := resolveArchitectureID(enterFlags.arch, "") + if err != nil { + return err + } + container, image, release, err := resolveContainerAndImageNames(container, containerArg, enterFlags.distro, "", enterFlags.release, - architecture.HostArchID) + archID) if err != nil { return err From 2bc3d13979424d1b029d987bba9d2de6ec780274 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Fri, 24 Apr 2026 22:19:15 +0200 Subject: [PATCH 15/21] cmd/run: Add --arch flag for container architecture selection Add the --arch / -a flag to the run command, allowing users to run commands inside cross-architecture containers by specifying the target architecture (e.g., toolbox run --arch arm64 uname -m). Can be used with flags --distro and --release, just as for container creation. The flag value is resolved through resolveArchitectureID() (added in [1]) and passed to resolveContainerAndImageNames() (updated for cross-arch in [1]) so that it resolves to the architecture-suffixed container name. [1] https://github.com/containers/toolbox/pull/1786 https://github.com/containers/toolbox/pull/1789 Signed-off-by: Dalibor Kricka --- src/cmd/run.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/cmd/run.go b/src/cmd/run.go index 2e9d63dc2..1e7f7f853 100644 --- a/src/cmd/run.go +++ b/src/cmd/run.go @@ -53,6 +53,7 @@ type entryPointError struct { var ( runFlags struct { + arch string container string distro string preserveFDs uint @@ -74,6 +75,12 @@ func init() { flags := runCmd.Flags() flags.SetInterspersed(false) + flags.StringVarP(&runFlags.arch, + "arch", + "a", + "", + "Run command inside a Toolbx container for a different architecture than the host") + flags.StringVarP(&runFlags.container, "container", "c", @@ -142,12 +149,17 @@ func run(cmd *cobra.Command, args []string) error { command := args + archID, err := resolveArchitectureID(runFlags.arch, "") + if err != nil { + return err + } + container, image, release, err := resolveContainerAndImageNames(runFlags.container, "--container", runFlags.distro, "", runFlags.release, - architecture.HostArchID) + archID) if err != nil { return err From e3955ed381841d4f9649964215929ef0911f8245 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Mon, 11 May 2026 11:09:21 +0200 Subject: [PATCH 16/21] pkg: Add unit tests for cross-architecture functionality Add unit tests for functionality introduced in PRs #1780, #1782, #1783, and #1784: - pkg/shell: Test RunWithExitCode2 and RunContextWithExitCode2 (#1780) - pkg/architecture/architecture: Test ParseArgArchValue, GetArchNameOCI, getArchNameBinfmt, getArchitecture, ImageReferenceGetArchFromTag, and isStaticallyLinkedELF (#1782, #1783) - pkg/architecture/binfmt_misc: Test bytesToEscapedString, buildRegistrationString, and getDefaultRegistration (#1782) - pkg/skopeo: Test GetSize, GetSizeHuman, VerifyArchitectureMatch, and JSON unmarshaling with real skopeo inspect output (#1784) https://github.com/containers/toolbox/pull/1794 Signed-off-by: Dalibor Kricka --- src/pkg/architecture/architecture_test.go | 389 +++++ src/pkg/architecture/binfmt_misc_test.go | 312 ++++ src/pkg/shell/shell_test.go | 252 +++ src/pkg/skopeo/skopeo_test.go | 1750 +++++++++++++++++++++ 4 files changed, 2703 insertions(+) create mode 100644 src/pkg/architecture/architecture_test.go create mode 100644 src/pkg/architecture/binfmt_misc_test.go create mode 100644 src/pkg/skopeo/skopeo_test.go diff --git a/src/pkg/architecture/architecture_test.go b/src/pkg/architecture/architecture_test.go new file mode 100644 index 000000000..3c2f6b014 --- /dev/null +++ b/src/pkg/architecture/architecture_test.go @@ -0,0 +1,389 @@ +/* + * Copyright © 2019 – 2026 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package architecture + +import ( + "bytes" + "debug/elf" + "encoding/binary" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseArgArchValue(t *testing.T) { + testCases := []struct { + name string + input string + expect int + errMsg string + }{ + { + name: "aarch64", + input: "aarch64", + expect: Aarch64, + }, + { + name: "arm64 alias for aarch64", + input: "arm64", + expect: Aarch64, + }, + { + name: "x86_64", + input: "x86_64", + expect: X86_64, + }, + { + name: "amd64 alias for x86_64", + input: "amd64", + expect: X86_64, + }, + { + name: "ppc64le", + input: "ppc64le", + expect: Ppc64le, + }, + { + name: "unsupported architecture", + input: "mips", + errMsg: "architecture 'mips' is not supported by Toolbx", + }, + { + name: "empty string", + input: "", + errMsg: "architecture '' is not supported by Toolbx", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := ParseArgArchValue(tc.input) + + if tc.errMsg != "" { + assert.Error(t, err) + assert.EqualError(t, err, tc.errMsg) + assert.Equal(t, NotSpecified, result) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expect, result) + } + }) + } +} + +func TestGetArchNameOCI(t *testing.T) { + testCases := []struct { + name string + archID int + expect string + }{ + { + name: "aarch64 returns arm64", + archID: Aarch64, + expect: "arm64", + }, + { + name: "x86_64 returns amd64", + archID: X86_64, + expect: "amd64", + }, + { + name: "ppc64le returns ppc64le", + archID: Ppc64le, + expect: "ppc64le", + }, + { + name: "NotSpecified returns fallback string", + archID: NotSpecified, + expect: "arch_not_specified", + }, + { + name: "invalid arch ID returns empty", + archID: 999, + expect: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := GetArchNameOCI(tc.archID) + assert.Equal(t, tc.expect, result) + }) + } +} + +func TestGetArchNameBinfmt(t *testing.T) { + testCases := []struct { + name string + archID int + expect string + }{ + { + name: "aarch64", + archID: Aarch64, + expect: "aarch64", + }, + { + name: "x86_64", + archID: X86_64, + expect: "x86_64", + }, + { + name: "ppc64le", + archID: Ppc64le, + expect: "ppc64le", + }, + { + name: "NotSpecified returns fallback string", + archID: NotSpecified, + expect: "arch_not_specified", + }, + { + name: "invalid arch ID returns empty", + archID: 999, + expect: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := getArchNameBinfmt(tc.archID) + assert.Equal(t, tc.expect, result) + }) + } +} + +func TestGetArchitecture(t *testing.T) { + testCases := []struct { + name string + archID int + exists bool + }{ + { + name: "aarch64 exists", + archID: Aarch64, + exists: true, + }, + { + name: "x86_64 exists", + archID: X86_64, + exists: true, + }, + { + name: "ppc64le exists", + archID: Ppc64le, + exists: true, + }, + { + name: "NotSpecified does not exist", + archID: NotSpecified, + exists: false, + }, + { + name: "invalid arch ID does not exist", + archID: 999, + exists: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + arch, exists := getArchitecture(tc.archID) + assert.Equal(t, tc.exists, exists) + + if tc.exists { + assert.Equal(t, tc.archID, arch.ID) + assert.NotEmpty(t, arch.NameBinfmt) + assert.NotEmpty(t, arch.NameOCI) + assert.NotEmpty(t, arch.Aliases) + assert.NotEmpty(t, arch.ELFMagic) + assert.NotEmpty(t, arch.ELFMask) + } + }) + } +} + +func TestImageReferenceGetArchFromTag(t *testing.T) { + testCases := []struct { + name string + image string + expect int + }{ + { + name: "image with aarch64 binfmt arch suffix", + image: "registry.fedoraproject.org/fedora-toolbox:41-aarch64", + expect: Aarch64, + }, + { + name: "image with arm64 OCI arch suffix", + image: "registry.fedoraproject.org/fedora-toolbox:41-arm64", + expect: Aarch64, + }, + { + name: "image with x86_64 binfmt arch suffix", + image: "registry.fedoraproject.org/fedora-toolbox:41-x86_64", + expect: X86_64, + }, + { + name: "image with amd64 OCI arch suffix", + image: "registry.fedoraproject.org/fedora-toolbox:41-amd64", + expect: X86_64, + }, + { + name: "image with ppc64le arch suffix", + image: "registry.fedoraproject.org/fedora-toolbox:41-ppc64le", + expect: Ppc64le, + }, + { + name: "image without arch suffix", + image: "registry.fedoraproject.org/fedora-toolbox:41", + expect: NotSpecified, + }, + { + name: "image without tag", + image: "registry.fedoraproject.org/fedora-toolbox", + expect: NotSpecified, + }, + { + name: "image with unknown arch suffix", + image: "registry.fedoraproject.org/fedora-toolbox:41-mips", + expect: NotSpecified, + }, + { + name: "empty image reference", + image: "", + expect: NotSpecified, + }, + { + name: "ubuntu image with amd64 OCI arch suffix", + image: "quay.io/toolbx-images/ubuntu-toolbox:22.04-amd64", + expect: X86_64, + }, + { + name: "ubuntu image with x86_64 binfmt arch suffix", + image: "quay.io/toolbx-images/ubuntu-toolbox:22.04-x86_64", + expect: X86_64, + }, + { + name: "ubuntu image with arm64 OCI arch suffix", + image: "quay.io/toolbx-images/ubuntu-toolbox:22.04-arm64", + expect: Aarch64, + }, + { + name: "ubuntu image with aarch64 binfmt arch suffix", + image: "quay.io/toolbx-images/ubuntu-toolbox:22.04-aarch64", + expect: Aarch64, + }, + { + name: "ubuntu image with unsupported arch suffix", + image: "quay.io/toolbx-images/ubuntu-toolbox:22.04-mips", + expect: NotSpecified, + }, + { + name: "ubuntu image without arch suffix", + image: "quay.io/toolbx-images/ubuntu-toolbox:22.04", + expect: NotSpecified, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := ImageReferenceGetArchFromTag(tc.image) + assert.Equal(t, tc.expect, result) + }) + } +} + +// buildMinimalELF64 constructs a valid ELF64 binary with the given program header types. +// A binary without PT_INTERP is statically linked; one with PT_INTERP is dynamically linked. +func buildMinimalELF64(progTypes []elf.ProgType) []byte { + var buf bytes.Buffer + + phnum := uint16(len(progTypes)) + phoff := uint64(64) + + hdr := elf.Header64{ + Ident: [16]byte{0x7f, 'E', 'L', 'F', 2, 1, 1}, + Type: uint16(elf.ET_EXEC), + Machine: uint16(elf.EM_X86_64), + Version: 1, + Phoff: phoff, + Ehsize: 64, + Phentsize: 56, + Phnum: phnum, + } + + binary.Write(&buf, binary.LittleEndian, &hdr) + + for _, pt := range progTypes { + prog := elf.Prog64{ + Type: uint32(pt), + } + binary.Write(&buf, binary.LittleEndian, &prog) + } + + return buf.Bytes() +} + +func TestIsStaticallyLinkedELF(t *testing.T) { + testCases := []struct { + name string + content []byte + expect bool + }{ + { + name: "statically linked ELF (PT_LOAD only)", + content: buildMinimalELF64([]elf.ProgType{elf.PT_LOAD}), + expect: true, + }, + { + name: "dynamically linked ELF (has PT_INTERP)", + content: buildMinimalELF64([]elf.ProgType{elf.PT_INTERP, elf.PT_LOAD}), + expect: false, + }, + { + name: "not an ELF file", + content: []byte("this is not an ELF binary"), + expect: false, + }, + { + name: "empty file", + content: []byte{}, + expect: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + file := filepath.Join(dir, "test-binary") + err := os.WriteFile(file, tc.content, 0755) + require.NoError(t, err) + + result := isStaticallyLinkedELF(file) + assert.Equal(t, tc.expect, result) + }) + } +} + +func TestIsStaticallyLinkedELFFileDoesNotExist(t *testing.T) { + result := isStaticallyLinkedELF("/does/not/exist") + assert.False(t, result) +} diff --git a/src/pkg/architecture/binfmt_misc_test.go b/src/pkg/architecture/binfmt_misc_test.go new file mode 100644 index 000000000..3f4914b05 --- /dev/null +++ b/src/pkg/architecture/binfmt_misc_test.go @@ -0,0 +1,312 @@ +/* + * Copyright © 2019 – 2026 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package architecture + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBytesToEscapedString(t *testing.T) { + testCases := []struct { + name string + input []byte + expect string + }{ + { + name: "ELF magic bytes", + input: []byte{0x7f, 0x45, 0x4c, 0x46}, + expect: `\x7f\x45\x4c\x46`, + }, + { + name: "ELF magic bytes (char)", + input: []byte{0x7f, 'E', 'L', 'F'}, + expect: `\x7f\x45\x4c\x46`, + }, + { + name: "single zero byte", + input: []byte{0x00}, + expect: `\x00`, + }, + { + name: "single 0xff byte", + input: []byte{0xff}, + expect: `\xff`, + }, + { + name: "empty input", + input: []byte{}, + expect: "", + }, + { + name: "nil input", + input: nil, + expect: "", + }, + { + name: "all zeros", + input: []byte{0x00, 0x00, 0x00}, + expect: `\x00\x00\x00`, + }, + { + name: "all 0xff", + input: []byte{0xff, 0xff, 0xff}, + expect: `\xff\xff\xff`, + }, + { + name: "aarch64 ELFMagic full", + input: []byte{ + 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0xb7, 0x00, + }, + expect: `\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00`, + }, + { + name: "aarch64 ELFMask full", + input: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0xff, + }, + expect: `\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := bytesToEscapedString(tc.input) + assert.Equal(t, tc.expect, result) + }) + } +} + +func TestBuildRegistrationString(t *testing.T) { + testCases := []struct { + name string + reg Registration + expect string + }{ + { + name: "standard aarch64 registration", + reg: Registration{ + Name: "qemu-aarch64", + MagicType: "M", + Offset: "0", + Magic: []byte{0x7f, 0x45}, + Mask: []byte{0xff, 0xff}, + Interpreter: "/run/host/usr/bin/qemu-aarch64-static", + Flags: "FC", + }, + expect: ":qemu-aarch64:M:0:\\x7f\\x45:\\xff\\xff:/run/host/usr/bin/qemu-aarch64-static:FC", + }, + { + name: "registration with empty magic and mask", + reg: Registration{ + Name: "test", + MagicType: "M", + Offset: "0", + Magic: []byte{}, + Mask: []byte{}, + Interpreter: "/usr/bin/test", + Flags: "F", + }, + expect: ":test:M:0:::/usr/bin/test:F", + }, + { + name: "registration with nil magic and mask", + reg: Registration{ + Name: "test", + MagicType: "M", + Offset: "0", + Magic: nil, + Mask: nil, + Interpreter: "/usr/bin/test", + Flags: "F", + }, + expect: ":test:M:0:::/usr/bin/test:F", + }, + { + name: "registration with all empty strings", + reg: Registration{ + Magic: []byte{}, + Mask: []byte{}, + }, + expect: ":::::::", + }, + { + name: "registration with non-default offset", + reg: Registration{ + Name: "custom", + MagicType: "M", + Offset: "16", + Magic: []byte{0x7f, 0x45, 0x4c, 0x46}, + Mask: []byte{0xfe, 0xff, 0xff, 0xff}, + Interpreter: "/run/host/usr/bin/qemu-aarch64", + Flags: "FC", + }, + expect: ":custom:M:16:\\x7f\\x45\\x4c\\x46:\\xfe\\xff\\xff\\xff:/run/host/usr/bin/qemu-aarch64:FC", + }, + { + name: "registration with flags only F", + reg: Registration{ + Name: "qemu-x86_64", + MagicType: "M", + Offset: "0", + Magic: []byte{0x7f, 0x45}, + Mask: []byte{0xff, 0xff}, + Interpreter: "/run/host/usr/bin/qemu-x86_64-static", + Flags: "F", + }, + expect: ":qemu-x86_64:M:0:\\x7f\\x45:\\xff\\xff:/run/host/usr/bin/qemu-x86_64-static:F", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := tc.reg.buildRegistrationString() + assert.Equal(t, tc.expect, result) + }) + } +} + +func TestGetDefaultRegistration(t *testing.T) { + testCases := []struct { + name string + archID int + interpreterPath string + expectNil bool + expectName string + expectInterp string + expectFlags string + expectMagicType string + expectOffset string + }{ + { + name: "aarch64 with absolute path", + archID: Aarch64, + interpreterPath: "/usr/bin/qemu-aarch64-static", + expectName: "qemu-aarch64", + expectInterp: "/run/host/usr/bin/qemu-aarch64-static", + expectFlags: defaultFlags, + expectMagicType: defaultMagicType, + expectOffset: defaultOffset, + }, + { + name: "aarch64 with -run-host prefix already present", + archID: Aarch64, + interpreterPath: "/run/host/usr/bin/qemu-aarch64-static", + expectName: "qemu-aarch64", + expectInterp: "/run/host/usr/bin/qemu-aarch64-static", + expectFlags: defaultFlags, + expectMagicType: defaultMagicType, + expectOffset: defaultOffset, + }, + { + name: "x86_64 with absolute path", + archID: X86_64, + interpreterPath: "/usr/bin/qemu-x86_64-static", + expectName: "qemu-x86_64", + expectInterp: "/run/host/usr/bin/qemu-x86_64-static", + expectFlags: defaultFlags, + expectMagicType: defaultMagicType, + expectOffset: defaultOffset, + }, + { + name: "ppc64le with absolute path", + archID: Ppc64le, + interpreterPath: "/usr/bin/qemu-ppc64le-static", + expectName: "qemu-ppc64le", + expectInterp: "/run/host/usr/bin/qemu-ppc64le-static", + expectFlags: defaultFlags, + expectMagicType: defaultMagicType, + expectOffset: defaultOffset, + }, + { + name: "invalid arch returns nil", + archID: 999, + interpreterPath: "/usr/bin/qemu-fake", + expectNil: true, + }, + { + name: "NotSpecified returns nil", + archID: NotSpecified, + interpreterPath: "/usr/bin/qemu-fake", + expectNil: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + reg := getDefaultRegistration(tc.archID, tc.interpreterPath) + + if tc.expectNil { + assert.Nil(t, reg) + return + } + + assert.NotNil(t, reg) + assert.Equal(t, tc.expectName, reg.Name) + assert.Equal(t, tc.expectInterp, reg.Interpreter) + assert.Equal(t, tc.expectFlags, reg.Flags) + assert.Equal(t, tc.expectMagicType, reg.MagicType) + assert.Equal(t, tc.expectOffset, reg.Offset) + + arch, _ := getArchitecture(tc.archID) + assert.Equal(t, arch.ELFMagic, reg.Magic) + assert.Equal(t, arch.ELFMask, reg.Mask) + }) + } +} + +func TestGetDefaultRegistrationInterpreterPathPrefixing(t *testing.T) { + testCases := []struct { + name string + interpreterPath string + expectInterp string + }{ + { + name: "absolute path gets -run-host prefix", + interpreterPath: "/usr/bin/qemu-aarch64-static", + expectInterp: "/run/host/usr/bin/qemu-aarch64-static", + }, + { + name: "-run-host- prefix is not duplicated", + interpreterPath: "/run/host/usr/bin/qemu-aarch64-static", + expectInterp: "/run/host/usr/bin/qemu-aarch64-static", + }, + { + name: "nested path", + interpreterPath: "/opt/custom/qemu/bin/qemu-aarch64-static", + expectInterp: "/run/host/opt/custom/qemu/bin/qemu-aarch64-static", + }, + { + name: "-run-host without trailing slash gets prefixed", + interpreterPath: "/run/host", + expectInterp: "/run/host/run/host", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + reg := getDefaultRegistration(Aarch64, tc.interpreterPath) + assert.NotNil(t, reg) + assert.Equal(t, tc.expectInterp, reg.Interpreter) + }) + } +} diff --git a/src/pkg/shell/shell_test.go b/src/pkg/shell/shell_test.go index d5e6a9ed6..ef3937416 100644 --- a/src/pkg/shell/shell_test.go +++ b/src/pkg/shell/shell_test.go @@ -21,6 +21,7 @@ import ( "errors" "io" "os" + "os/exec" "strings" "testing" "time" @@ -364,6 +365,257 @@ func TestShellRunWithExitCode(t *testing.T) { } } +func TestRunContextWithExitCode2(t *testing.T) { + testCases := []struct { + cancel bool + command []string + err error + isExitError bool + exitCode int + stdout []byte + stderr []byte + timeout time.Duration + }{ + { + command: []string{"true"}, + }, + { + command: []string{"false"}, + isExitError: true, + exitCode: 1, + }, + { + command: []string{"echo"}, + stdout: []byte("\n"), + }, + { + command: []string{"echo", "hello, world"}, + stdout: []byte("hello, world\n"), + }, + { + command: []string{"command-does-not-exist"}, + err: exec.ErrNotFound, + exitCode: 1, + }, + { + command: []string{"cat", "/file/does/not/exist"}, + isExitError: true, + exitCode: 1, + stderr: []byte("cat: /file/does/not/exist: No such file or directory\n"), + }, + { + cancel: true, + command: []string{"sleep", "+Inf"}, + err: context.Canceled, + exitCode: 1, + }, + { + command: []string{"sleep", "+Inf"}, + err: context.DeadlineExceeded, + exitCode: 1, + timeout: 1 * time.Second, + }, + } + + for _, tc := range testCases { + name := strings.Join(tc.command, " ") + if tc.cancel { + name += " (cancel)" + } + if tc.timeout != 0 { + name += " (timeout)" + } + + t.Run(name, func(t *testing.T) { + var cancel context.CancelFunc + ctx := context.Background() + if tc.cancel { + ctx, cancel = context.WithCancel(ctx) + defer cancel() + } + if tc.timeout != 0 { + ctx, cancel = context.WithTimeout(ctx, tc.timeout) + defer cancel() + } + + if tc.cancel { + cancel() + } + + var stdout outputMock + var stderr outputMock + + exitCode, err := shell.RunContextWithExitCode2(ctx, + tc.command[0], + os.Stdin, + &stdout, + &stderr, + tc.command[1:]...) + + if tc.err == nil && !tc.isExitError { + assert.NoError(t, err) + } + + if tc.err != nil { + assert.Error(t, err) + assert.ErrorIs(t, err, tc.err) + } + + if tc.isExitError { + assert.Error(t, err) + var exitErr *exec.ExitError + assert.ErrorAs(t, err, &exitErr) + } + + assert.Equal(t, tc.exitCode, exitCode) + + if tc.stdout == nil { + assert.Empty(t, stdout.written) + } else { + assert.NotEmpty(t, stdout.written) + assert.Equal(t, tc.stdout, stdout.written) + } + + if tc.stderr == nil { + assert.Empty(t, stderr.written) + } else { + assert.NotEmpty(t, stderr.written) + assert.Equal(t, tc.stderr, stderr.written) + } + }) + } +} + +func TestRunWithExitCode2(t *testing.T) { + type input struct { + commandName string + stdIn io.Reader + args []string + loglevel logrus.Level + useStdErr bool + } + + type expect struct { + isExitError bool + err error + code int + stdout []byte + stderr []byte + } + + testCases := []struct { + name string + input input + expect expect + }{ + { + name: "OK_Without_stderr_and_info_log_level", + input: input{ + commandName: "echo", + stdIn: os.Stdin, + args: []string{"Toolbx test"}, + loglevel: logrus.InfoLevel, + useStdErr: false, + }, + expect: expect{ + code: 0, + stdout: []byte("Toolbx test\n"), + }, + }, + { + name: "OK_Without_stderr_and_debug_log_level", + input: input{ + commandName: "echo", + stdIn: os.Stdin, + args: []string{"Toolbx test"}, + loglevel: logrus.DebugLevel, + useStdErr: false, + }, + expect: expect{ + code: 0, + stdout: []byte("Toolbx test\n"), + }, + }, + { + name: "OK_With_stderr_and_info_log_level", + input: input{ + commandName: "echo", + stdIn: os.Stdin, + args: nil, + loglevel: logrus.InfoLevel, + useStdErr: true, + }, + expect: expect{ + code: 0, + stdout: []byte("\n"), + }, + }, + { + name: "FAIL_NonExisting_Command_preserves_ErrNotFound", + input: input{ + commandName: "no-exist-executable", + stdIn: os.Stdin, + args: []string{"Toolbx test"}, + loglevel: logrus.InfoLevel, + useStdErr: false, + }, + expect: expect{ + err: exec.ErrNotFound, + code: 1, + }, + }, + { + name: "FAIL_Command_preserves_ExitError", + input: input{ + commandName: "cat", + stdIn: os.Stdin, + args: []string{"/bogus/file.foo"}, + loglevel: logrus.InfoLevel, + useStdErr: true, + }, + expect: expect{ + isExitError: true, + code: 1, + stderr: []byte("cat: /bogus/file.foo: No such file or directory\n"), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var actualStdOut outputMock + var actualStdErr outputMock + + logrus.SetLevel(tc.input.loglevel) + + code, err := shell.RunWithExitCode2(tc.input.commandName, tc.input.stdIn, &actualStdOut, &actualStdErr, tc.input.args...) + + if tc.expect.err == nil && !tc.expect.isExitError { + assert.NoError(t, err) + } + + if tc.expect.err != nil { + assert.Error(t, err) + assert.ErrorIs(t, err, tc.expect.err) + } + + if tc.expect.isExitError { + assert.Error(t, err) + var exitErr *exec.ExitError + assert.ErrorAs(t, err, &exitErr) + } + + assert.Equal(t, tc.expect.code, code) + assert.Equal(t, tc.expect.stdout, actualStdOut.written) + if tc.input.useStdErr { + assert.Equal(t, tc.expect.stderr, actualStdErr.written) + } else { + assert.Empty(t, actualStdErr) + } + }) + } +} + // outputMock is a mock to ensure content written to stdout/stderr was correct type outputMock struct { written []byte diff --git a/src/pkg/skopeo/skopeo_test.go b/src/pkg/skopeo/skopeo_test.go new file mode 100644 index 000000000..8b9a44a46 --- /dev/null +++ b/src/pkg/skopeo/skopeo_test.go @@ -0,0 +1,1750 @@ +/* + * Copyright © 2019 – 2026 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package skopeo + +import ( + "encoding/json" + "testing" + + "github.com/containers/toolbox/pkg/architecture" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestImageGetSize(t *testing.T) { + testCases := []struct { + name string + image Image + expect float64 + errMsg string + }{ + { + name: "single layer", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("73528142")}, + }, + }, + expect: 73528142, + }, + { + name: "multiple layers", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("73528142")}, + {Size: json.Number("1849805")}, + {Size: json.Number("512")}, + }, + }, + expect: 75378459, + }, + { + name: "zero-size layers", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("0")}, + {Size: json.Number("0")}, + }, + }, + expect: 0, + }, + { + name: "empty LayersData slice", + image: Image{ + LayersData: []Layer{}, + }, + expect: 0, + }, + { + name: "nil LayersData", + image: Image{ + LayersData: nil, + }, + expect: -1, + errMsg: "'skopeo inspect' did not have LayersData", + }, + { + name: "invalid size value", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("not-a-number")}, + }, + }, + expect: -1, + errMsg: "strconv.ParseFloat: parsing \"not-a-number\": invalid syntax", + }, + { + name: "large layer sizes", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("2147483648")}, + {Size: json.Number("2147483648")}, + }, + }, + expect: 4294967296, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + size, err := tc.image.GetSize() + + if tc.errMsg != "" { + assert.Error(t, err) + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tc.expect, size) + }) + } +} + +func TestImageGetSizeHuman(t *testing.T) { + testCases := []struct { + name string + image Image + expect string + errMsg string + }{ + { + name: "typical image size", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("73528142")}, + {Size: json.Number("1849805")}, + }, + }, + expect: "75.38MB", + }, + { + name: "large image size over 1 GB", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("2204748042")}, + }, + }, + expect: "2.205GB", + }, + { + name: "zero size", + image: Image{ + LayersData: []Layer{ + {Size: json.Number("0")}, + }, + }, + expect: "0B", + }, + { + name: "nil LayersData", + image: Image{ + LayersData: nil, + }, + errMsg: "'skopeo inspect' did not have LayersData", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sizeHuman, err := tc.image.GetSizeHuman() + + if tc.errMsg != "" { + assert.Error(t, err) + assert.EqualError(t, err, tc.errMsg) + assert.Empty(t, sizeHuman) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expect, sizeHuman) + } + }) + } +} + +func TestImageVerifyArchitectureMatch(t *testing.T) { + testCases := []struct { + name string + image Image + expectedArchID int + errMsg string + }{ + { + name: "amd64 image matches x86_64", + image: Image{ + Architecture: "amd64", + NameFull: "registry.fedoraproject.org/fedora-toolbox:41", + }, + expectedArchID: architecture.X86_64, + }, + { + name: "arm64 image matches aarch64", + image: Image{ + Architecture: "arm64", + NameFull: "registry.fedoraproject.org/fedora-toolbox:41-aarch64", + }, + expectedArchID: architecture.Aarch64, + }, + { + name: "ppc64le image matches ppc64le", + image: Image{ + Architecture: "ppc64le", + NameFull: "registry.fedoraproject.org/fedora-toolbox:41-ppc64le", + }, + expectedArchID: architecture.Ppc64le, + }, + { + name: "amd64 image does not match aarch64", + image: Image{ + Architecture: "amd64", + NameFull: "registry.fedoraproject.org/fedora-toolbox:41", + }, + expectedArchID: architecture.Aarch64, + errMsg: "image registry.fedoraproject.org/fedora-toolbox:41 is a single-architecture image for amd64, but arm64 was requested", + }, + { + name: "arm64 image does not match x86_64", + image: Image{ + Architecture: "arm64", + NameFull: "registry.fedoraproject.org/fedora-toolbox:41-aarch64", + }, + expectedArchID: architecture.X86_64, + errMsg: "image registry.fedoraproject.org/fedora-toolbox:41-aarch64 is a single-architecture image for arm64, but amd64 was requested", + }, + { + name: "unsupported architecture in image", + image: Image{ + Architecture: "mips", + NameFull: "example.com/custom-image:latest", + }, + expectedArchID: architecture.X86_64, + errMsg: "architecture 'mips' is not supported by Toolbx", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.image.VerifyArchitectureMatch(tc.expectedArchID) + + if tc.errMsg != "" { + assert.Error(t, err) + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestInspectJSONUnmarshal(t *testing.T) { + type expect struct { + architecture string + layersCount int + totalSize float64 + matchArchID int + mismatchArchID int + } + + testCases := []struct { + name string + data string + expect expect + }{ + { + name: "fedora_38,skopeo_1.15.0", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:efde4efa9f18e619f62a14849959a436fc8062b2248aca794a0f4957b1d2eeca\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2024-02-01T19:09:35.77181Z\"," + + " \"DockerVersion\": \"1.13.1\"," + + " \"Labels\": {" + + " \"architecture\": \"x86_64\"," + + " \"authoritative-source-url\": \"registry.fedoraproject.org\"," + + " \"build-date\": \"2024-02-01T19:07:19.783946\"," + + " \"com.github.containers.toolbox\": \"true\"," + + " \"com.redhat.build-host\": \"osbs-node01.iad2.fedoraproject.org\"," + + " \"com.redhat.component\": \"fedora-toolbox\"," + + " \"distribution-scope\": \"public\"," + + " \"license\": \"MIT\"," + + " \"maintainer\": \"Debarshi Ray \\u003crishi@fedoraproject.org\\u003e\"," + + " \"name\": \"fedora-toolbox\"," + + " \"release\": \"20\"," + + " \"summary\": \"Base image for creating Fedora toolbox containers\"," + + " \"usage\": \"This image is meant to be used with the toolbox command\"," + + " \"vcs-ref\": \"c36ca58a44f947077b7c62e17d7b4fe4cd1b5797\"," + + " \"vcs-type\": \"git\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"38\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:0642c0ebdff310231260783e8d24b6b322dd220d43ac297538242fe87350fcb6\"," + + " \"sha256:badbfb3b457582ca3e9dcb7617fe76c45cedfb6bd4f3e6970703ac0161c0e591\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\"," + + " \"Digest\": \"sha256:0642c0ebdff310231260783e8d24b6b322dd220d43ac297538242fe87350fcb6\"," + + " \"Size\": 68602971," + + " \"Annotations\": null" + + " }," + + " {" + + " \"MIMEType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\"," + + " \"Digest\": \"sha256:badbfb3b457582ca3e9dcb7617fe76c45cedfb6bd4f3e6970703ac0161c0e591\"," + + " \"Size\": 248948495," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"DISTTAG=f38container\"," + + " \"FGC=f38\"," + + " \"container=oci\"," + + " \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 2, + totalSize: 317551466, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + { + name: "fedora_38,skopeo_1.15.0,architecture_arm64", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:efde4efa9f18e619f62a14849959a436fc8062b2248aca794a0f4957b1d2eeca\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2024-02-01T19:10:17.64198Z\"," + + " \"DockerVersion\": \"19.03.13\"," + + " \"Labels\": {" + + " \"architecture\": \"arm64\"," + + " \"authoritative-source-url\": \"registry.fedoraproject.org\"," + + " \"build-date\": \"2024-02-01T19:07:18.853192\"," + + " \"com.github.containers.toolbox\": \"true\"," + + " \"com.redhat.build-host\": \"osbs-aarch64-node01.iad2.fedoraproject.org\"," + + " \"com.redhat.component\": \"fedora-toolbox\"," + + " \"distribution-scope\": \"public\"," + + " \"license\": \"MIT\"," + + " \"maintainer\": \"Debarshi Ray \\u003crishi@fedoraproject.org\\u003e\"," + + " \"name\": \"fedora-toolbox\"," + + " \"release\": \"20\"," + + " \"summary\": \"Base image for creating Fedora toolbox containers\"," + + " \"usage\": \"This image is meant to be used with the toolbox command\"," + + " \"vcs-ref\": \"c36ca58a44f947077b7c62e17d7b4fe4cd1b5797\"," + + " \"vcs-type\": \"git\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"38\"" + + " }," + + " \"Architecture\": \"arm64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:9104857c647cd9953bca093a735e9aeb8940d9852ae2ed4080def13a55506260\"," + + " \"sha256:ab482b93b9e3916c98db0482b961294e4624501a1f4e4920fe202d3d09e6086a\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\"," + + " \"Digest\": \"sha256:9104857c647cd9953bca093a735e9aeb8940d9852ae2ed4080def13a55506260\"," + + " \"Size\": 67268628," + + " \"Annotations\": null" + + " }," + + " {" + + " \"MIMEType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\"," + + " \"Digest\": \"sha256:ab482b93b9e3916c98db0482b961294e4624501a1f4e4920fe202d3d09e6086a\"," + + " \"Size\": 279142877," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"DISTTAG=f38container\"," + + " \"FGC=f38\"," + + " \"container=oci\"," + + " \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"" + + " ]" + + "}", + expect: expect{ + architecture: "arm64", + layersCount: 2, + totalSize: 346411505, + matchArchID: architecture.Aarch64, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_39,skopeo_1.16.1", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:11f3634d4a8f2d4a69c4ad8442133f69979be49fa6269eccc6ab0863c39d59d0\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2024-11-26T07:51:25Z\"," + + " \"DockerVersion\": \"1.10.1\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"39\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:40eb91ad8a1c0e03d84a95ef38af4fe3caac449d78e796539a6062056e1f5777\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\"," + + " \"Digest\": \"sha256:40eb91ad8a1c0e03d84a95ef38af4fe3caac449d78e796539a6062056e1f5777\"," + + " \"Size\": 362650126," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"DISTTAG=f39container\"," + + " \"FGC=f39\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 1, + totalSize: 362650126, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + { + name: "fedora_39,skopeo_1.16.1,architecture_arm64", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:11f3634d4a8f2d4a69c4ad8442133f69979be49fa6269eccc6ab0863c39d59d0\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2024-11-26T07:50:54Z\"," + + " \"DockerVersion\": \"1.10.1\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"39\"" + + " }," + + " \"Architecture\": \"arm64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:56d6ea82c48514304c4dbf3a8296a159458440f4835520ae1783429fa3acd792\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\"," + + " \"Digest\": \"sha256:56d6ea82c48514304c4dbf3a8296a159458440f4835520ae1783429fa3acd792\"," + + " \"Size\": 336450730," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"DISTTAG=f39container\"," + + " \"FGC=f39\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "arm64", + layersCount: 1, + totalSize: 336450730, + matchArchID: architecture.Aarch64, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_39,skopeo_1.16.1,architecture_ppc64le", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:11f3634d4a8f2d4a69c4ad8442133f69979be49fa6269eccc6ab0863c39d59d0\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2024-11-26T07:58:00Z\"," + + " \"DockerVersion\": \"1.10.1\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"39\"" + + " }," + + " \"Architecture\": \"ppc64le\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:014b7e796167e4c90ac97df346776a9253b099f11ddff67656f100de92bedf5f\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\"," + + " \"Digest\": \"sha256:014b7e796167e4c90ac97df346776a9253b099f11ddff67656f100de92bedf5f\"," + + " \"Size\": 346423938," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"DISTTAG=f39container\"," + + " \"FGC=f39\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "ppc64le", + layersCount: 1, + totalSize: 346423938, + matchArchID: architecture.Ppc64le, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_40,skopeo_1.18.0", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:c3cc9836c9e55475f85496f9369e925164f1b7cd832b55e22c13b74840576c31\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2025-05-13T07:49:00.272423532Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.39.2\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"40\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"40\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:3fa9653b37941f2c2fcdbdaaf6ac8d97496e3e331cc4aeb27629bd65ee5b94a8\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:3fa9653b37941f2c2fcdbdaaf6ac8d97496e3e331cc4aeb27629bd65ee5b94a8\"," + + " \"Size\": 378214266," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 1, + totalSize: 378214266, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + { + name: "fedora_40,skopeo_1.18.0,architecture_arm64", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:c3cc9836c9e55475f85496f9369e925164f1b7cd832b55e22c13b74840576c31\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2025-05-13T07:48:52.762718634Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.39.2\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"40\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"40\"" + + " }," + + " \"Architecture\": \"arm64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:8c8790edeeac9e5823cf1442623d8eac2d83c1b8a12465351e9c159a33a89565\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:8c8790edeeac9e5823cf1442623d8eac2d83c1b8a12465351e9c159a33a89565\"," + + " \"Size\": 357895766," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "arm64", + layersCount: 1, + totalSize: 357895766, + matchArchID: architecture.Aarch64, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_40,skopeo_1.18.0,architecture_ppc64le", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:c3cc9836c9e55475f85496f9369e925164f1b7cd832b55e22c13b74840576c31\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2025-05-13T07:50:04.841519173Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.39.2\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"40\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"40\"" + + " }," + + " \"Architecture\": \"ppc64le\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:6fa7808a885e4c0597fbe0da6832bfb114a1baf4cd0383a7f6ad14fd14c713fc\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:6fa7808a885e4c0597fbe0da6832bfb114a1baf4cd0383a7f6ad14fd14c713fc\"," + + " \"Size\": 362731624," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "ppc64le", + layersCount: 1, + totalSize: 362731624, + matchArchID: architecture.Ppc64le, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_41,skopeo_1.20.0", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:f5f4b8d1c904810404117d99325e7130452c71e94cd78077e0141d3f0141eda0\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2025-12-15T07:47:49.048519394Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.41.5\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"41\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"41\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:4050d18ab275ca85fcf4de4ea4f01355ecf605a1ac1c944fe540e3a1838e1559\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:4050d18ab275ca85fcf4de4ea4f01355ecf605a1ac1c944fe540e3a1838e1559\"," + + " \"Size\": 387608637," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 1, + totalSize: 387608637, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + { + name: "fedora_41,skopeo_1.20.0,architecture_arm64", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:f5f4b8d1c904810404117d99325e7130452c71e94cd78077e0141d3f0141eda0\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2025-12-15T07:47:52.135380621Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.41.5\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"41\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"41\"" + + " }," + + " \"Architecture\": \"arm64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:3b9ad2fb7a433b49659fa781d9406a4faa86a92a284ebfcc9eeb2accae5fa8da\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:3b9ad2fb7a433b49659fa781d9406a4faa86a92a284ebfcc9eeb2accae5fa8da\"," + + " \"Size\": 358789413," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "arm64", + layersCount: 1, + totalSize: 358789413, + matchArchID: architecture.Aarch64, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_41,skopeo_1.20.0,architecture_ppc64le", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:f5f4b8d1c904810404117d99325e7130452c71e94cd78077e0141d3f0141eda0\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2025-12-15T07:48:34.495136412Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.41.5\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"41\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"41\"" + + " }," + + " \"Architecture\": \"ppc64le\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:9920118d3124e87eb834b4777f83ffb19a455d8cd3d7c64f95fbad6473acc43b\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:9920118d3124e87eb834b4777f83ffb19a455d8cd3d7c64f95fbad6473acc43b\"," + + " \"Size\": 364425315," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "ppc64le", + layersCount: 1, + totalSize: 364425315, + matchArchID: architecture.Ppc64le, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_42,skopeo_1.22.2", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:c4ebfe5df39c582a1d15c1a2251c6f7060bb07568f28913289e73b1b72b882ab\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2026-05-03T07:47:44.819587225Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.43.1\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.licenses\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.title\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"42\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"42\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:285a4586579509386fd5c5899923ced5b72c21c29682b32ed0ba0c68b909d04c\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:285a4586579509386fd5c5899923ced5b72c21c29682b32ed0ba0c68b909d04c\"," + + " \"Size\": 369354285," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/bin:/usr/bin\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 1, + totalSize: 369354285, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + { + name: "fedora_42,skopeo_1.22.2,architecture_arm64", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:c4ebfe5df39c582a1d15c1a2251c6f7060bb07568f28913289e73b1b72b882ab\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2026-05-03T07:48:18.994213287Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.43.1\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.licenses\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.title\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"42\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"42\"" + + " }," + + " \"Architecture\": \"arm64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:fd87ecd39d657fd1bde5020ebc33460925b94ac43a4d09e8c5ad535504d746b9\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:fd87ecd39d657fd1bde5020ebc33460925b94ac43a4d09e8c5ad535504d746b9\"," + + " \"Size\": 349545018," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/bin:/usr/bin\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "arm64", + layersCount: 1, + totalSize: 349545018, + matchArchID: architecture.Aarch64, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_42,skopeo_1.22.2,architecture_ppc64le", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:c4ebfe5df39c582a1d15c1a2251c6f7060bb07568f28913289e73b1b72b882ab\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2026-05-03T07:49:12.61781795Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.43.1\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.licenses\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.title\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"42\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"42\"" + + " }," + + " \"Architecture\": \"ppc64le\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:db1e644afebaba886ca48a84fbd6f7aa3dd0f53e03af807f06b405a9a0fd443d\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:db1e644afebaba886ca48a84fbd6f7aa3dd0f53e03af807f06b405a9a0fd443d\"," + + " \"Size\": 358713438," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/bin:/usr/bin\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "ppc64le", + layersCount: 1, + totalSize: 358713438, + matchArchID: architecture.Ppc64le, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_43,skopeo_1.22.2", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:06c9a93965c14d87dcb75ab5e03df0232b5ed71ccc9e98b818bdf665d3d095ab\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2026-05-02T05:48:04.671009859Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.43.1\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.licenses\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.title\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"43\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"43\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:07e96dd49dae8b7557b1a483eb263f1f96def8f7a56ec76c70aa06802264f722\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:07e96dd49dae8b7557b1a483eb263f1f96def8f7a56ec76c70aa06802264f722\"," + + " \"Size\": 359026274," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/bin:/usr/bin\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 1, + totalSize: 359026274, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + { + name: "fedora_43,skopeo_1.22.2,architecture_arm64", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:06c9a93965c14d87dcb75ab5e03df0232b5ed71ccc9e98b818bdf665d3d095ab\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2026-05-02T05:48:22.512575956Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.43.1\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.licenses\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.title\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"43\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"43\"" + + " }," + + " \"Architecture\": \"arm64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:467ccd74d1af1ef3dc3122490cf0c5083a8bd8c6dbe01be2ddb73921db7e8c64\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:467ccd74d1af1ef3dc3122490cf0c5083a8bd8c6dbe01be2ddb73921db7e8c64\"," + + " \"Size\": 339693369," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/bin:/usr/bin\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "arm64", + layersCount: 1, + totalSize: 339693369, + matchArchID: architecture.Aarch64, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "fedora_43,skopeo_1.22.2,architecture_ppc64le", + data: "" + + "{" + + " \"Name\": \"registry.fedoraproject.org/fedora-toolbox\"," + + " \"Digest\": \"sha256:06c9a93965c14d87dcb75ab5e03df0232b5ed71ccc9e98b818bdf665d3d095ab\"," + + " \"RepoTags\": [" + + " \"32\", \"32-12\", \"32-13\", \"32-14\", \"33\"," + + " \"33-12\", \"33-13\", \"33-14\", \"33-15\", \"33-16\"," + + " \"33-17\", \"33-18\", \"34\", \"34-12\", \"34-13\"," + + " \"34-14\", \"34-15\", \"34-16\", \"34-17\", \"34-18\"," + + " \"34-19\", \"34-20\", \"34-21\", \"34-22\", \"34-7\"," + + " \"34-9\", \"35\", \"35-1\", \"35-11\", \"35-12\"," + + " \"35-13\", \"35-14\", \"35-15\", \"35-16\", \"35-17\"," + + " \"35-18\", \"35-3\", \"35-4\", \"35-5\", \"35-6\"," + + " \"35-8\", \"35-9\", \"36\", \"36-10\", \"36-14\"," + + " \"36-15\", \"36-16\", \"36-2\", \"36-3\", \"36-4\"," + + " \"36-5\", \"36-6\", \"36-7\", \"36-8\", \"37\"," + + " \"37-1\", \"37-13\", \"37-14\", \"37-15\", \"37-16\"," + + " \"37-17\", \"37-2\", \"37-20\", \"37-3\", \"37-4\"," + + " \"37-5\", \"37-6\", \"37-8\", \"38\", \"38-1\"," + + " \"38-12\", \"38-13\", \"38-14\", \"38-15\", \"38-16\"," + + " \"38-17\", \"38-19\", \"38-2\", \"38-20\", \"38-3\"," + + " \"38-4\", \"39\", \"39-1\", \"39-2\", \"39-3\"," + + " \"39-4\", \"39-aarch64\", \"39-ppc64le\", \"39-s390x\", \"39-x86_64\"," + + " \"40\", \"40-aarch64\", \"40-ppc64le\", \"40-s390x\", \"40-x86_64\"," + + " \"41\", \"41-aarch64\", \"41-ppc64le\", \"41-s390x\", \"41-x86_64\"," + + " \"42\", \"42-aarch64\", \"42-ppc64le\", \"42-s390x\", \"42-x86_64\"," + + " \"43\", \"43-aarch64\", \"43-ppc64le\", \"43-s390x\", \"43-x86_64\"," + + " \"44\", \"44-aarch64\", \"44-ppc64le\", \"44-s390x\", \"44-x86_64\"," + + " \"45\", \"45-aarch64\", \"45-ppc64le\", \"45-s390x\", \"45-x86_64\"," + + " \"latest\", \"rawhide\", \"testing\"" + + " ]," + + " \"Created\": \"2026-05-02T05:49:14.078771354Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.43.1\"," + + " \"license\": \"MIT\"," + + " \"name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.license\": \"MIT\"," + + " \"org.opencontainers.image.licenses\": \"MIT\"," + + " \"org.opencontainers.image.name\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.title\": \"fedora-toolbox\"," + + " \"org.opencontainers.image.url\": \"https://fedoraproject.org/\"," + + " \"org.opencontainers.image.vendor\": \"Fedora Project\"," + + " \"org.opencontainers.image.version\": \"43\"," + + " \"vendor\": \"Fedora Project\"," + + " \"version\": \"43\"" + + " }," + + " \"Architecture\": \"ppc64le\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:ad3fe53c004ef47eb1fa876a2487d785732aeda88f41e969e02598dbf10ea1ca\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:ad3fe53c004ef47eb1fa876a2487d785732aeda88f41e969e02598dbf10ea1ca\"," + + " \"Size\": 343373524," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/bin:/usr/bin\"," + + " \"container=oci\"" + + " ]" + + "}", + expect: expect{ + architecture: "ppc64le", + layersCount: 1, + totalSize: 343373524, + matchArchID: architecture.Ppc64le, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "ubuntu_24.04,skopeo_1.22.2", + data: "" + + "{" + + " \"Name\": \"quay.io/toolbx/ubuntu-toolbox\"," + + " \"Digest\": \"sha256:2989cc44ce8e136e5e33cdee1d0bb5fded5b782b4e863365762ac96d1f7fd7e1\"," + + " \"RepoTags\": [" + + " \"16.04\", \"18.04\", \"20.04\", \"22.04\", \"22.10\"," + + " \"23.04\", \"23.10\", \"24.04\", \"24.10\", \"25.04\"," + + " \"latest\"" + + " ]," + + " \"Created\": \"2026-04-27T00:59:54.842771346Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.33.7\"," + + " \"maintainer\": \"Ievgen Popovych \\u003cjmennius@gmail.com\\u003e\"," + + " \"name\": \"ubuntu-toolbox\"," + + " \"org.opencontainers.image.version\": \"24.04\"," + + " \"summary\": \"Base image for creating Ubuntu Toolbx containers\"," + + " \"usage\": \"This image is meant to be used with the toolbox command\"," + + " \"version\": \"24.04\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:9c6d3dc912b4c7f77d573326b2040a3d210efaf389734ccd1d335237e146b67a\"," + + " \"sha256:befa25c0402b5bd10c3b85e6f9ef7fbb8dec14b1f1fe2f7cba504f414ea96211\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:9c6d3dc912b4c7f77d573326b2040a3d210efaf389734ccd1d335237e146b67a\"," + + " \"Size\": 31654253," + + " \"Annotations\": null" + + " }," + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:befa25c0402b5bd10c3b85e6f9ef7fbb8dec14b1f1fe2f7cba504f414ea96211\"," + + " \"Size\": 175991436," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 2, + totalSize: 207645689, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + { + name: "ubuntu_24.04,skopeo_1.22.2,architecture_arm64", + data: "" + + "{" + + " \"Name\": \"quay.io/toolbx/ubuntu-toolbox\"," + + " \"Digest\": \"sha256:2989cc44ce8e136e5e33cdee1d0bb5fded5b782b4e863365762ac96d1f7fd7e1\"," + + " \"RepoTags\": [" + + " \"16.04\", \"18.04\", \"20.04\", \"22.04\", \"22.10\"," + + " \"23.04\", \"23.10\", \"24.04\", \"24.10\", \"25.04\"," + + " \"latest\"" + + " ]," + + " \"Created\": \"2026-04-27T01:28:47.169971593Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.33.7\"," + + " \"maintainer\": \"Ievgen Popovych \\u003cjmennius@gmail.com\\u003e\"," + + " \"name\": \"ubuntu-toolbox\"," + + " \"org.opencontainers.image.version\": \"24.04\"," + + " \"summary\": \"Base image for creating Ubuntu Toolbx containers\"," + + " \"usage\": \"This image is meant to be used with the toolbox command\"," + + " \"version\": \"24.04\"" + + " }," + + " \"Architecture\": \"arm64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:77efc964b64f9c6338305d55a8c7042eeb441ee5d9b23f2e37ad6684bcc939cb\"," + + " \"sha256:b1e40f994cd2a81d2dfdcba9bccc3e8391253cf9eb94ae2219faab2b6a5948e3\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:77efc964b64f9c6338305d55a8c7042eeb441ee5d9b23f2e37ad6684bcc939cb\"," + + " \"Size\": 30753035," + + " \"Annotations\": null" + + " }," + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:b1e40f994cd2a81d2dfdcba9bccc3e8391253cf9eb94ae2219faab2b6a5948e3\"," + + " \"Size\": 174479376," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"" + + " ]" + + "}", + expect: expect{ + architecture: "arm64", + layersCount: 2, + totalSize: 205232411, + matchArchID: architecture.Aarch64, + mismatchArchID: architecture.X86_64, + }, + }, + { + name: "arch,skopeo_1.22.2", + data: "" + + "{" + + " \"Name\": \"quay.io/toolbx/arch-toolbox\"," + + " \"Digest\": \"sha256:40a85480d3e8c72d9b2a56f8615b66accae502b9d233b3a18266121656467e5d\"," + + " \"RepoTags\": [" + + " \"latest\"" + + " ]," + + " \"Created\": \"2026-04-27T00:28:04.418383637Z\"," + + " \"DockerVersion\": \"\"," + + " \"Labels\": {" + + " \"com.github.containers.toolbox\": \"true\"," + + " \"io.buildah.version\": \"1.33.7\"," + + " \"maintainer\": \"Morten Linderud \\u003cfoxboron@archlinux.org\\u003e\"," + + " \"name\": \"arch-toolbox\"," + + " \"org.opencontainers.image.authors\": \"Santiago Torres-Arias \\u003csantiago@archlinux.org\\u003e (@SantiagoTorres), Christian Rebischke \\u003cChris.Rebischke@archlinux.org\\u003e (@shibumi), Justin Kromlinger \\u003chashworks@archlinux.org\\u003e (@hashworks)\"," + + " \"org.opencontainers.image.created\": \"2026-04-19T00:06:37+00:00\"," + + " \"org.opencontainers.image.description\": \"Official containerd image of Arch Linux, a simple, lightweight Linux distribution aimed for flexibility.\"," + + " \"org.opencontainers.image.documentation\": \"https://wiki.archlinux.org/title/Docker#Arch_Linux\"," + + " \"org.opencontainers.image.licenses\": \"GPL-3.0-or-later\"," + + " \"org.opencontainers.image.revision\": \"0d7c4c0017584f9bcb495105cc412d6575f04564\"," + + " \"org.opencontainers.image.source\": \"https://gitlab.archlinux.org/archlinux/archlinux-docker\"," + + " \"org.opencontainers.image.title\": \"Arch Linux base-devel Image\"," + + " \"org.opencontainers.image.url\": \"https://gitlab.archlinux.org/archlinux/archlinux-docker/-/blob/master/README.md\"," + + " \"org.opencontainers.image.version\": \"20260419.0.517065\"," + + " \"summary\": \"Base image for creating Arch Linux Toolbx containers\"," + + " \"usage\": \"This image is meant to be used with the toolbox command\"," + + " \"version\": \"base-devel\"" + + " }," + + " \"Architecture\": \"amd64\"," + + " \"Os\": \"linux\"," + + " \"Layers\": [" + + " \"sha256:9713e57a91bb6c4d42be627d18cb764a4eb6221fa6a6fec0f66312bbc4279714\"," + + " \"sha256:8e68f3509eb65780eb16bd28716312a4f2ef3613e694ff4cfadd156b779c99af\"," + + " \"sha256:9e59132d6ea60eb12fe1792013e57e192c2f51c133d07f977c97f1fc16a69462\"" + + " ]," + + " \"LayersData\": [" + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:9713e57a91bb6c4d42be627d18cb764a4eb6221fa6a6fec0f66312bbc4279714\"," + + " \"Size\": 256601029," + + " \"Annotations\": null" + + " }," + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:8e68f3509eb65780eb16bd28716312a4f2ef3613e694ff4cfadd156b779c99af\"," + + " \"Size\": 9659," + + " \"Annotations\": null" + + " }," + + " {" + + " \"MIMEType\": \"application/vnd.oci.image.layer.v1.tar+gzip\"," + + " \"Digest\": \"sha256:9e59132d6ea60eb12fe1792013e57e192c2f51c133d07f977c97f1fc16a69462\"," + + " \"Size\": 361785305," + + " \"Annotations\": null" + + " }" + + " ]," + + " \"Env\": [" + + " \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"," + + " \"LANG=C.UTF-8\"" + + " ]" + + "}", + expect: expect{ + architecture: "amd64", + layersCount: 3, + totalSize: 618395993, + matchArchID: architecture.X86_64, + mismatchArchID: architecture.Aarch64, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + data := []byte(tc.data) + var image Image + err := json.Unmarshal(data, &image) + require.NoError(t, err) + + assert.Equal(t, tc.expect.architecture, image.Architecture) + assert.Len(t, image.LayersData, tc.expect.layersCount) + + size, err := image.GetSize() + assert.NoError(t, err) + assert.Equal(t, tc.expect.totalSize, size) + + image.NameFull = "test-image" + + err = image.VerifyArchitectureMatch(tc.expect.matchArchID) + assert.NoError(t, err) + + err = image.VerifyArchitectureMatch(tc.expect.mismatchArchID) + assert.Error(t, err) + }) + } +} From c0de2ec3faad8d5f16f9b7e05b9d8d7690938d56 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 14 May 2026 11:31:18 +0200 Subject: [PATCH 17/21] test/system: Add test infrastructure for non-native architecture containers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helper functions and image caching support for running Bats system tests against cross-architecture (non-native) Toolbx containers, using QEMU user-mode emulation via binfmt_misc. In helpers.bash: - Add 'non-native' to TOOLBX_TEST_SYSTEM_TAGS_ALL so that the new tests are included when all tags are run - Add _pull_and_cache_distro_image_cross_arch() that uses 'skopeo copy --override-arch' to cache a foreign architecture image variant, with the cache directory suffixed by the architecture name to avoid collisions with native images - Add pull_distro_image_cross_arch() and pull_default_image_cross_arch() to load cached cross-arch images into Podman's container storage with architecture-suffixed tags - Add create_distro_container_cross_arch() and create_default_container_cross_arch() for creating non-native containers using the --arch flag - Add pull_default_image_and_copy_to() that copies the default image under a different name, for testing custom image names with architecture-like suffixes - Add skip_if_no_cross_arch_support() that checks for QEMU and binfmt_misc availability and skips the test if not present - Add get_cross_arch() that maps the host architecture to a suitable foreign architecture for testing (x86_64 → arm64, aarch64 → amd64) - Add oci_arch_to_binfmt() for converting OCI architecture names to their binfmt_misc equivalents (arm64 → aarch64, amd64 → x86_64) - Add build_restricted_path() that creates a directory of symlinks to all host executables except specified patterns, for testing error paths when tools like QEMU or skopeo are missing In setup_suite.bash: - Add a 'non-native' tag block that caches the default system image, a Fedora 44 image, and their cross-arch variants for use by the non-native test suites https://github.com/containers/toolbox/pull/1794 Signed-off-by: Dalibor Kricka --- test/system/libs/helpers.bash | 302 +++++++++++++++++++++++++++++++++- test/system/setup_suite.bash | 11 ++ 2 files changed, 312 insertions(+), 1 deletion(-) diff --git a/test/system/libs/helpers.bash b/test/system/libs/helpers.bash index 4b48c7009..7f984086e 100644 --- a/test/system/libs/helpers.bash +++ b/test/system/libs/helpers.bash @@ -36,7 +36,7 @@ readonly DOCKER_REG_NAME="docker-registry" # Podman and Toolbx commands to run readonly TOOLBX="${TOOLBX:-$(command -v toolbox)}" -readonly TOOLBX_TEST_SYSTEM_TAGS_ALL="arch-fedora,commands-options,custom-image,runtime-environment,ubuntu" +readonly TOOLBX_TEST_SYSTEM_TAGS_ALL="arch-fedora,non-native,commands-options,custom-image,runtime-environment,ubuntu" readonly TOOLBX_TEST_SYSTEM_TAGS="${TOOLBX_TEST_SYSTEM_TAGS:-$TOOLBX_TEST_SYSTEM_TAGS_ALL}" # Images @@ -142,6 +142,83 @@ function _pull_and_cache_distro_image() { } +# Pulls a non-native image using Skopeo and saves it to a dir cache +# +# The image is fetched from the same registry as the native one, but with +# --override-arch to select the foreign architecture variant from a multi-arch +# manifest. The cache directory is suffixed with the arch name to avoid +# collisions with native images (Toolbx does it same way for non-native images). +# +# Parameters +# ========== +# - distro - os-release field ID (e.g., fedora, rhel) +# - version - os-release field VERSION_ID (e.g., 42) +# - arch - target architecture in OCI format (e.g., arm64, ppc64le) +# +# Only use during test suite setup for caching all images to be used throughout +# tests. +function _pull_and_cache_distro_image_cross_arch() { + local num_of_retries=5 + local timeout=10 + local cached=false + local distro + local version + local arch + local image + local image_archive + + distro="$1" + version="$2" + arch="$3" + + if [ -z "${IMAGES[$distro]+x}" ]; then + fail "Requested distro (${distro}) does not have a matching image" + return 1 + fi + + image="${IMAGES[$distro]}:${version}" + image_archive="${distro}-toolbox-${version}-${arch}" + + if [[ -d "${IMAGE_CACHE_DIR}/${image_archive}" ]] ; then + return 0 + fi + + if [ ! -d "${IMAGE_CACHE_DIR}" ]; then + run mkdir -p "${IMAGE_CACHE_DIR}" + assert_success + fi + + local error_message + local -i j + local -i ret_val + + for ((j = 0; j < num_of_retries; j++)); do + error_message="$( (skopeo copy --override-arch "${arch}" --dest-compress \ + "docker://${image}" \ + "dir:${IMAGE_CACHE_DIR}/${image_archive}" >/dev/null) 2>&1)" + ret_val="$?" + + if [ "$ret_val" -eq 0 ]; then + cached=true + break + fi + + sleep "$timeout" + done + + if ! $cached; then + echo "Failed to cache cross-arch image ${image} (${arch}) to ${IMAGE_CACHE_DIR}/${image_archive}" >&2 + [ "$error_message" != "" ] && echo "$error_message" >&2 + return "$ret_val" + fi + + cleanup_all + ret_val="$?" + + return "$ret_val" +} + + # Prepares a locally hosted image registry # # The registry is set up with Podman set to an alternative root. It won't @@ -255,6 +332,44 @@ function build_image_without_name() { } +# Creates a directory with symlinks to all executables from /usr/bin and +# /usr/sbin EXCEPT those matching the given glob patterns. +# +# Used for testing error paths where specific tools (skopeo, qemu) are +# expected to be missing. The resulting directory can be set as PATH to +# control what exec.LookPath() finds in Toolbx's Go code. +# +# Parameters: +# =========== +# - dest_dir - directory to populate with symlinks +# - exclude_patterns... - glob patterns to exclude (e.g., "qemu-*" "skopeo") +function build_restricted_path() { + local dest_dir="$1" + shift + + mkdir -p "$dest_dir" + + local f + local name + local skip + local pattern + + for f in /usr/bin/* /usr/sbin/*; do + [ -x "$f" ] || continue + name="$(basename "$f")" + [ -e "$dest_dir/$name" ] && continue + skip=false + for pattern in "$@"; do + # shellcheck disable=SC2254 + case "$name" in + $pattern) skip=true; break ;; + esac + done + $skip || ln -sf "$f" "$dest_dir/$name" + done +} + + function check_bats_version() { local required_version required_version="$1" @@ -271,6 +386,26 @@ function check_bats_version() { } +# Skips the current test if QEMU and binfmt_misc support is not available +# for the cross-arch architecture. Use in setup() or individual @test functions. +function skip_if_no_cross_arch_support() { + local cross_arch + cross_arch="$(get_cross_arch)" + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + if ! command -v "qemu-${binfmt_arch}-static" >/dev/null 2>&1 && \ + ! command -v "qemu-${binfmt_arch}" >/dev/null 2>&1; then + skip "QEMU for ${cross_arch} is not installed" + fi + + if [ ! -f "/proc/sys/fs/binfmt_misc/qemu-${binfmt_arch}" ] && \ + [ ! -f "/proc/sys/fs/binfmt_misc/qemu-${binfmt_arch}-static" ]; then + skip "binfmt_misc registration for ${binfmt_arch} is not present" + fi +} + + function get_busybox_image() { local image image="${IMAGES[busybox]}" @@ -343,6 +478,55 @@ function pull_distro_image() { } +# Copies a non-native image from cache to Podman's image store +# +# The image is stored with an arch-suffixed tag (e.g., :42-arm64) so that +# Toolbx can find it when --arch is used. +# +# An image has to be cached first. See _pull_and_cache_distro_image_cross_arch() +# +# Parameters: +# =========== +# - distro - os-release field ID (e.g., fedora, rhel) +# - version - os-release field VERSION_ID (e.g., 42) +# - arch - target architecture in OCI format (e.g., arm64, ppc64le) +function pull_distro_image_cross_arch() { + local distro + local version + local arch + local image + local image_archive + + distro="$1" + version="$2" + arch="$3" + + if [ -z "${IMAGES[$distro]+x}" ]; then + fail "Requested distro (${distro}) does not have a matching image" + return 1 + fi + + image="${IMAGES[$distro]}:${version}-${arch}" + image_archive="${distro}-toolbox-${version}-${arch}" + + # No need to copy if the image is already available in Podman + if podman image exists "${image}"; then + return 0 + fi + + # https://github.com/containers/skopeo/issues/547 for the options for containers-storage + run skopeo copy "dir:${IMAGE_CACHE_DIR}/${image_archive}" "containers-storage:[overlay@$TOOLBX_ROOTLESS_STORAGE_PATH+$TOOLBX_ROOTLESS_STORAGE_PATH]${image}" + + # shellcheck disable=SC2154 + if [ "$status" -ne 0 ]; then + echo "Failed to load cross-arch image ${image} from cache ${IMAGE_CACHE_DIR}/${image_archive}" + assert_success + fi + + return 0 +} + + # Copies the system's default image to Podman's image store # # See pull_default_image() for more info. @@ -351,6 +535,44 @@ function pull_default_image() { } +# Copies the system's default cross-arch image to Podman's image store +function pull_default_image_cross_arch() { + local cross_arch + cross_arch="$(get_cross_arch)" + pull_distro_image_cross_arch "$(get_system_id)" "$(get_system_version)" "${cross_arch}" +} + + +# Copies the system's default image to Podman's image store under a custom name +# +# Parameters: +# =========== +# - dest_image - the destination image name (e.g., "custom-image:v1-arm64") +function pull_default_image_and_copy_to() { + local dest_image="$1" + + pull_default_image + + local distro + local version + local image + + distro="$(get_system_id)" + version="$(get_system_version)" + image="${IMAGES[$distro]}:$version" + + # https://github.com/containers/skopeo/issues/547 for the options for containers-storage + run skopeo copy \ + "containers-storage:[overlay@$TOOLBX_ROOTLESS_STORAGE_PATH+$TOOLBX_ROOTLESS_STORAGE_PATH]$image" \ + "containers-storage:[overlay@$TOOLBX_ROOTLESS_STORAGE_PATH+$TOOLBX_ROOTLESS_STORAGE_PATH]$dest_image" + + if [ "$status" -ne 0 ]; then + echo "Failed to copy image $image to $dest_image" + assert_success + fi +} + + function pull_default_image_and_copy() { pull_default_image @@ -399,6 +621,34 @@ function create_distro_container() { } +# Creates a non-native container +# +# Pulling of the cross-arch image is taken care of by the function. +# +# Parameters: +# =========== +# - distro - os-release field ID (e.g., fedora, rhel) +# - version - os-release field VERSION_ID (e.g., 42) +# - container_name - name of the container +# - arch - target architecture in OCI format (e.g., arm64, ppc64le) +function create_distro_container_cross_arch() { + local distro + local version + local arch + local container_name + + distro="$1" + version="$2" + arch="$3" + container_name="$4" + + pull_distro_image_cross_arch "${distro}" "${version}" "${arch}" + + "$TOOLBX" --assumeyes create --arch "${arch}" --container "${container_name}" --distro "${distro}" --release "${version}" >/dev/null \ + || fail "Toolbx couldn't create cross-arch container '$container_name' (${arch})" +} + + # Creates a container with specific name matching the system # # Parameters: @@ -422,6 +672,18 @@ function create_default_container() { } +# Creates a default cross-arch container for the system +function create_default_container_cross_arch() { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_default_image_cross_arch + + "$TOOLBX" --assumeyes create --arch "${cross_arch}" >/dev/null \ + || fail "Toolbx couldn't create default cross-arch container (${cross_arch})" +} + + function start_container() { local container_name container_name="$1" @@ -579,6 +841,44 @@ function is_fedora_rawhide() ( ) +# Returns a non-native architecture (OCI format) suitable for cross-arch tests. +# +# On x86_64/amd64 hosts, returns "arm64". +# On aarch64/arm64 hosts, returns "amd64". +function get_cross_arch() { + local host_arch + host_arch="$(uname -m)" + + case "$host_arch" in + x86_64) echo "arm64" ;; + aarch64) echo "amd64" ;; + ppc64le) echo "arm64" ;; + *) + echo "No cross-arch mapping for host architecture: $host_arch" >&2 + return 1 + ;; + esac +} + + +# Converts an OCI architecture name to its binfmt_misc name. +# +# Parameters: +# =========== +# - oci_arch - architecture in OCI format (e.g., arm64, amd64, ppc64le) +function oci_arch_to_binfmt() { + case "$1" in + arm64) echo "aarch64" ;; + amd64) echo "x86_64" ;; + ppc64le) echo "ppc64le" ;; + *) + echo "Unknown OCI arch: $1" >&2 + return 1 + ;; + esac +} + + # Creates a container with org.freedesktop.Flatpak.SessionHelper D-Bus interface # # Pulling of an image is taken care of by the function diff --git a/test/system/setup_suite.bash b/test/system/setup_suite.bash index 216be00a3..e43aeb9d1 100644 --- a/test/system/setup_suite.bash +++ b/test/system/setup_suite.bash @@ -53,6 +53,17 @@ setup_suite() { _pull_and_cache_distro_image rhel 8.10 || false fi + if echo "$TOOLBX_TEST_SYSTEM_TAGS" | grep "non-native" >/dev/null 2>/dev/null; then + # Cache the default image (needed for some cross-arch tests that also use native containers) + _pull_and_cache_distro_image "$system_id" "$system_version" || false + _pull_and_cache_distro_image fedora 44 || false + # Cache a non-native architecture image for cross-arch tests + local cross_arch + cross_arch="$(get_cross_arch)" + _pull_and_cache_distro_image_cross_arch fedora 44 "${cross_arch}" || false + _pull_and_cache_distro_image_cross_arch "$system_id" "$system_version" "${cross_arch}" || false + fi + if echo "$TOOLBX_TEST_SYSTEM_TAGS" | grep "ubuntu" >/dev/null 2>/dev/null; then _pull_and_cache_distro_image ubuntu 16.04 || false _pull_and_cache_distro_image ubuntu 18.04 || false From d0b8797ff4033b5f975643b25e3b472be24ecf35 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 14 May 2026 11:38:04 +0200 Subject: [PATCH 18/21] test/system: Add non-native architecture tests for create and run commands Add system tests for the cross-architecture extensions to the 'create' and 'run' (including the entry point init-container) commands introduced in PRs #1787, #1788, and #1789. The 3xx numbering range mirrors the 1xx range for native command tests, with 301 corresponding to 101 (create) and 304 to 104 (run). 301-create.bats (28 tests) covers: - Basic cross-arch container creation with --arch - Interaction with --distro/--release, --image, and --container flags - binfmt architecture name aliases (e.g., aarch64 resolves to arm64) - Host architecture passed to --arch is treated as native - Architecture-suffixed image tags (inference and conflict detection) - Native and cross-arch container coexistence - Entry point arguments (--arch, --arch-emulator-path) - Error paths: unsupported architecture, case sensitivity, conflicting --arch and image tag, duplicate container, missing QEMU, fake QEMU (shell script instead of ELF), missing skopeo 304-run.bats (23 tests) covers: - Smoke tests with true(1) and uname(1) architecture verification - /run/.toolboxenv existence, --container targeting, container reuse - binfmt_misc mount and QEMU registration inside the container - Exit code propagation (false, exit 2) - Entry point error and delay handling - Stop and restart re-initialization - sudo inside cross-arch container - Native and cross-arch container coexistence without interference - QEMU emulator fallback paths (missing recorded path, /usr/bin fallback) All tests use the 'non-native' Bats file tag for selective execution. https://github.com/containers/toolbox/pull/1794 Signed-off-by: Dalibor Kricka --- test/system/301-create.bats | 843 ++++++++++++++++++++++++++++++++++++ test/system/304-run.bats | 604 ++++++++++++++++++++++++++ 2 files changed, 1447 insertions(+) create mode 100644 test/system/301-create.bats create mode 100644 test/system/304-run.bats diff --git a/test/system/301-create.bats b/test/system/301-create.bats new file mode 100644 index 000000000..21670ffc7 --- /dev/null +++ b/test/system/301-create.bats @@ -0,0 +1,843 @@ +# shellcheck shell=bats +# +# Copyright © 2019 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support +} + +setup() { + cleanup_all + pushd "$HOME" || return 1 +} + +teardown() { + popd || return 1 + cleanup_all +} + +@test "create: Cross-arch Smoke test" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local default_container + default_container="$(get_system_id)-toolbox-$(get_system_version)-${cross_arch}" + + pull_default_image_cross_arch + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --arch "${cross_arch}" + + assert_success + assert_line --index 0 "Created container: $default_container" + assert_line --index 1 "Enter with: toolbox enter $default_container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$default_container" + + run podman inspect \ + --format '{{index .Config.Labels "com.github.containers.toolbox"}} {{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$default_container" + + assert_success + assert_output "true ${cross_arch}" +} + +@test "create: Cross-arch with --arch and --distro/--release" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local container="fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$container" + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: Cross-arch with --arch (binfmt alias)" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local container="fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${binfmt_arch}" \ + --distro fedora \ + --release 44 + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$container" + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: Cross-arch with --image (no --distro/--release)" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local container="fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --image "fedora-toolbox:44" + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: Cross-arch inferred from image tag (no --arch)" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + local container="fedora-toolbox-44-${binfmt_arch}" + + # The image tag contains the architecture suffix — toolbox should infer + # the architecture from it without needing --arch + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --image "fedora-toolbox:44-${binfmt_arch}" + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: Cross-arch with --arch matching image tag suffix" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + local container="fedora-toolbox-44-${binfmt_arch}" + + # --arch matches the architecture in the image tag — no conflict + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --image "fedora-toolbox:44-${binfmt_arch}" + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: Cross-arch with custom container name" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local container="my-cross-arch-container" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --container "$container" \ + --distro fedora \ + --release 44 + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$container" + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: --arch matching host is treated as native" { + local host_arch + + case "$(uname -m)" in + x86_64) host_arch="amd64" ;; + aarch64) host_arch="arm64" ;; + ppc64le) host_arch="ppc64le" ;; + esac + + pull_default_image + + local default_container + default_container="$(get_system_id)-toolbox-$(get_system_version)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${host_arch}" + + assert_success + assert_line --index 0 "Created container: $default_container" + assert_line --index 1 "Enter with: toolbox enter" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$default_container" + + assert_success + assert_output "${host_arch}" +} + +@test "create: --arch matching host with --image is native" { + local host_arch + + case "$(uname -m)" in + x86_64) host_arch="amd64" ;; + aarch64) host_arch="arm64" ;; + ppc64le) host_arch="ppc64le" ;; + esac + + pull_default_image + + local default_image + default_image="$(get_default_image)" + + local container + container="$(get_system_id)-toolbox-$(get_system_version)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${host_arch}" \ + --image "$default_image" + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${host_arch}" +} + +@test "create: Image with arch-like suffix from unsupported distro is native" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local custom_image="localhost/custom-image:v1-${cross_arch}" + + pull_default_image_and_copy_to "$custom_image" + + local container="native-test-container" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --image "$custom_image" \ + --container "$container" + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + local host_arch + case "$(uname -m)" in + x86_64) host_arch="amd64" ;; + aarch64) host_arch="arm64" ;; + ppc64le) host_arch="ppc64le" ;; + esac + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${host_arch}" +} + +@test "create: Native and cross-arch containers can coexist" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_default_image + pull_default_image_cross_arch + + local native_container + native_container="$(get_system_id)-toolbox-$(get_system_version)" + + local cross_container + cross_container="$(get_system_id)-toolbox-$(get_system_version)-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create + + assert_success + assert_line --index 0 "Created container: $native_container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create --arch "${cross_arch}" + + assert_success + assert_line --index 0 "Created container: $cross_container" + assert [ ${#lines[@]} -eq 2 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman ps --all --format "{{.Names}}" + + assert_success + assert_line --index 0 "$native_container" + assert_line --index 1 "$cross_container" + assert [ ${#lines[@]} -eq 2 ] +} + +@test "create: Cross-arch image stored with arch-suffixed tag" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local container="fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman image exists "${IMAGES[fedora]}:44-${cross_arch}" + + assert_success + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: Image tag with arch suffix is not doubled" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + local container="fedora-toolbox-44-${binfmt_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --image "fedora-toolbox:44-${binfmt_arch}" + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman image exists "${IMAGES[fedora]}:44-${binfmt_arch}-${cross_arch}" + + assert_failure +} + +@test "create: Cross-arch entry point has --arch and --arch-emulator-path args" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local container="fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_success + assert_line --index 0 "Created container: $container" + assert_line --index 1 "Enter with: toolbox enter $container" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman inspect \ + --format '{{join .Config.Cmd " "}}' \ + --type container \ + "$container" + + assert_success + assert_output --partial "--arch" + assert_output --partial "--arch-emulator-path" + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "$container" + + assert_success + assert_output "${cross_arch}" +} + +@test "create: Try cross-arch with non-existing registry" { + local cross_arch + cross_arch="$(get_cross_arch)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --image "nonexistent-registry.invalid/fake-toolbox:44" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to verify: image nonexistent-registry.invalid/fake-toolbox:44 does not support architecture ${cross_arch} or the image does not exists at all" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "create: Try cross-arch with single-arch Arch Linux image" { + if [ "$(uname -m)" = "aarch64" ]; then + skip "arm64 is the host architecture" + fi + + local cross_arch + cross_arch="arm64" + + local image_arch + image_arch="${IMAGES[arch]}:latest" + + # Arch Linux Toolbox is a single-arch image (amd64 only). + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --image "${image_arch}" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: image ${image_arch} is a single-architecture image for amd64, but ${cross_arch} was requested" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "create: Try cross-arch with multi-arch Ubuntu image for ppc64le" { + if [ "$(uname -m)" = "ppc64le" ]; then + skip "ppc64le is the host architecture" + fi + + local image_ubuntu + image_ubuntu="${IMAGES[ubuntu]}:22.04" + + # Ubuntu Toolbox does not support ppc64le (amd64 and arm64 only). + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch ppc64le \ + --image "${image_ubuntu}" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to verify: image ${image_ubuntu} does not support architecture ppc64le or the image does not exists at all" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "create: Try with an unsupported architecture" { + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch sparc64 \ + --distro fedora \ + --release 44 + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: architecture 'sparc64' is not supported by Toolbx" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "create: Try with an empty architecture value" { + local host_arch + case "$(uname -m)" in + x86_64) host_arch="amd64" ;; + aarch64) host_arch="arm64" ;; + ppc64le) host_arch="ppc64le" ;; + esac + + pull_distro_image fedora 44 + + local default_container + default_container="fedora-toolbox-44-native" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "" \ + --distro fedora \ + --release 44 \ + --container "$default_container" + + assert_success + assert_line --index 0 "Created container: ${default_container}" + assert_line --index 1 "Enter with: toolbox enter ${default_container}" + assert [ ${#lines[@]} -eq 2 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run podman inspect \ + --format '{{index .Config.Labels "toolbox-arch"}}' \ + --type container \ + "${default_container}" + + assert_success + assert_output "${host_arch}" +} + +@test "create: Try with a case-sensitive architecture value" { + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch ARM64 \ + --distro fedora \ + --release 44 + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: architecture 'ARM64' is not supported by Toolbx" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "create: Try with conflicting --arch and image tag" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local conflicting_arch + if [ "${cross_arch}" = "arm64" ]; then + conflicting_arch="ppc64le" + else + conflicting_arch="arm64" + fi + + local tag_arch + tag_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${conflicting_arch}" \ + --image "fedora-toolbox:44-${tag_arch}" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: conflicting architecture specifications" + assert_line --index 1 "--arch=${conflicting_arch} but image tag specifies ${cross_arch}" + assert_line --index 2 "Run 'toolbox --help' for usage." + assert [ ${#stderr_lines[@]} -eq 3 ] +} + +@test "create: Try with --release containing architecture suffix" { + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --distro fedora \ + --release 44-aarch64 + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: invalid argument for '--release'" + assert_line --index 1 "The release must be a positive integer." + assert_line --index 2 "Run 'toolbox --help' for usage." + assert [ ${#stderr_lines[@]} -eq 3 ] +} + +@test "create: Try with --distro and --image together with --arch" { + local cross_arch + cross_arch="$(get_cross_arch)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --image "fedora-toolbox:44" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: options --distro and --image cannot be used together" + assert_line --index 1 "Run 'toolbox --help' for usage." + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "create: Try with --image and --release together with --arch" { + local cross_arch + cross_arch="$(get_cross_arch)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --image "fedora-toolbox:44" \ + --release 44 + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: options --image and --release cannot be used together" + assert_line --index 1 "Run 'toolbox --help' for usage." + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "create: Try creating duplicate cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + local container="fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_success + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --image "fedora-toolbox:44-${cross_arch}" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: container $container already exists" + assert_line --index 1 "Enter with: toolbox enter $container" + assert_line --index 2 "Run 'toolbox --help' for usage." + assert [ ${#stderr_lines[@]} -eq 3 ] +} + +@test "create: Try cross-arch without QEMU in PATH" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + local restricted_path="$BATS_TEST_TMPDIR/no-qemu-path" + build_restricted_path "$restricted_path" "qemu-*" + + if [ -e "$restricted_path/qemu-${binfmt_arch}-static" ] || [ -e "$restricted_path/qemu-${binfmt_arch}" ]; then + fail "qemu binaries were not excluded from restricted PATH" + fi + + run --keep-empty-lines --separate-stderr env PATH="$restricted_path" \ + "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: Cannot create container for architecture ${cross_arch}" + assert_line --index 1 "The host system does not have the required support: No ${cross_arch} statically linked QEMU emulator binary found" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "create: Try cross-arch with fake QEMU (shell script, not ELF)" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + local restricted_path="$BATS_TEST_TMPDIR/fake-qemu-path" + build_restricted_path "$restricted_path" "qemu-*" + + cat > "$restricted_path/qemu-${binfmt_arch}-static" <<'FAKE' +#!/bin/sh +echo "I am not a real QEMU" +FAKE + + chmod +x "$restricted_path/qemu-${binfmt_arch}-static" + + run --keep-empty-lines --separate-stderr env PATH="$restricted_path" \ + "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: Cannot create container for architecture ${cross_arch}" + assert_line --index 1 "The host system does not have the required support: No ${cross_arch} statically linked QEMU emulator binary found" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "create: Try cross-arch without skopeo in PATH" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local restricted_path="$BATS_TEST_TMPDIR/no-skopeo-path" + build_restricted_path "$restricted_path" "skopeo" + + if [ -e "$restricted_path/skopeo" ]; then + fail "skopeo was not excluded from restricted PATH" + fi + + run --keep-empty-lines --separate-stderr env PATH="$restricted_path" \ + "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: Cannot inspect image ${IMAGES[fedora]}:44 for architecture ${cross_arch}: skopeo is not installed." + assert_line --index 1 "Skopeo is required for creating non-native architecture containers." + assert [ ${#stderr_lines[@]} -eq 2 ] +} + diff --git a/test/system/304-run.bats b/test/system/304-run.bats new file mode 100644 index 000000000..da2f1c4d6 --- /dev/null +++ b/test/system/304-run.bats @@ -0,0 +1,604 @@ +# shellcheck shell=bats +# +# Copyright © 2019 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support +} + +setup() { + cleanup_all + pushd "$HOME" || return 1 +} + +teardown() { + popd || return 1 + cleanup_all +} + +@test "run: Smoke test with true(1) in cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Try with an unsupported --arch value" { + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch sparc64 \ + --distro fedora \ + --release 44 \ + true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: architecture 'sparc64' is not supported by Toolbx" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "run: Try with a missing command when --arch is specified" { + local cross_arch + cross_arch="$(get_cross_arch)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: missing argument for \"run\"" + assert_line --index 1 "Run 'toolbox --help' for usage." + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: Verify machine architecture with uname(1) in cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + uname -m + + assert_success + assert_line --index 0 "${binfmt_arch}" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Ensure that /run/.toolboxenv exists in cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + test -f /run/.toolboxenv + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Ensure that a specific cross-arch container is used with --container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local container="cross-arch-run-container" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "$container" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --container "$container" \ + true + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Ensure that a cross-arch container is reused on second run" { + local cross_arch + cross_arch="$(get_cross_arch)" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_success + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Ensure that the native container is used when --arch is omitted" { + create_default_container + + run --keep-empty-lines --separate-stderr "$TOOLBX" run true + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Ensure that binfmt_misc is mounted inside cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + test -d /proc/sys/fs/binfmt_misc + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Ensure that QEMU emulator is registered in binfmt_misc inside cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + sh -c "test -f /proc/sys/fs/binfmt_misc/qemu-${binfmt_arch} \ + || test -f /proc/sys/fs/binfmt_misc/qemu-${binfmt_arch}-static" + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Ensure that QEMU emulator registration is enabled inside cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + sh -c "cat /proc/sys/fs/binfmt_misc/qemu-${binfmt_arch} 2>/dev/null \ + || cat /proc/sys/fs/binfmt_misc/qemu-${binfmt_arch}-static 2>/dev/null" + + assert_success + assert_line --index 0 "enabled" + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Try a non-existent command inside a cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local cmd="nonexistent-command-xyz123" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run -127 --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + "$cmd" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "bash: line 1: exec: $cmd: not found" + assert_line --index 1 "Error: command $cmd not found in container fedora-toolbox-44-${cross_arch}" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: Exit code propagation — false(1) in cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run -1 --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + false + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Exit code propagation — 'exit 2' in cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run -2 --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + /bin/sh -c 'exit 2' + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Short entry point error propagates from cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + # shellcheck disable=SC2030 + export TOOLBX_FAIL_ENTRY_POINT=1 + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2031,SC2154 + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: TOOLBX_FAIL_ENTRY_POINT is set" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "run: Multi-line entry point error propagates from cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + # shellcheck disable=SC2030 + export TOOLBX_FAIL_ENTRY_POINT=2 + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2031,SC2154 + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: TOOLBX_FAIL_ENTRY_POINT is set" + assert_line --index 1 "This environment variable should only be set when testing." + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: Smoke test with 5s entry point delay in cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + # shellcheck disable=SC2030 + export TOOLBX_DELAY_ENTRY_POINT=5 + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2031,SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Stop and restart cross-arch container re-initializes correctly" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local container="fedora-toolbox-44-${cross_arch}" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "$container" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_success + + podman stop "$container" >/dev/null + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: 'sudo id' inside cross-arch container" { + local cross_arch + cross_arch="$(get_cross_arch)" + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + sudo id + + assert_success + assert_line --index 0 "uid=0(root) gid=0(root) groups=0(root)" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: binfmt arch name alias (aarch64) resolves same as OCI name (arm64)" { + local cross_arch + cross_arch="$(get_cross_arch)" + + if [ "${cross_arch}" != "arm64" ]; then + skip "alias test only applies when cross-arch is arm64" + fi + + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch aarch64 \ + --distro fedora \ + --release 44 \ + uname -m + + assert_success + assert_line --index 0 "aarch64" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Native and cross-arch containers coexist without interference" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + local host_binfmt_arch + host_binfmt_arch="$(uname -m)" + + create_default_container + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run uname -m + + assert_success + assert_line --index 0 "${host_binfmt_arch}" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + uname -m + + assert_success + assert_line --index 0 "${binfmt_arch}" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "run: Error when QEMU is missing from recorded path and from /usr/bin/ fallback" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + if [ -f "/usr/bin/qemu-${binfmt_arch}-static" ] || \ + [ -f "/usr/bin/qemu-${binfmt_arch}" ]; then + skip "QEMU is in /usr/bin/ — fallback would succeed, see the Warning test" + fi + + local real_qemu="" + for candidate in "qemu-${binfmt_arch}-static" "qemu-${binfmt_arch}"; do + if command -v "$candidate" >/dev/null 2>&1; then + real_qemu="$(command -v "$candidate")" + break + fi + done + + local restricted_path="$BATS_TEST_TMPDIR/fake-qemu-path" + build_restricted_path "$restricted_path" "qemu-*" + ln -s "$real_qemu" "$restricted_path/qemu-${binfmt_arch}-static" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + run --keep-empty-lines --separate-stderr env PATH="$restricted_path" \ + "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_success + + local expected_interp="/run/host${restricted_path}/qemu-${binfmt_arch}-static" + + rm "$restricted_path/qemu-${binfmt_arch}-static" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 \ + "Warning: QEMU emulator not found at expected path '${expected_interp}', using fallback at '/run/host/usr/bin/'" + assert_line --index 1 "Error: Cannot run container for architecture ${cross_arch}:" + assert_line --index 2 "The host system does not have the required support: No ${cross_arch} statically linked QEMU emulator binary found" + assert [ ${#stderr_lines[@]} -eq 3 ] +} + +@test "run: Warning when QEMU not at recorded path but found at /usr/bin/ fallback" { + local cross_arch + cross_arch="$(get_cross_arch)" + + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + if [ ! -f "/usr/bin/qemu-${binfmt_arch}-static" ] && \ + [ ! -f "/usr/bin/qemu-${binfmt_arch}" ]; then + skip "QEMU not in /usr/bin/ — fallback would fail, see the Error test" + fi + + local real_qemu="" + for candidate in "qemu-${binfmt_arch}-static" "qemu-${binfmt_arch}"; do + if command -v "$candidate" >/dev/null 2>&1; then + real_qemu="$(command -v "$candidate")" + break + fi + done + + local restricted_path="$BATS_TEST_TMPDIR/fake-qemu-path" + build_restricted_path "$restricted_path" "qemu-*" + ln -s "$real_qemu" "$restricted_path/qemu-${binfmt_arch}-static" + + pull_distro_image_cross_arch fedora 44 "${cross_arch}" + + # Create the container: the symlink's path is stored as --arch-emulator-path. + run --keep-empty-lines --separate-stderr env PATH="$restricted_path" \ + "$TOOLBX" --assumeyes create \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 + + assert_success + + local expected_interp="/run/host${restricted_path}/qemu-${binfmt_arch}-static" + + rm "$restricted_path/qemu-${binfmt_arch}-static" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + true + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + lines=("${stderr_lines[@]}") + assert_line --index 0 \ + "Warning: QEMU emulator not found at expected path '${expected_interp}', using fallback at '/run/host/usr/bin/'" + assert [ ${#stderr_lines[@]} -eq 1 ] +} From a22dee3016faaa026dac835b7771de0405dd69f9 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 14 May 2026 11:56:43 +0200 Subject: [PATCH 19/21] test/system: Add non-native architecture tests for runtime environment Add system tests that verify the runtime environment inside cross-architecture Toolbx containers matches the behavior of native ones. The 4xx numbering range mirrors the 2xx range for native runtime environment tests: 401 (IPC), 403 (network), 406 (user), 410 (ulimit), 411 (D-Bus), 420 (environment variables), 430 (CDI), 450 (Kerberos), and 470 (RPM). Each test creates a non-native container verifies that the corresponding host integration feature (namespace sharing, DNS, user mapping, resource limits, D-Bus session/system buses, environment variable forwarding, CDI device access, Kerberos configuration, and RPM macros) works correctly under QEMU emulation. All tests use the 'non-native' Bats file tag for selective execution. https://github.com/containers/toolbox/pull/1794 Signed-off-by: Dalibor Kricka --- test/system/401-ipc.bats | 72 + test/system/403-network.bats | 147 ++ test/system/406-user.bats | 144 ++ test/system/410-ulimit.bats | 523 +++++++ test/system/411-dbus.bats | 103 ++ test/system/420-environment-variables.bats | 139 ++ test/system/430-cdi.bats | 1534 ++++++++++++++++++++ test/system/450-kerberos.bats | 70 + test/system/470-rpm.bats | 74 + 9 files changed, 2806 insertions(+) create mode 100644 test/system/401-ipc.bats create mode 100644 test/system/403-network.bats create mode 100644 test/system/406-user.bats create mode 100644 test/system/410-ulimit.bats create mode 100644 test/system/411-dbus.bats create mode 100644 test/system/420-environment-variables.bats create mode 100644 test/system/430-cdi.bats create mode 100644 test/system/450-kerberos.bats create mode 100644 test/system/470-rpm.bats diff --git a/test/system/401-ipc.bats b/test/system/401-ipc.bats new file mode 100644 index 000000000..97ece514c --- /dev/null +++ b/test/system/401-ipc.bats @@ -0,0 +1,72 @@ +# shellcheck shell=bats +# +# Copyright © 2023 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support + cleanup_all + pushd "$HOME" || return 1 + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" +} + +teardown_file() { + popd || return 1 + cleanup_all +} + +@test "ipc: No namespace inside non-native container" { + local cross_arch + cross_arch="$(get_cross_arch)" + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "${cross_arch}")" + + local ns_host + ns_host=$(readlink /proc/$$/ns/ipc) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + sh -c 'readlink /proc/$$/ns/ipc' + + assert_success + assert_line --index 0 "$ns_host" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + # -- Verify the container runs under the expected non-native architecture + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "${cross_arch}" \ + --distro fedora \ + --release 44 \ + sh -c 'lscpu' + + assert_success + assert_line --index 0 --regexp "^Architecture:[[:space:]]+${binfmt_arch}$" + assert [ ${#stderr_lines[@]} -eq 0 ] +} diff --git a/test/system/403-network.bats b/test/system/403-network.bats new file mode 100644 index 000000000..8cccfefd8 --- /dev/null +++ b/test/system/403-network.bats @@ -0,0 +1,147 @@ +# shellcheck shell=bats +# +# Copyright © 2023 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +readonly RESOLVER_PYTHON3='\ +import socket; \ +import sys; \ +family = {"A": socket.AddressFamily.AF_INET, "AAAA": socket.AddressFamily.AF_INET6}; \ +addr = socket.getaddrinfo(sys.argv[2], None, family[sys.argv[1]], socket.SocketKind.SOCK_RAW)[0][4][0]; \ +print(addr)' + +# shellcheck disable=SC2016 +readonly RESOLVER_SH='resolvectl --legend false --no-pager --type "$0" query "$1" \ + | cut --delimiter " " --fields 4' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support + cleanup_all + pushd "$HOME" || return 1 + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" +} + +teardown_file() { + popd || return 1 + cleanup_all +} + +@test "network: No namespace inside non-native container" { + local ns_host + ns_host=$(readlink /proc/$$/ns/net) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 sh -c 'readlink /proc/$$/ns/net' + + assert_success + assert_line --index 0 "$ns_host" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + # -- Architecture check + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 sh -c 'lscpu' + + assert_success + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "$(get_cross_arch)")" + assert_line --index 0 --regexp "^Architecture:[[:space:]]+${binfmt_arch}$" + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "network: /etc/resolv.conf inside non-native container" { + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /etc/resolv.conf + + assert_success + + if [ "${lines[0]}" = "/run/host/run/systemd/resolve/stub-resolv.conf" ]; then + skip "host has absolute symlink" + else + assert_line --index 0 "/run/host/etc/resolv.conf" + fi + + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "network: DNS inside non-native container" { + local ipv4_skip=false + local ipv4_addr + if ! ipv4_addr="$(python3 -c "$RESOLVER_PYTHON3" A k.root-servers.net)"; then + ipv4_skip=true + fi + + local ipv6_skip=false + local ipv6_addr + if ! ipv6_addr="$(python3 -c "$RESOLVER_PYTHON3" AAAA k.root-servers.net)"; then + ipv6_skip=true + fi + + if $ipv4_skip && $ipv6_skip; then + skip "DNS not working on host" + fi + + if ! $ipv4_skip; then + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + python3 -c "$RESOLVER_PYTHON3" A k.root-servers.net + + assert_success + assert_line --index 0 "$ipv4_addr" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + fi + + if ! $ipv6_skip; then + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + python3 -c "$RESOLVER_PYTHON3" AAAA k.root-servers.net + + assert_success + assert_line --index 0 "$ipv6_addr" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + fi +} + +@test "network: ping(8) inside non-native container" { + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 ping -c 2 f.root-servers.net + + if [ "$status" -eq 1 ]; then + skip "lost packets" + fi + + assert_success + assert [ ${#lines[@]} -gt 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} diff --git a/test/system/406-user.bats b/test/system/406-user.bats new file mode 100644 index 000000000..30313828d --- /dev/null +++ b/test/system/406-user.bats @@ -0,0 +1,144 @@ +# shellcheck shell=bats +# +# Copyright © 2023 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support + cleanup_all + pushd "$HOME" || return 1 + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" +} + +teardown_file() { + popd || return 1 + cleanup_all +} + +@test "user: Separate namespace inside non-native container" { + local ns_host + ns_host=$(readlink /proc/$$/ns/user) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 sh -c 'readlink /proc/$$/ns/user' + + assert_success + assert_line --index 0 --regexp '^user:\[[[:digit:]]+\]$' + refute_line --index 0 "$ns_host" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + # -- Architecture check + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 sh -c 'lscpu' + + assert_success + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "$(get_cross_arch)")" + assert_line --index 0 --regexp "^Architecture:[[:space:]]+${binfmt_arch}$" + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "user: root in shadow(5) inside non-native container" { + container_root_file_system="$(podman unshare podman mount fedora-toolbox-44-$(get_cross_arch))" + + "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + run --keep-empty-lines --separate-stderr podman unshare cat "$container_root_file_system/etc/shadow" + podman unshare podman unmount fedora-toolbox-44-$(get_cross_arch) + + assert_success + assert_line --regexp '^root::.+$' + assert [ ${#lines[@]} -gt 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "user: $USER in passwd(5) inside non-native container" { + local user_gecos + user_gecos="$(getent passwd "$USER" | cut --delimiter : --fields 5)" + + local user_id_real + user_id_real="$(id --real --user)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 cat /etc/passwd + + assert_success + assert_line --regexp "^$USER::$user_id_real:$user_id_real:$user_gecos:$HOME:$SHELL$" + assert [ ${#lines[@]} -gt 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "user: $USER in shadow(5) inside non-native container" { + container_root_file_system="$(podman unshare podman mount fedora-toolbox-44-$(get_cross_arch))" + + "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + run --keep-empty-lines --separate-stderr podman unshare cat "$container_root_file_system/etc/shadow" + podman unshare podman unmount fedora-toolbox-44-$(get_cross_arch) + + assert_success + refute_line --regexp "^$USER:.*$" + assert [ ${#lines[@]} -gt 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "user: $USER in group(5) inside non-native container" { + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 cat /etc/group + + assert_success + assert_line --regexp "^$USER:x:[[:digit:]]+:$USER$" + assert_line --regexp "^wheel:x:[[:digit:]]+:$USER$" + assert [ ${#lines[@]} -gt 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "user: id(1) for $USER inside non-native container" { + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 id + + assert_success + assert [ ${#lines[@]} -eq 1 ] + + local output_id="${lines[0]}" + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 id "$USER" + + assert_success + assert_line --index 0 "$output_id" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} \ No newline at end of file diff --git a/test/system/410-ulimit.bats b/test/system/410-ulimit.bats new file mode 100644 index 000000000..04a16fd26 --- /dev/null +++ b/test/system/410-ulimit.bats @@ -0,0 +1,523 @@ +# shellcheck shell=bats +# +# Copyright © 2023 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support + cleanup_all + pushd "$HOME" || return 1 + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" +} + +teardown_file() { + popd || return 1 + cleanup_all +} + +@test "ulimit: Real-time non-blocking time (hard) inside non-native container" { + local limit + limit=$(ulimit -H -R) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -R' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + # -- Architecture check + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 sh -c 'lscpu' + + assert_success + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "$(get_cross_arch)")" + assert_line --index 0 --regexp "^Architecture:[[:space:]]+${binfmt_arch}$" + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Real-time non-blocking time (soft) inside non-native container" { + local limit + limit=$(ulimit -S -R) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -R' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Core file size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -c) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -c' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Core file size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -c) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -c' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Data segment size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -d) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -d' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Data segment size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -d) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -d' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Scheduling priority (hard) inside non-native container" { + local limit + limit=$(ulimit -H -e) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -e' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Scheduling priority (soft) inside non-native container" { + local limit + limit=$(ulimit -S -e) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -e' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: File size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -f) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -f' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: File size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -f) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -f' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of pending signals (hard) inside non-native container" { + local limit + limit=$(ulimit -H -i) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -i' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of pending signals (soft) inside non-native container" { + local limit + limit=$(ulimit -S -i) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -i' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Locked memory size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -l) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -l' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Locked memory size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -l) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -l' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Resident memory size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -m) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -m' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Resident memory size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -m) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -m' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of open files (hard) inside non-native container" { + local limit + limit=$(ulimit -H -n) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -n' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of open files (soft) inside non-native container" { + local limit + limit=$(ulimit -H -n) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -n' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Pipe size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -p) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -p' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Pipe size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -p) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -p' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: POSIX message queue size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -q) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -q' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: POSIX message queue size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -q) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -q' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Real-time scheduling priority (hard) inside non-native container" { + local limit + limit=$(ulimit -H -r) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -r' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Real-time scheduling priority (soft) inside non-native container" { + local limit + limit=$(ulimit -S -r) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -r' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Stack size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -s) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -s' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Stack size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -s) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -s' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: CPU time (hard) inside non-native container" { + local limit + limit=$(ulimit -H -t) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -t' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: CPU time (soft) inside non-native container" { + local limit + limit=$(ulimit -S -t) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -t' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of user processes (hard) inside non-native container" { + local limit + limit=$(ulimit -H -u) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -u' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of user processes (soft) inside non-native container" { + local limit + limit=$(ulimit -S -u) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -u' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Virtual memory size (hard) inside non-native container" { + local limit + limit=$(ulimit -H -v) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -v' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Virtual memory size (soft) inside non-native container" { + local limit + limit=$(ulimit -S -v) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -v' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of file locks (hard) inside non-native container" { + local limit + limit=$(ulimit -H -x) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -H -x' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "ulimit: Number of file locks (soft) inside non-native container" { + local limit + limit=$(ulimit -S -x) + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'ulimit -S -x' + + assert_success + assert_line --index 0 "$limit" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} \ No newline at end of file diff --git a/test/system/411-dbus.bats b/test/system/411-dbus.bats new file mode 100644 index 000000000..8515e30e8 --- /dev/null +++ b/test/system/411-dbus.bats @@ -0,0 +1,103 @@ +# shellcheck shell=bats +# +# Copyright © 2023 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support + cleanup_all + pushd "$HOME" || return 1 + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" +} + +teardown_file() { + popd || return 1 + cleanup_all +} + +@test "dbus: Session bus inside non-native container" { + local expected_response + expected_response="$(gdbus call \ + --session \ + --dest org.freedesktop.DBus \ + --object-path /org/freedesktop/DBus \ + --method org.freedesktop.DBus.Peer.Ping)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + gdbus call \ + --session \ + --dest org.freedesktop.DBus \ + --object-path /org/freedesktop/DBus \ + --method org.freedesktop.DBus.Peer.Ping + + assert_success + assert_line --index 0 "$expected_response" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + # -- Architecture check + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 sh -c 'lscpu' + + assert_success + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "$(get_cross_arch)")" + assert_line --index 0 --regexp "^Architecture:[[:space:]]+${binfmt_arch}$" + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "dbus: System bus inside non-native container" { + local expected_response + expected_response="$(gdbus call \ + --system \ + --dest org.freedesktop.systemd1 \ + --object-path /org/freedesktop/systemd1 \ + --method org.freedesktop.DBus.Properties.Get \ + org.freedesktop.systemd1.Manager \ + Version)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + gdbus call \ + --system \ + --dest org.freedesktop.systemd1 \ + --object-path /org/freedesktop/systemd1 \ + --method org.freedesktop.DBus.Properties.Get \ + org.freedesktop.systemd1.Manager \ + Version + + assert_success + assert_line --index 0 "$expected_response" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} \ No newline at end of file diff --git a/test/system/420-environment-variables.bats b/test/system/420-environment-variables.bats new file mode 100644 index 000000000..4e455f3e5 --- /dev/null +++ b/test/system/420-environment-variables.bats @@ -0,0 +1,139 @@ +# shellcheck shell=bats +# +# Copyright © 2023 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup_file() { + bats_require_minimum_version 1.10.0 + skip_if_no_cross_arch_support + cleanup_all + pushd "$HOME" || return 1 + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" +} + +teardown_file() { + popd || return 1 + cleanup_all +} + +@test "environment variables: HISTFILESIZE inside non-native container" { + # shellcheck disable=SC2031 + if [ "$HISTFILESIZE" = "" ]; then + # shellcheck disable=SC2030 + HISTFILESIZE=1001 + else + ((HISTFILESIZE++)) + fi + + export HISTFILESIZE + + # shellcheck disable=SC2016 + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'echo "$HISTFILESIZE"' + + assert_success + assert_line --index 0 "$HISTFILESIZE" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + # -- Architecture check + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 sh -c 'lscpu' + + assert_success + local binfmt_arch + binfmt_arch="$(oci_arch_to_binfmt "$(get_cross_arch)")" + assert_line --index 0 --regexp "^Architecture:[[:space:]]+${binfmt_arch}$" + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "environment variables: HISTSIZE inside non-native container" { + skip "https://pagure.io/setup/pull-request/48" + + # shellcheck disable=SC2031 + if [ "$HISTSIZE" = "" ]; then + # shellcheck disable=SC2030 + HISTSIZE=1001 + else + ((HISTSIZE++)) + fi + + export HISTSIZE + + # shellcheck disable=SC2016 + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'echo "$HISTSIZE"' + + assert_success + assert_line --index 0 "$HISTSIZE" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "environment variables: HOSTNAME inside non-native container" { + # shellcheck disable=SC2016 + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'echo "$HOSTNAME"' + + assert_success + assert_line --index 0 "toolbx" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "environment variables: KONSOLE_VERSION inside non-native container" { + # shellcheck disable=SC2031 + if [ "$KONSOLE_VERSION" = "" ]; then + # shellcheck disable=SC2030 + export KONSOLE_VERSION=230804 + fi + + # shellcheck disable=SC2016 + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'echo "$KONSOLE_VERSION"' + + assert_success + assert_line --index 0 "$KONSOLE_VERSION" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "environment variables: XTERM_VERSION inside non-native container" { + # shellcheck disable=SC2031 + if [ "$XTERM_VERSION" = "" ]; then + # shellcheck disable=SC2030 + export XTERM_VERSION="XTerm(385)" + fi + + # shellcheck disable=SC2016 + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 bash -c 'echo "$XTERM_VERSION"' + + assert_success + assert_line --index 0 "$XTERM_VERSION" + assert [ ${#lines[@]} -eq 1 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] +} diff --git a/test/system/430-cdi.bats b/test/system/430-cdi.bats new file mode 100644 index 000000000..c3ea1224e --- /dev/null +++ b/test/system/430-cdi.bats @@ -0,0 +1,1534 @@ +# shellcheck shell=bats +# +# Copyright © 2024 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup() { + bats_require_minimum_version 1.10.0 + cleanup_all + pushd "$HOME" || return 1 + skip_if_no_cross_arch_support + + rm --force "$XDG_RUNTIME_DIR/toolbox/cdi-nvidia.json" || return 1 +} + +teardown() { + rm --force "$XDG_RUNTIME_DIR/toolbox/cdi-nvidia.json" || return 1 + + popd || return 1 + cleanup_all +} + +@test "cdi: Smoke test inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-empty.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-empty.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + + # shellcheck disable=SC2154 + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test /etc/ld.so.cache -ot "$toolbx_runtime_directory/cdi-nvidia.json" + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test -e /etc/ld.so.conf.d/toolbx-nvidia.conf + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with no link inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-00.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (absolute target, different parent) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-01.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (absolute target, different parent, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-01.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (absolute target, missing parent) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-02.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (absolute target, missing parent, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-02.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (absolute target, same parent) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-03.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (absolute target, same parent, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-03.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with three links (absolute targets, mixed parents) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-04.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with three links (absolute targets, mixed parents, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-04.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "/usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (relative target, different parent) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-05.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (relative target, different parent, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-05.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (relative target, missing parent) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-06.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "../../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (relative target, missing parent, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-06.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "../../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "../../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (relative target, same parent) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-07.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with one link (relative target, same parent, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-07.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with three links (relative targets, mixed parents) inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-08.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "../../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: create-symlinks with three links (relative targets, mixed parents, restart) inside non-native container" { + local container_name + container_name="fedora-toolbox-44-$(get_cross_arch)" + + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-08.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "../../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + podman stop "$container_name" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /run/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /run/toolbox.1 + + assert_success + assert_line --index 0 "../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /opt/bin/toolbox + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /opt/bin/toolbox + + assert_success + assert_line --index 0 "../../usr/bin/toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 readlink /usr/bin/toolbox.1 + + assert_success + assert_line --index 0 "toolbox" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: ldconfig(8) with no folder inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-00.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" --distro fedora --release 44 test "$toolbx_runtime_directory/cdi-nvidia.json" -ot /etc/ld.so.cache + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-00.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /etc/ld.so.conf.d/toolbx-nvidia.conf + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: ldconfig(8) with one folder inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-01.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" --distro fedora --release 44 test "$toolbx_runtime_directory/cdi-nvidia.json" -ot /etc/ld.so.cache + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-01.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" --distro fedora --release 44 cat /etc/ld.so.conf.d/toolbx-nvidia.conf + + assert_success + assert_line --index 0 "# Written by Toolbx" + assert_line --index 1 "# https://containertoolbx.org/" + assert_line --index 2 "" + assert_line --index 3 "/usr/lib64" + assert [ ${#lines[@]} -eq 4 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: ldconfig(8) with two folders inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-02.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" --distro fedora --release 44 test "$toolbx_runtime_directory/cdi-nvidia.json" -ot /etc/ld.so.cache + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-02.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" --distro fedora --release 44 cat /etc/ld.so.conf.d/toolbx-nvidia.conf + + assert_success + assert_line --index 0 "# Written by Toolbx" + assert_line --index 1 "# https://containertoolbx.org/" + assert_line --index 2 "" + assert_line --index 3 "/usr/lib" + assert_line --index 4 "/usr/lib64" + assert [ ${#lines[@]} -eq 5 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: Try invalid JSON inside non-native container" { + local invalid_json="This is not JSON" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + echo "$invalid_json" >"$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if grep --invert-match --quiet --no-messages "^$invalid_json$" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to load Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try an empty file inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + touch "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if [ -s "$toolbx_runtime_directory/cdi-nvidia.json" ]; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to load Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try create-symlinks with invalid path inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-30.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: invalid hook in Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try create-symlinks with unknown path inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-31.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: Try create-symlinks with missing --link argument inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-32.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to create symlinks for Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try create-symlinks with relative link in --link argument inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-33.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to create symlinks for Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try create-symlinks with wrongly formatted --link argument ('foo') inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-34.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to create symlinks for Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try create-symlinks with wrongly formatted --link argument ('foo::bar::baz') inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-35.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: failed to create symlinks for Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try create-symlinks with invalid name inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-36.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: invalid hook in Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try create-symlinks with unknown name inside non-native container" { + local test_cdi_file="$BATS_TEST_DIRNAME/data/cdi-hooks-create-symlinks-37.json" + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 test -e /usr/bin/toolbox.1 + + if ! cmp --silent "$test_cdi_file" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: Try hook with invalid path inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-10.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-10.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: invalid hook in Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try hook with unknown path inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-11.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test /etc/ld.so.cache -ot "$toolbx_runtime_directory/cdi-nvidia.json" + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-11.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test -e /etc/ld.so.conf.d/toolbx-nvidia.conf + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: Try hook with unknown args inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-12.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test /etc/ld.so.cache -ot "$toolbx_runtime_directory/cdi-nvidia.json" + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-12.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test -e /etc/ld.so.conf.d/toolbx-nvidia.conf + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: Try hook with invalid name inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-14.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-14.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: invalid hook in Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try hook with unknown name inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-hooks-15.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test /etc/ld.so.cache -ot "$toolbx_runtime_directory/cdi-nvidia.json" + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-hooks-15.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test -e /etc/ld.so.conf.d/toolbx-nvidia.conf + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} + +@test "cdi: Try mount with invalid container path inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-mounts-10.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-mounts-10.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: invalid mount in Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try mount with invalid host path inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-mounts-11.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-mounts-11.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + lines=("${stderr_lines[@]}") + assert_line --index 0 "Error: invalid mount in Container Device Interface for NVIDIA" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "cdi: Try mount with non-existent paths inside non-native container" { + local toolbx_runtime_directory="$XDG_RUNTIME_DIR/toolbox" + + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + # shellcheck disable=SC2174 + mkdir --mode 700 --parents "$toolbx_runtime_directory" + + cp "$BATS_TEST_DIRNAME/data/cdi-mounts-12.json" "$toolbx_runtime_directory/cdi-nvidia.json" + chmod 644 "$toolbx_runtime_directory/cdi-nvidia.json" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 true + + if ! cmp --silent "$BATS_TEST_DIRNAME/data/cdi-mounts-12.json" "$toolbx_runtime_directory/cdi-nvidia.json"; then + skip "found NVIDIA hardware" + fi + + assert_success + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + test -e /non/existent/path + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} diff --git a/test/system/450-kerberos.bats b/test/system/450-kerberos.bats new file mode 100644 index 000000000..d38e9ef33 --- /dev/null +++ b/test/system/450-kerberos.bats @@ -0,0 +1,70 @@ +# shellcheck shell=bats +# +# Copyright © 2025 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup() { + bats_require_minimum_version 1.10.0 + cleanup_all + pushd "$HOME" || return 1 + skip_if_no_cross_arch_support +} + +teardown() { + popd || return 1 + cleanup_all +} + +@test "kerberos: Smoke test with non-native container" { + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + cat /etc/krb5.conf.d/kcm_default_ccache + + assert_success + assert_line --index 0 "# Written by Toolbx" + assert_line --index 1 "# https://containertoolbx.org/" + assert_line --index 2 "#" + assert_line --index 3 "# # To disable the KCM credential cache, comment out the following lines." + assert_line --index 4 "" + assert_line --index 5 "[libdefaults]" + assert_line --index 6 " default_ccache_name = KCM:" + assert [ ${#lines[@]} -eq 7 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + stat \ + --format "%A %U:%G" \ + /etc/krb5.conf.d/kcm_default_ccache + + assert_success + assert_line --index 0 "-rw-r--r-- root:root" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} \ No newline at end of file diff --git a/test/system/470-rpm.bats b/test/system/470-rpm.bats new file mode 100644 index 000000000..f0d633e08 --- /dev/null +++ b/test/system/470-rpm.bats @@ -0,0 +1,74 @@ +# shellcheck shell=bats +# +# Copyright © 2025 – 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# bats file_tags=non-native + +load 'libs/bats-support/load' +load 'libs/bats-assert/load' +load 'libs/helpers' + +setup() { + bats_require_minimum_version 1.10.0 + cleanup_all + pushd "$HOME" || return 1 + skip_if_no_cross_arch_support +} + +teardown() { + popd || return 1 + cleanup_all +} + +@test "rpm: %_netsharedpath inside non-native container" { + local cross_arch + cross_arch="$(get_cross_arch)" + create_distro_container_cross_arch fedora 44 "${cross_arch}" "fedora-toolbox-44-${cross_arch}" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --arch "$(get_cross_arch)" --distro fedora --release 44 rpm --eval %_netsharedpath + + assert_success + assert_line --index 0 "/dev:/media:/mnt:/proc:/sys:/tmp:/var/lib/flatpak:/var/lib/libvirt" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + cat /usr/lib/rpm/macros.d/macros.toolbox + + assert_success + assert_line --index 0 "# Written by Toolbx" + assert_line --index 1 "# https://containertoolbx.org/" + assert_line --index 2 "" + assert_line --index 3 "%_netsharedpath /dev:/media:/mnt:/proc:/sys:/tmp:/var/lib/flatpak:/var/lib/libvirt" + assert [ ${#lines[@]} -eq 4 ] + assert [ ${#stderr_lines[@]} -eq 0 ] + + run --keep-empty-lines --separate-stderr "$TOOLBX" run \ + --arch "$(get_cross_arch)" \ + --distro fedora \ + --release 44 \ + stat \ + --format "%A %U:%G" \ + /usr/lib/rpm/macros.d/macros.toolbox + + assert_success + assert_line --index 0 "-rw-r--r-- root:root" + assert [ ${#lines[@]} -eq 1 ] + assert [ ${#stderr_lines[@]} -eq 0 ] +} From a8a60ef04138c0f29c7e40c771c586751f468639 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 14 May 2026 12:02:50 +0200 Subject: [PATCH 20/21] .zuul, playbooks, .github/workflows: Add CI for non-native architecture tests Add CI infrastructure to run the non-native architecture system tests on Zuul and GitHub Actions. Add qemu-user-static to the Fedora dependency playbook so that QEMU user-mode emulation is available on CI nodes. Add the system-test-non-native.yaml playbook that runs 'bats --filter-tags non-native' with the TOOLBX_TEST_SYSTEM_TAGS environment variable set to 'non-native' (3xx and 4xx non-native test files), following the same pattern used by the existing playbooks for commands-options and runtime-environment test groups [1]. Add non-native-architecture jobs for all Fedora Rawhide, Fedora 43 and Fedora 42. Add the 3xx and 4xx non-native test files to the GitHub Actions Ubuntu workflow. [1] https://github.com/containers/toolbox/commit/987f5e259289b4b3 https://github.com/containers/toolbox/pull/1794 Signed-off-by: Dalibor Kricka --- .github/workflows/ubuntu-tests.yaml | 9 +++++ .zuul.yaml | 56 +++++++++++++++++++++++++++ playbooks/dependencies-fedora.yaml | 1 + playbooks/system-test-non-native.yaml | 29 ++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 playbooks/system-test-non-native.yaml diff --git a/.github/workflows/ubuntu-tests.yaml b/.github/workflows/ubuntu-tests.yaml index 90d7761c7..5fc4f4775 100644 --- a/.github/workflows/ubuntu-tests.yaml +++ b/.github/workflows/ubuntu-tests.yaml @@ -50,6 +50,7 @@ jobs: ninja-build \ openssl \ podman \ + qemu-user-static \ shellcheck \ skopeo \ systemd \ @@ -166,6 +167,14 @@ jobs: test/system/230-cdi.bats \ test/system/250-kerberos.bats \ test/system/270-rpm.bats \ + test/system/301-create.bats \ + test/system/401-ipc.bats \ + test/system/403-network.bats \ + test/system/410-ulimit.bats \ + test/system/420-environment-variables.bats \ + test/system/430-cdi.bats \ + test/system/450-kerberos.bats \ + test/system/470-rpm.bats \ test/system/501-create.bats \ test/system/505-enter.bats env: diff --git a/.zuul.yaml b/.zuul.yaml index 9cee90813..43f8aa32e 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -48,6 +48,17 @@ pre-run: playbooks/setup-env-restricted.yaml run: playbooks/unit-test.yaml +- job: + name: system-test-fedora-rawhide-non-native-architecture + description: Run Toolbx's non-native architecture system tests in Fedora Rawhide + timeout: 7200 + nodeset: + nodes: + - name: fedora-rawhide + label: cloud-fedora-rawhide + pre-run: playbooks/setup-env.yaml + run: playbooks/system-test-non-native.yaml + - job: name: system-test-fedora-rawhide-commands-options description: Run Toolbx's commands-options system tests in Fedora Rawhide @@ -81,6 +92,17 @@ pre-run: playbooks/setup-env.yaml run: playbooks/system-test-runtime-environment-ubuntu.yaml +- job: + name: system-test-fedora-44-non-native-architecture + description: Run Toolbx's non-native architecture system tests in Fedora 44 + timeout: 6300 + nodeset: + nodes: + - name: fedora-44 + label: cloud-fedora-44 + pre-run: playbooks/setup-env.yaml + run: playbooks/system-test-non-native.yaml + - job: name: system-test-fedora-44-commands-options description: Run Toolbx's commands-options system tests in Fedora 44 @@ -114,6 +136,17 @@ pre-run: playbooks/setup-env.yaml run: playbooks/system-test-runtime-environment-ubuntu.yaml +- job: + name: system-test-fedora-43-non-native-architecture + description: Run Toolbx's non-native architecture system tests in Fedora 43 + timeout: 6300 + nodeset: + nodes: + - name: fedora-43 + label: cloud-fedora-43 + pre-run: playbooks/setup-env.yaml + run: playbooks/system-test-non-native.yaml + - job: name: system-test-fedora-43-commands-options description: Run Toolbx's commands-options system tests in Fedora 43 @@ -147,6 +180,17 @@ pre-run: playbooks/setup-env.yaml run: playbooks/system-test-runtime-environment-ubuntu.yaml +- job: + name: system-test-fedora-42-non-native-architecture + description: Run Toolbx's non-native architecture system tests in Fedora 42 + timeout: 6300 + nodeset: + nodes: + - name: fedora-42 + label: cloud-fedora-42 + pre-run: playbooks/setup-env.yaml + run: playbooks/system-test-non-native.yaml + - job: name: system-test-fedora-42-commands-options description: Run Toolbx's commands-options system tests in Fedora 42 @@ -184,15 +228,19 @@ periodic: jobs: - system-test-fedora-rawhide-commands-options + - system-test-fedora-rawhide-non-native-architecture - system-test-fedora-rawhide-runtime-environment-arch-fedora - system-test-fedora-rawhide-runtime-environment-ubuntu - system-test-fedora-44-commands-options + - system-test-fedora-44-non-native-architecture - system-test-fedora-44-runtime-environment-arch-fedora - system-test-fedora-44-runtime-environment-ubuntu - system-test-fedora-43-commands-options + - system-test-fedora-43-non-native-architecture - system-test-fedora-43-runtime-environment-arch-fedora - system-test-fedora-43-runtime-environment-ubuntu - system-test-fedora-42-commands-options + - system-test-fedora-42-non-native-architecture - system-test-fedora-42-runtime-environment-arch-fedora - system-test-fedora-42-runtime-environment-ubuntu check: @@ -201,15 +249,19 @@ - unit-test-migration-path-for-coreos-toolbox - unit-test-restricted - system-test-fedora-rawhide-commands-options + - system-test-fedora-rawhide-non-native-architecture - system-test-fedora-rawhide-runtime-environment-arch-fedora - system-test-fedora-rawhide-runtime-environment-ubuntu - system-test-fedora-44-commands-options + - system-test-fedora-44-non-native-architecture - system-test-fedora-44-runtime-environment-arch-fedora - system-test-fedora-44-runtime-environment-ubuntu - system-test-fedora-43-commands-options + - system-test-fedora-43-non-native-architecture - system-test-fedora-43-runtime-environment-arch-fedora - system-test-fedora-43-runtime-environment-ubuntu - system-test-fedora-42-commands-options + - system-test-fedora-42-non-native-architecture - system-test-fedora-42-runtime-environment-arch-fedora - system-test-fedora-42-runtime-environment-ubuntu gate: @@ -218,14 +270,18 @@ - unit-test-migration-path-for-coreos-toolbox - unit-test-restricted - system-test-fedora-rawhide-commands-options + - system-test-fedora-rawhide-non-native-architecture - system-test-fedora-rawhide-runtime-environment-arch-fedora - system-test-fedora-rawhide-runtime-environment-ubuntu - system-test-fedora-44-commands-options + - system-test-fedora-44-non-native-architecture - system-test-fedora-44-runtime-environment-arch-fedora - system-test-fedora-44-runtime-environment-ubuntu - system-test-fedora-43-commands-options + - system-test-fedora-43-non-native-architecture - system-test-fedora-43-runtime-environment-arch-fedora - system-test-fedora-43-runtime-environment-ubuntu - system-test-fedora-42-commands-options + - system-test-fedora-42-non-native-architecture - system-test-fedora-42-runtime-environment-arch-fedora - system-test-fedora-42-runtime-environment-ubuntu diff --git a/playbooks/dependencies-fedora.yaml b/playbooks/dependencies-fedora.yaml index 069c87cfd..ae6f5f34c 100644 --- a/playbooks/dependencies-fedora.yaml +++ b/playbooks/dependencies-fedora.yaml @@ -33,6 +33,7 @@ - openssl - pkgconfig(bash-completion) - podman + - qemu-user-static - shadow-utils-subid-devel - skopeo - systemd diff --git a/playbooks/system-test-non-native.yaml b/playbooks/system-test-non-native.yaml new file mode 100644 index 000000000..3674796f3 --- /dev/null +++ b/playbooks/system-test-non-native.yaml @@ -0,0 +1,29 @@ +# +# Copyright © 2026 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +--- +- hosts: all + tasks: + - include_tasks: build.yaml + + - name: Run the non-native architecture system tests + command: bats --filter-tags non-native ./test/system + environment: + TMPDIR: '/var/tmp' + TOOLBX: '/usr/local/bin/toolbox' + TOOLBX_TEST_SYSTEM_TAGS: 'non-native' + args: + chdir: '{{ zuul.project.src_dir }}' From d10e9eb74cf1af774ae7a6ce70ecd7601f56c570 Mon Sep 17 00:00:00 2001 From: Dalibor Kricka Date: Thu, 14 May 2026 14:36:15 +0200 Subject: [PATCH 21/21] doc: Document cross-architecture container support extension Add the --arch flag to the manual pages for create, enter and run. Update the init-container manual page with the --arch and --arch-emulator-path flags, and add a description of the QEMU user-mode emulation setup performed during container initialization. Add a new "Cross-architecture containers support" section to the main toolbox(1) manual page, describing the feature and listing the supported architectures. https://github.com/containers/toolbox/pull/1794 Signed-off-by: Dalibor Kricka --- doc/toolbox-create.1.md | 15 ++++++++++++++- doc/toolbox-enter.1.md | 15 ++++++++++++++- doc/toolbox-init-container.1.md | 21 ++++++++++++++++++++- doc/toolbox-run.1.md | 15 ++++++++++++++- doc/toolbox.1.md | 15 +++++++++++++++ 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/doc/toolbox-create.1.md b/doc/toolbox-create.1.md index f338dd824..6d944ceb4 100644 --- a/doc/toolbox-create.1.md +++ b/doc/toolbox-create.1.md @@ -4,7 +4,8 @@ toolbox\-create - Create a new Toolbx container ## SYNOPSIS -**toolbox create** [*--authfile FILE*] +**toolbox create** [*--arch ARCHITECTURE* | *-a ARCHITECTURE*] + [*--authfile FILE*] [*--distro DISTRO* | *-d DISTRO*] [*--image NAME* | *-i NAME*] [*--release RELEASE* | *-r RELEASE*] @@ -90,6 +91,12 @@ confusion. ## OPTIONS ## +**--arch** ARCHITECTURE, **-a** ARCHITECTURE + +Create a Toolbx container for a different architecture ARCHITECTURE than +the host (e.g., `arm64` on `amd64`). Can be combined with `--distro` and +`--release`. + **--authfile** FILE Path to a FILE with credentials for authenticating to the registry for private @@ -140,6 +147,12 @@ $ toolbox create --distro fedora --release f36 $ toolbox create --image bar foo ``` +### Create a Toolbx container for the arm64 architecture + +``` +$ toolbox create --arch arm64 +``` + ### Create a custom Toolbx container from a custom image that's private ``` diff --git a/doc/toolbox-enter.1.md b/doc/toolbox-enter.1.md index 1744f09a0..f67614bd4 100644 --- a/doc/toolbox-enter.1.md +++ b/doc/toolbox-enter.1.md @@ -4,7 +4,8 @@ toolbox\-enter - Enter a Toolbx container for interactive use ## SYNOPSIS -**toolbox enter** [*--distro DISTRO* | *-d DISTRO*] +**toolbox enter** [*--arch ARCHITECTURE* | *-a ARCHITECTURE*] + [*--distro DISTRO* | *-d DISTRO*] [*--release RELEASE* | *-r RELEASE*] [*CONTAINER*] @@ -29,6 +30,12 @@ analogous to a `podman start` followed by a `podman exec`. The following options are understood: +**--arch** ARCHITECTURE, **-a** ARCHITECTURE + +Enter a Toolbx container for a different architecture ARCHITECTURE than the +host (e.g., `arm64` on `amd64`). Can be combined with `--distro` and +`--release`. + **--distro** DISTRO, **-d** DISTRO Enter a Toolbx container for a different operating system DISTRO than the host. @@ -59,6 +66,12 @@ $ toolbox enter --distro fedora --release f36 $ toolbox enter foo ``` +### Enter the default Toolbx container based on the arm64 architecture + +``` +$ toolbox enter --arch arm64 +``` + ## SEE ALSO `toolbox(1)`, `toolbox-run(1)`, `podman(1)`, `podman-exec(1)`, diff --git a/doc/toolbox-init-container.1.md b/doc/toolbox-init-container.1.md index 03bdd2f62..521f240ee 100644 --- a/doc/toolbox-init-container.1.md +++ b/doc/toolbox-init-container.1.md @@ -4,7 +4,9 @@ toolbox\-init\-container - Initialize a running container ## SYNOPSIS -**toolbox init-container** *--gid GID* +**toolbox init-container** *--arch ID* + *--arch-emulator-path PATH* + *--gid GID* *--home HOME* *--home-link* *--media-link* @@ -48,10 +50,27 @@ On some host operating systems, important paths like `/home`, `/media` or paths inside the container match those on the host, to avoid needless confusion. +When the container's architecture differs from the host, the entry point +configures QEMU user-mode emulation inside the container. It validates that +QEMU emulation is functional, mounts a sandboxed `binfmt_misc` filesystem, and +registers the QEMU interpreter with the `C` (credential) flag for transparent +emulation of non-native architecture binaries. + ## OPTIONS ## The following options are understood: +**--arch** ID + +The container's architecture ID. When it differs from the host, additional +configuration of cross-architecture QEMU emulation is performed during +initialization. + +**--arch-emulator-path** PATH + +Register an emulator using binfmt_misc with PATH as the interpreter for a +non-native architecture container. + **--gid** GID Pass GID as the user's numerical group ID from the host to the Toolbx diff --git a/doc/toolbox-run.1.md b/doc/toolbox-run.1.md index bfcceeff0..6bbdb05e3 100644 --- a/doc/toolbox-run.1.md +++ b/doc/toolbox-run.1.md @@ -4,7 +4,8 @@ toolbox\-run - Run a command in an existing Toolbx container ## SYNOPSIS -**toolbox run** [*--container NAME* | *-c NAME*] +**toolbox run** [*--arch ARCHITECTURE* | *-a ARCHITECTURE*] + [*--container NAME* | *-c NAME*] [*--distro DISTRO* | *-d DISTRO*] [*--preserve-fds N*] [*--release RELEASE* | *-r RELEASE*] @@ -26,6 +27,12 @@ to a `podman start` followed by a `podman exec`. The following options are understood: +**--arch** ARCHITECTURE, **-a** ARCHITECTURE + +Run command inside a Toolbx container for a different architecture ARCHITECTURE +than the host (e.g., `arm64` on `amd64`). Can be combined with `--distro` and +`--release`. + **--container** NAME, **-c** NAME Run command inside a Toolbx container with the given NAME. This is useful when @@ -103,6 +110,12 @@ $ toolbox run --distro fedora --release f36 emacs $ toolbox run --container foo uptime ``` +### Run uname inside the default Toolbx container for the arm64 architecture + +``` +$ toolbox run --arch arm64 uname -m +``` + ## SEE ALSO `toolbox(1)`, `podman(1)`, `podman-exec(1)`, `podman-start(1)` diff --git a/doc/toolbox.1.md b/doc/toolbox.1.md index b30efd40c..6df09c99a 100644 --- a/doc/toolbox.1.md +++ b/doc/toolbox.1.md @@ -72,6 +72,21 @@ fedora |\ or f\ eg., 36 or f36 rhel |\.\ eg., 8.5 ubuntu |\.\ eg., 22.04 +## Cross-architecture containers support + +Toolbx supports creating and using containers for a different CPU architecture +than the host through QEMU user-mode emulation. This allows, for example, +running an `arm64` container on an `amd64` host for cross-compilation or +testing. Cross-architecture containers are created and used through the +`--arch` flag accepted by `toolbox create`, `toolbox enter` and `toolbox run`. +The following architectures are supported: + +Architecture |Accepted values +-------------|--------------------------- +arm64 |arm64, aarch64 +ppc64le |ppc64le +amd64 |amd64, x86\_64 + ## USAGE ### Create a Toolbx container: