From 34a7a6bb5ec675d213bffc1b332a7a7b94938ef5 Mon Sep 17 00:00:00 2001 From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Date: Thu, 29 May 2025 11:28:00 +0100 Subject: [PATCH] UPSTREAM: : [Default Catalog Consistency Test] (feat) add check to validate multi-arch support --- .../pkg/check/image.go | 63 +++++++++++++++++++ .../test/validate/suite_test.go | 37 ++++++----- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/openshift/default-catalog-consistency/pkg/check/image.go b/openshift/default-catalog-consistency/pkg/check/image.go index 3c32de180..785ada852 100644 --- a/openshift/default-catalog-consistency/pkg/check/image.go +++ b/openshift/default-catalog-consistency/pkg/check/image.go @@ -8,7 +8,9 @@ import ( "sort" "strings" + "github.com/containers/image/v5/docker" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" specsgov1 "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" ) @@ -112,6 +114,67 @@ func ImageHasLabels(expectedLabels map[string]string) ImageCheck { } } +// RequiredPlatforms is a list of platforms that are required for the image to be considered valid. +var RequiredPlatforms = []specsgov1.Platform{ + {OS: "linux", Architecture: "amd64"}, + {OS: "linux", Architecture: "arm64"}, + {OS: "linux", Architecture: "ppc64le"}, + {OS: "linux", Architecture: "s390x"}, +} + +// ImageSupportsMultiArch verifies multi-arch support by inspecting the remote image manifest +// list directly. We don’t use extract.UnpackImage and the check interfaces here because it picks +// one platform based on OSChoice. On macOS, this fails if the image doesn’t support darwin/arm64 +// or darwin/amd64 and to make easier develop and test on macOS we keep the check independent of +// the unpacking logic where we set OSChoice = "linux" to avoid OS mismatch errors. +func ImageSupportsMultiArch(imageRef string, expected []specsgov1.Platform, sysCtx *types.SystemContext) ImageCheck { + return ImageCheck{ + Name: "ImageSupportsMultiArch", + // Do not use the unpacked image, but pull the manifest list directly from the remote. + Fn: func(ctx context.Context, _ specsgov1.Descriptor, _ oras.ReadOnlyTarget) error { + ref, err := docker.ParseReference("//" + imageRef) + if err != nil { + return fmt.Errorf("parse image ref: %w", err) + } + + src, err := ref.NewImageSource(ctx, sysCtx) + if err != nil { + return fmt.Errorf("new image source: %w", err) + } + defer src.Close() + + manifestBytes, _, err := src.GetManifest(ctx, nil) + if err != nil { + return fmt.Errorf("get manifest: %w", err) + } + + mf, err := manifest.Schema2ListFromManifest(manifestBytes) + if err != nil { + return fmt.Errorf("parse multiarch list: %w", err) + } + + found := map[string]struct{}{} + for _, desc := range mf.Manifests { + key := fmt.Sprintf("%s/%s", desc.Platform.OS, desc.Platform.Architecture) + found[key] = struct{}{} + } + + var missing []string + for _, p := range expected { + key := fmt.Sprintf("%s/%s", p.OS, p.Architecture) + if _, ok := found[key]; !ok { + missing = append(missing, key) + } + } + + if len(missing) > 0 { + return fmt.Errorf("missing required platforms: %v", missing) + } + return nil + }, + } +} + func isValidMediaType(m specsgov1.Manifest) error { switch m.MediaType { case specsgov1.MediaTypeImageManifest, manifest.DockerV2Schema2MediaType: diff --git a/openshift/default-catalog-consistency/test/validate/suite_test.go b/openshift/default-catalog-consistency/test/validate/suite_test.go index 295e3339e..ac9b7e486 100644 --- a/openshift/default-catalog-consistency/test/validate/suite_test.go +++ b/openshift/default-catalog-consistency/test/validate/suite_test.go @@ -10,6 +10,7 @@ import ( . "github.com/onsi/gomega" "github.com/containers/image/v5/types" + specsgov1 "github.com/opencontainers/image-spec/specs-go/v1" "github/operator-framework-operator-controller/openshift/default-catalog-consistency/pkg/check" "github/operator-framework-operator-controller/openshift/default-catalog-consistency/pkg/extract" @@ -28,21 +29,7 @@ var _ = Describe("Check Catalog Consistency", func() { Expect(images).ToNot(BeEmpty(), "no images found") authPath := os.Getenv("REGISTRY_AUTH_FILE") - // Force image resolution to Linux to avoid OS mismatch errors on macOS, - // like: "no image found for architecture 'arm64', OS 'darwin'". - // - // Setting OSChoice = "linux" ensures we always get a Linux image, - // even when running on macOS. - // - // This skips the full multi-arch index and gives us just one manifest. - // To check all supported architectures (e.g., amd64, arm64, ppc64le, s390x), - // we’d need to avoid setting OSChoice and inspect the full index manually. - // - // TODO: Update this to support checking all architectures. - // See: https://issues.redhat.com/browse/OPRUN-3793 - sysCtx := &types.SystemContext{ - OSChoice: "linux", - } + sysCtx := &types.SystemContext{} if authPath != "" { fmt.Println("Using registry auth file:", authPath) sysCtx.AuthFilePath = authPath @@ -50,10 +37,28 @@ var _ = Describe("Check Catalog Consistency", func() { for _, url := range images { name := utils.ImageNameFromRef(url) + ctx := context.Background() + + It(fmt.Sprintf("validates multiarch support for image: %s", name), func() { + By(fmt.Sprintf("Validating image: %s", url)) + err := check.ImageSupportsMultiArch( + url, + check.RequiredPlatforms, + sysCtx, + ).Fn(ctx, specsgov1.Descriptor{}, nil) + Expect(err).ToNot(HaveOccurred()) + }) It(fmt.Sprintf("validates image: %s", name), func() { - ctx := context.Background() By(fmt.Sprintf("Validating image: %s", url)) + // Force image resolution to Linux to avoid OS mismatch errors on macOS, + // like: "no image found for architecture 'arm64', OS 'darwin'". + // + // Setting OSChoice = "linux" ensures we always get a Linux image, + // even when running on macOS. + // + // This skips the full multi-arch index and gives us just one manifest. + sysCtx.OSChoice = "linux" extractedImage, err := extract.UnpackImage(ctx, url, name, sysCtx) Expect(err).ToNot(HaveOccurred())