diff --git a/libimage/copier.go b/libimage/copier.go index fd10dd72d..27b4e59df 100644 --- a/libimage/copier.go +++ b/libimage/copier.go @@ -11,6 +11,7 @@ import ( "time" "github.com/containers/common/libimage/manifests" + "github.com/containers/common/libimage/platform" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/retry" "github.com/containers/image/v5/copy" @@ -239,7 +240,7 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) { c.systemContext.DockerArchiveAdditionalTags = options.dockerArchiveAdditionalTags - c.systemContext.OSChoice, c.systemContext.ArchitectureChoice, c.systemContext.VariantChoice = NormalizePlatform(options.OS, options.Architecture, options.Variant) + c.systemContext.OSChoice, c.systemContext.ArchitectureChoice, c.systemContext.VariantChoice = platform.Normalize(options.OS, options.Architecture, options.Variant) if options.SignaturePolicyPath != "" { c.systemContext.SignaturePolicyPath = options.SignaturePolicyPath diff --git a/libimage/define/platform.go b/libimage/define/platform.go new file mode 100644 index 000000000..7e13abffd --- /dev/null +++ b/libimage/define/platform.go @@ -0,0 +1,11 @@ +package define + +// PlatformPolicy controls the behavior of image-platform matching. +type PlatformPolicy int + +const ( + // Only debug log if an image does not match the expected platform. + PlatformPolicyDefault PlatformPolicy = iota + // Warn if an image does not match the expected platform. + PlatformPolicyWarn +) diff --git a/libimage/image.go b/libimage/image.go index e4198a792..6ff044530 100644 --- a/libimage/image.go +++ b/libimage/image.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/containerd/containerd/platforms" + "github.com/containers/common/libimage/platform" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" storageTransport "github.com/containers/image/v5/storage" @@ -1021,3 +1023,35 @@ func getImageID(ctx context.Context, src types.ImageReference, sys *types.System } return "@" + imageDigest.Encoded(), nil } + +// Checks whether the image matches the specified platform. +// Returns +// - 1) a matching error that can be used for logging (or returning) what does not match +// - 2) a bool indicating whether architecture, os or variant were set (some callers need that to decide whether they need to throw an error) +// - 3) a fatal error that occurred prior to check for matches (e.g., storage errors etc.) +func (i *Image) matchesPlatform(ctx context.Context, os, arch, variant string) (error, bool, error) { + if err := i.isCorrupted(""); err != nil { + return err, false, nil + } + inspectInfo, err := i.inspectInfo(ctx) + if err != nil { + return nil, false, fmt.Errorf("inspecting image: %w", err) + } + + customPlatform := len(os)+len(arch)+len(variant) != 0 + + expected, err := platforms.Parse(platform.ToString(os, arch, variant)) + if err != nil { + return nil, false, fmt.Errorf("parsing host platform: %v", err) + } + fromImage, err := platforms.Parse(platform.ToString(inspectInfo.Os, inspectInfo.Architecture, inspectInfo.Variant)) + if err != nil { + return nil, false, fmt.Errorf("parsing image platform: %v", err) + } + + if platforms.NewMatcher(expected).Match(fromImage) { + return nil, customPlatform, nil + } + + return fmt.Errorf("image platform (%s) does not match the expected platform (%s)", platforms.Format(fromImage), platforms.Format(expected)), customPlatform, nil +} diff --git a/libimage/normalize_test.go b/libimage/normalize_test.go index 9b9dfb464..50583d9ad 100644 --- a/libimage/normalize_test.go +++ b/libimage/normalize_test.go @@ -3,6 +3,7 @@ package libimage import ( "testing" + lplatform "github.com/containers/common/libimage/platform" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -83,7 +84,7 @@ func TestNormalizePlatform(t *testing.T) { platform{"linux", "arm", "v6"}, }, } { - os, arch, variant := NormalizePlatform(test.input.os, test.input.arch, test.input.variant) + os, arch, variant := lplatform.Normalize(test.input.os, test.input.arch, test.input.variant) assert.Equal(t, test.expected.os, os, test.input) assert.Equal(t, test.expected.arch, arch, test.input) assert.Equal(t, test.expected.variant, variant, test.input) diff --git a/libimage/platform.go b/libimage/platform.go deleted file mode 100644 index 06c15ee64..000000000 --- a/libimage/platform.go +++ /dev/null @@ -1,100 +0,0 @@ -package libimage - -import ( - "context" - "fmt" - "runtime" - - "github.com/containerd/containerd/platforms" - v1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/sirupsen/logrus" -) - -// PlatformPolicy controls the behavior of image-platform matching. -type PlatformPolicy int - -const ( - // Only debug log if an image does not match the expected platform. - PlatformPolicyDefault PlatformPolicy = iota - // Warn if an image does not match the expected platform. - PlatformPolicyWarn -) - -// NormalizePlatform normalizes (according to the OCI spec) the specified os, -// arch and variant. If left empty, the individual item will be normalized. -func NormalizePlatform(rawOS, rawArch, rawVariant string) (os, arch, variant string) { - platformSpec := v1.Platform{ - OS: rawOS, - Architecture: rawArch, - Variant: rawVariant, - } - normalizedSpec := platforms.Normalize(platformSpec) - if normalizedSpec.Variant == "" && rawVariant != "" { - normalizedSpec.Variant = rawVariant - } - rawPlatform := toPlatformString(normalizedSpec.OS, normalizedSpec.Architecture, normalizedSpec.Variant) - normalizedPlatform, err := platforms.Parse(rawPlatform) - if err != nil { - logrus.Debugf("Error normalizing platform: %v", err) - return rawOS, rawArch, rawVariant - } - logrus.Debugf("Normalized platform %s to %s", rawPlatform, normalizedPlatform) - os = rawOS - if rawOS != "" { - os = normalizedPlatform.OS - } - arch = rawArch - if rawArch != "" { - arch = normalizedPlatform.Architecture - } - variant = rawVariant - if rawVariant != "" || (rawVariant == "" && normalizedPlatform.Variant != "") { - variant = normalizedPlatform.Variant - } - return os, arch, variant -} - -func toPlatformString(os, arch, variant string) string { - if os == "" { - os = runtime.GOOS - } - if arch == "" { - arch = runtime.GOARCH - } - if variant == "" { - return fmt.Sprintf("%s/%s", os, arch) - } - return fmt.Sprintf("%s/%s/%s", os, arch, variant) -} - -// Checks whether the image matches the specified platform. -// Returns -// - 1) a matching error that can be used for logging (or returning) what does not match -// - 2) a bool indicating whether architecture, os or variant were set (some callers need that to decide whether they need to throw an error) -// - 3) a fatal error that occurred prior to check for matches (e.g., storage errors etc.) -func (i *Image) matchesPlatform(ctx context.Context, os, arch, variant string) (error, bool, error) { - if err := i.isCorrupted(""); err != nil { - return err, false, nil - } - inspectInfo, err := i.inspectInfo(ctx) - if err != nil { - return nil, false, fmt.Errorf("inspecting image: %w", err) - } - - customPlatform := len(os)+len(arch)+len(variant) != 0 - - expected, err := platforms.Parse(toPlatformString(os, arch, variant)) - if err != nil { - return nil, false, fmt.Errorf("parsing host platform: %v", err) - } - fromImage, err := platforms.Parse(toPlatformString(inspectInfo.Os, inspectInfo.Architecture, inspectInfo.Variant)) - if err != nil { - return nil, false, fmt.Errorf("parsing image platform: %v", err) - } - - if platforms.NewMatcher(expected).Match(fromImage) { - return nil, customPlatform, nil - } - - return fmt.Errorf("image platform (%s) does not match the expected platform (%s)", platforms.Format(fromImage), platforms.Format(expected)), customPlatform, nil -} diff --git a/libimage/platform/platform.go b/libimage/platform/platform.go new file mode 100644 index 000000000..2272cee75 --- /dev/null +++ b/libimage/platform/platform.go @@ -0,0 +1,57 @@ +package platform + +import ( + "fmt" + "runtime" + + "github.com/containerd/containerd/platforms" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" +) + +// Normalize normalizes (according to the OCI spec) the specified os, +// arch and variant. If left empty, the individual item will be normalized. +func Normalize(rawOS, rawArch, rawVariant string) (os, arch, variant string) { + platformSpec := v1.Platform{ + OS: rawOS, + Architecture: rawArch, + Variant: rawVariant, + } + normalizedSpec := platforms.Normalize(platformSpec) + if normalizedSpec.Variant == "" && rawVariant != "" { + normalizedSpec.Variant = rawVariant + } + rawPlatform := ToString(normalizedSpec.OS, normalizedSpec.Architecture, normalizedSpec.Variant) + normalizedPlatform, err := platforms.Parse(rawPlatform) + if err != nil { + logrus.Debugf("Error normalizing platform: %v", err) + return rawOS, rawArch, rawVariant + } + logrus.Debugf("Normalized platform %s to %s", rawPlatform, normalizedPlatform) + os = rawOS + if rawOS != "" { + os = normalizedPlatform.OS + } + arch = rawArch + if rawArch != "" { + arch = normalizedPlatform.Architecture + } + variant = rawVariant + if rawVariant != "" || (rawVariant == "" && normalizedPlatform.Variant != "") { + variant = normalizedPlatform.Variant + } + return os, arch, variant +} + +func ToString(os, arch, variant string) string { + if os == "" { + os = runtime.GOOS + } + if arch == "" { + arch = runtime.GOARCH + } + if variant == "" { + return fmt.Sprintf("%s/%s", os, arch) + } + return fmt.Sprintf("%s/%s/%s", os, arch, variant) +} diff --git a/libimage/platform_test.go b/libimage/platform/platform_test.go similarity index 87% rename from libimage/platform_test.go rename to libimage/platform/platform_test.go index 36104107b..f7c14e9d2 100644 --- a/libimage/platform_test.go +++ b/libimage/platform/platform_test.go @@ -1,4 +1,4 @@ -package libimage +package platform import ( "fmt" @@ -19,7 +19,7 @@ func TestToPlatformString(t *testing.T) { {"", "", "", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)}, {"", "", "c", fmt.Sprintf("%s/%s/c", runtime.GOOS, runtime.GOARCH)}, } { - platform := toPlatformString(test.os, test.arch, test.variant) + platform := ToString(test.os, test.arch, test.variant) require.Equal(t, test.expected, platform) } } diff --git a/libimage/runtime.go b/libimage/runtime.go index 6d90272c3..33f515e99 100644 --- a/libimage/runtime.go +++ b/libimage/runtime.go @@ -7,6 +7,8 @@ import ( "os" "strings" + "github.com/containers/common/libimage/define" + "github.com/containers/common/libimage/platform" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/pkg/shortnames" @@ -184,7 +186,7 @@ type LookupImageOptions struct { Variant string // Controls the behavior when checking the platform of an image. - PlatformPolicy PlatformPolicy + PlatformPolicy define.PlatformPolicy // If set, do not look for items/instances in the manifest list that // match the current platform but return the manifest list as is. @@ -283,7 +285,7 @@ func (r *Runtime) LookupImage(name string, options *LookupImageOptions) (*Image, options.Variant = r.systemContext.VariantChoice } // Normalize platform to be OCI compatible (e.g., "aarch64" -> "arm64"). - options.OS, options.Architecture, options.Variant = NormalizePlatform(options.OS, options.Architecture, options.Variant) + options.OS, options.Architecture, options.Variant = platform.Normalize(options.OS, options.Architecture, options.Variant) // Second, try out the candidates as resolved by shortnames. This takes // "localhost/" prefixed images into account as well. @@ -435,9 +437,9 @@ func (r *Runtime) lookupImageInLocalStorage(name, candidate string, namedCandida return nil, nil } switch options.PlatformPolicy { - case PlatformPolicyDefault: + case define.PlatformPolicyDefault: logrus.Debugf("%v", matchError) - case PlatformPolicyWarn: + case define.PlatformPolicyWarn: logrus.Warnf("%v", matchError) } }