diff --git a/common/libimage/copier.go b/common/libimage/copier.go index 4f5c7d0a16..4599895793 100644 --- a/common/libimage/copier.go +++ b/common/libimage/copier.go @@ -218,15 +218,7 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) { c.systemContext.DockerArchiveAdditionalTags = options.dockerArchiveAdditionalTags - if options.Architecture != "" { - c.systemContext.ArchitectureChoice = options.Architecture - } - if options.OS != "" { - c.systemContext.OSChoice = options.OS - } - if options.Variant != "" { - c.systemContext.VariantChoice = options.Variant - } + c.systemContext.OSChoice, c.systemContext.ArchitectureChoice, c.systemContext.VariantChoice = NormalizePlatform(options.OS, options.Architecture, options.Variant) if options.SignaturePolicyPath != "" { c.systemContext.SignaturePolicyPath = options.SignaturePolicyPath diff --git a/common/libimage/normalize.go b/common/libimage/normalize.go index bfea807c8b..7ceb628306 100644 --- a/common/libimage/normalize.go +++ b/common/libimage/normalize.go @@ -1,13 +1,51 @@ package libimage import ( + "runtime" "strings" + "github.com/containerd/containerd/platforms" "github.com/containers/image/v5/docker/reference" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +// NormalizePlatform normalizes (according to the OCI spec) the specified os, +// arch and variant. If left empty, the individual item will not be normalized. +func NormalizePlatform(rawOS, rawArch, rawVariant string) (os, arch, variant string) { + os, arch, variant = rawOS, rawArch, rawVariant + if os == "" { + os = runtime.GOOS + } + if arch == "" { + arch = runtime.GOARCH + } + rawPlatform := os + "/" + arch + if variant != "" { + rawPlatform += "/" + 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 != "" { + variant = normalizedPlatform.Variant + } + return os, arch, variant +} + // NormalizeName normalizes the provided name according to the conventions by // Podman and Buildah. If tag and digest are missing, the "latest" tag will be // used. If it's a short name, it will be prefixed with "localhost/". diff --git a/common/libimage/normalize_test.go b/common/libimage/normalize_test.go index 2dcd284481..63659178b1 100644 --- a/common/libimage/normalize_test.go +++ b/common/libimage/normalize_test.go @@ -7,6 +7,61 @@ import ( "github.com/stretchr/testify/require" ) +func TestNormalizePlatform(t *testing.T) { + type platform struct { + os, arch, variant string + } + for _, test := range []struct { + input, expected platform + }{ + { + platform{"", "", ""}, + platform{"", "", ""}, + }, + { + platform{"foo", "", "garbage"}, + platform{"foo", "", "garbage"}, + }, + { + platform{"&", "invalid", "os"}, + platform{"&", "invalid", "os"}, + }, + { + platform{"linux", "", ""}, + platform{"linux", "", ""}, + }, + { + platform{"LINUX", "", ""}, + platform{"linux", "", ""}, + }, + { + platform{"", "aarch64", ""}, + platform{"", "arm64", ""}, + }, + { + platform{"macos", "x86_64", ""}, + platform{"darwin", "amd64", ""}, + }, + { + platform{"linux", "amd64", ""}, + platform{"linux", "amd64", ""}, + }, + { + platform{"linux", "arm64", "v8"}, + platform{"linux", "arm64", "v8"}, + }, + { + platform{"linux", "aarch64", ""}, + platform{"linux", "arm64", ""}, + }, + } { + os, arch, variant := NormalizePlatform(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) + } +} + func TestNormalizeName(t *testing.T) { const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" diff --git a/common/libimage/pull_test.go b/common/libimage/pull_test.go index c3fd4ddea5..6bd6137b22 100644 --- a/common/libimage/pull_test.go +++ b/common/libimage/pull_test.go @@ -123,7 +123,12 @@ func TestPullPlatforms(t *testing.T) { image, _, err = runtime.LookupImage(withTag, &LookupImageOptions{Architecture: "arm"}) require.NoError(t, err, "lookup busybox - by arm") - require.NotNil(t, image, "lookup busybox - by local arch") + require.NotNil(t, image, "lookup busybox - by arm") + + pullOptions.Architecture = "aarch64" + pulledImages, err = runtime.Pull(ctx, withTag, config.PullPolicyAlways, pullOptions) + require.NoError(t, err, "pull busybox - aarch64") + require.Len(t, pulledImages, 1) } func TestPullPolicy(t *testing.T) { diff --git a/common/libimage/runtime.go b/common/libimage/runtime.go index c7cf8124a1..486be20bc9 100644 --- a/common/libimage/runtime.go +++ b/common/libimage/runtime.go @@ -253,6 +253,8 @@ func (r *Runtime) LookupImage(name string, options *LookupImageOptions) (*Image, if options.Variant == "" { 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) // First, check if we have an exact match in the storage. Maybe an ID // or a fully-qualified image name. @@ -489,13 +491,16 @@ func (r *Runtime) imageReferenceMatchesContext(ref types.ImageReference, options } if options.Architecture != "" && options.Architecture != data.Architecture { - return false, err + logrus.Debugf("architecture %q does not match architecture %q of image %s", options.Architecture, data.Architecture, ref) + return false, nil } if options.OS != "" && options.OS != data.Os { - return false, err + logrus.Debugf("OS %q does not match OS %q of image %s", options.OS, data.Os, ref) + return false, nil } if options.Variant != "" && options.Variant != data.Variant { - return false, err + logrus.Debugf("variant %q does not match variant %q of image %s", options.Variant, data.Variant, ref) + return false, nil } return true, nil