From 7158535fe6f3971e1ceed0ea4e572a3bf5bced70 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Wed, 11 Mar 2026 09:37:40 -0400 Subject: [PATCH] chore(tests): fix test fixture build on modern ARM Mac (#4666) BUILDPLATFORM is automatically set to the host's platform in new Docker, so having it defined as an arg results in it being overridden by this automatic value. Since it was always assigned to a literal string in the test files, just use that string. Additionally, image platform is better pulled from the manifest, not the image config, in containerd store, so try that first. Additionally, python3 is on PATH on new macs by default, but not python. Signed-off-by: Will Murphy --- .../executable/testdata/elf/Makefile | 4 +- .../testdata/shared-info/Dockerfile | 5 +- .../executable/testdata/shared-info/Makefile | 2 +- .../manager/internal/download_from_image.go | 70 +++++++++++++++++-- 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/syft/file/cataloger/executable/testdata/elf/Makefile b/syft/file/cataloger/executable/testdata/elf/Makefile index 5130c8fac..5d859e067 100644 --- a/syft/file/cataloger/executable/testdata/elf/Makefile +++ b/syft/file/cataloger/executable/testdata/elf/Makefile @@ -22,7 +22,7 @@ tools-check: # docker buildx build --platform linux/amd64 -t $(TOOL_IMAGE) . tools: - @(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256) + @(docker image inspect $(TOOL_IMAGE) > /dev/null 2>&1 && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256) build: tools mkdir -p $(BIN) @@ -31,7 +31,7 @@ build: tools verify: tools @rm -f $(VERIFY_FILE) docker run -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make verify > $(VERIFY_FILE) - @python ./differ expected_verify $(VERIFY_FILE) + @python3 ./differ expected_verify $(VERIFY_FILE) debug: docker run -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash diff --git a/syft/file/cataloger/executable/testdata/shared-info/Dockerfile b/syft/file/cataloger/executable/testdata/shared-info/Dockerfile index da598949e..d034b339c 100644 --- a/syft/file/cataloger/executable/testdata/shared-info/Dockerfile +++ b/syft/file/cataloger/executable/testdata/shared-info/Dockerfile @@ -1,10 +1,9 @@ # syntax=docker/dockerfile:1 ARG OSXCROSS_VERSION=13.1 -ARG BUILDPLATFORM=linux/amd64 -FROM --platform=$BUILDPLATFORM crazymax/osxcross:${OSXCROSS_VERSION}-ubuntu AS osxcross +FROM --platform=linux/amd64 crazymax/osxcross:${OSXCROSS_VERSION}-ubuntu AS osxcross -FROM --platform=$BUILDPLATFORM ubuntu:22.04 +FROM --platform=linux/amd64 ubuntu:22.04 RUN apt update -y && apt install -y \ curl wget \ diff --git a/syft/file/cataloger/executable/testdata/shared-info/Makefile b/syft/file/cataloger/executable/testdata/shared-info/Makefile index 8321e0ae0..cc5b03a77 100644 --- a/syft/file/cataloger/executable/testdata/shared-info/Makefile +++ b/syft/file/cataloger/executable/testdata/shared-info/Makefile @@ -19,7 +19,7 @@ tools-check: @sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1) tools: - @(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256) + @(docker image inspect $(TOOL_IMAGE) > /dev/null 2>&1 && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256) build: tools @mkdir -p $(BIN) diff --git a/syft/pkg/cataloger/binary/internal/manager/internal/download_from_image.go b/syft/pkg/cataloger/binary/internal/manager/internal/download_from_image.go index 7eb502e90..6b8af8e6e 100644 --- a/syft/pkg/cataloger/binary/internal/manager/internal/download_from_image.go +++ b/syft/pkg/cataloger/binary/internal/manager/internal/download_from_image.go @@ -127,23 +127,83 @@ func pullDockerImage(imageReference, platform string) error { } func checkArchitecturesMatch(imageReference, platform string) (bool, string, error) { + // first check if the image exists locally cmd := exec.Command("docker", "image", "inspect", imageReference) - out, err := cmd.CombinedOutput() + if err := cmd.Run(); err != nil { + return false, "", err + } + + // prefer the manifest list for platform info — with Docker's containerd image store, + // platform metadata lives on the manifest list entry, not in the image config. + if found, err := platformInManifest(imageReference, platform); err == nil { + return found, platform, nil + } + + // fall back to image config for older Docker daemons that don't support "docker manifest inspect" + gotPlatform, err := platformFromImageInspect(imageReference) if err != nil { return false, "", err } + return gotPlatform == platform, gotPlatform, nil +} + +func platformFromImageInspect(imageReference string) (string, error) { + cmd := exec.Command("docker", "image", "inspect", imageReference) + out, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + var inspect []imageInspect if err := json.Unmarshal(out, &inspect); err != nil { - return false, "", fmt.Errorf("unable to unmarshal image inspect: %w", err) + return "", fmt.Errorf("unable to unmarshal image inspect: %w", err) } if len(inspect) != 1 { - return false, "", fmt.Errorf("expected 1 image inspect, got %d", len(inspect)) + return "", fmt.Errorf("expected 1 image inspect, got %d", len(inspect)) } - gotPlatform := inspect[0].Platform() - return gotPlatform == platform, gotPlatform, nil + return inspect[0].Platform(), nil +} + +type manifestList struct { + Manifests []manifestEntry `json:"manifests"` +} + +type manifestEntry struct { + Platform manifestPlatform `json:"platform"` +} + +type manifestPlatform struct { + Architecture string `json:"architecture"` + OS string `json:"os"` +} + +// platformInManifest checks whether the wanted platform is available in the image's manifest list. +// With Docker's containerd image store, all images are distributed via manifest lists (indexes), +// and platform metadata may only be on the index entry — not in the image config that +// "docker image inspect" reads. "docker manifest inspect" reads the index directly. +func platformInManifest(imageReference, wantPlatform string) (bool, error) { + cmd := exec.Command("docker", "manifest", "inspect", imageReference) + out, err := cmd.CombinedOutput() + if err != nil { + return false, fmt.Errorf("manifest inspect failed: %w", err) + } + + var ml manifestList + if err := json.Unmarshal(out, &ml); err != nil { + return false, fmt.Errorf("unable to unmarshal manifest: %w", err) + } + + for _, m := range ml.Manifests { + p := fmt.Sprintf("%s/%s", m.Platform.OS, m.Platform.Architecture) + if p == wantPlatform { + return true, nil + } + } + + return false, nil } func copyBinariesFromDockerImages(config config.BinaryFromImage, destination string) (err error) {