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/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: 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 }}' diff --git a/src/cmd/create.go b/src/cmd/create.go index bdd86be37..925b8384d 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -21,34 +21,40 @@ import ( "errors" "fmt" "os" + "os/exec" "path/filepath" "strings" "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" "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 authFile string container string distro string @@ -75,6 +81,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", "", @@ -170,24 +182,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) + createFlags.release, + 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") } @@ -214,14 +245,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 @@ -413,6 +449,8 @@ func createContainer(container, image, release, authFile string, showCommandToEn 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, @@ -446,6 +484,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{ @@ -486,19 +528,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) @@ -508,6 +546,13 @@ func createContainer(container, image, release, authFile string, showCommandToEn 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() { @@ -567,42 +612,19 @@ func getEnterCommand(container string) string { return enterCommand } -func getImageSizeFromRegistry(ctx context.Context, imageFull string) (string, error) { - image, err := skopeo.Inspect(ctx, imageFull) - 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 @@ -665,11 +687,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 } } @@ -680,7 +704,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 } } @@ -692,13 +716,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) @@ -709,10 +735,18 @@ func pullImage(image, release, authFile string) (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 { @@ -723,36 +757,56 @@ 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) + shouldPullImage, imageInfo, imageInspectErr = showPromptForDownload(imageFull, archID, authFile) } if !shouldPullImage { - return false, nil + return false, false, nil } - logrus.Debugf("Pulling image %s", imageFull) + if imageInspectErr != nil && isNonNativeArch { + if errors.Is(imageInspectErr, exec.ErrNotFound) { + return false, false, createErrorSkopeoNotFound(imageFull, archID) + } - 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() + // For now, log and continue (imageInfo will be nil) + logrus.Debugf("Failed to inspect image: %s", imageInspectErr) } - 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) + logrus.Debugf("Pulling image %s", imageFull) - errMsg := builder.String() - return false, errors.New(errMsg) + s := startSpinner(fmt.Sprintf("Pulling %s: ", imageFull)) + defer stopSpinner(s) + + if !isNonNativeArch { + logrus.Debugf("'podman pull' is used for pulling image %s", imageFull) + + 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 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) + } } - return true, nil + return true, isNonNativeArch, nil } func createPromptForDownload(imageFull, imageSize string) string { @@ -766,8 +820,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) @@ -775,48 +830,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 { @@ -827,13 +903,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) @@ -859,12 +935,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") @@ -945,22 +1016,53 @@ 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 { + 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 @@ -990,17 +1092,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/enter.go b/src/cmd/enter.go index 9ba84d6ad..851868769 100644 --- a/src/cmd/enter.go +++ b/src/cmd/enter.go @@ -27,6 +27,7 @@ import ( var ( enterFlags struct { + arch string container string distro string release string @@ -43,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", @@ -104,11 +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) + enterFlags.release, + archID) if err != nil { return err 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/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..1e7f7f853 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" @@ -52,6 +53,7 @@ type entryPointError struct { var ( runFlags struct { + arch string container string distro string preserveFDs uint @@ -73,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", @@ -141,11 +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) + runFlags.release, + archID) if err != nil { return err @@ -225,7 +239,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 { @@ -962,8 +976,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() diff --git a/src/cmd/utils.go b/src/cmd/utils.go index 191da4ef6..c3762b48a 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) @@ -281,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) @@ -345,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" @@ -483,9 +513,40 @@ func poll(pollFn pollFunc, eventFD int32, fds ...int32) error { } } -func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, releaseCLI string) ( +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, archID int) ( string, string, string, error, ) { + containerWasEmpty := container == "" + container, image, release, err := utils.ResolveContainerAndImageNames(container, distroCLI, imageCLI, @@ -540,9 +601,35 @@ 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 } +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") diff --git a/src/pkg/architecture/architecture.go b/src/pkg/architecture/architecture.go new file mode 100644 index 000000000..652ed224d --- /dev/null +++ b/src/pkg/architecture/architecture.go @@ -0,0 +1,293 @@ +/* + * 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 ( + "debug/elf" + "errors" + "fmt" + "os" + "os/exec" + "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 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 { + return NotSpecified, fmt.Errorf("architecture '%s' is not supported by Toolbx", value) + } + + 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 +} 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.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 +} 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.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 +} 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.go b/src/pkg/skopeo/skopeo.go index 8b15c7370..7fd52be90 100644 --- a/src/pkg/skopeo/skopeo.go +++ b/src/pkg/skopeo/skopeo.go @@ -20,24 +20,116 @@ import ( "bytes" "context" "encoding/json" + "errors" + "fmt" + "os" + "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) { +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 } @@ -47,5 +139,7 @@ func Inspect(ctx context.Context, target string) (*Image, error) { return nil, err } + image.NameFull = target + return &image, nil } 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) + }) + } +} 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") 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 ] +} 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 ] +} 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