libimage: normalize platform

Buildah, containers and probably other container engines are normalizing
the platform parameters to support common values.  For instance, "x86_64"
is normalized to the OCI conformant "amd64".

Use the same normalization when copying images and looking up local
images.  Also add some debug logs to facilitate future debugging.

Fixes: containers/podman/issues/12680
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg 2021-12-22 16:43:40 +01:00
parent 463f91ec52
commit 281201d87d
5 changed files with 108 additions and 13 deletions

View File

@ -218,15 +218,7 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
c.systemContext.DockerArchiveAdditionalTags = options.dockerArchiveAdditionalTags c.systemContext.DockerArchiveAdditionalTags = options.dockerArchiveAdditionalTags
if options.Architecture != "" { c.systemContext.OSChoice, c.systemContext.ArchitectureChoice, c.systemContext.VariantChoice = NormalizePlatform(options.OS, options.Architecture, options.Variant)
c.systemContext.ArchitectureChoice = options.Architecture
}
if options.OS != "" {
c.systemContext.OSChoice = options.OS
}
if options.Variant != "" {
c.systemContext.VariantChoice = options.Variant
}
if options.SignaturePolicyPath != "" { if options.SignaturePolicyPath != "" {
c.systemContext.SignaturePolicyPath = options.SignaturePolicyPath c.systemContext.SignaturePolicyPath = options.SignaturePolicyPath

View File

@ -1,13 +1,51 @@
package libimage package libimage
import ( import (
"runtime"
"strings" "strings"
"github.com/containerd/containerd/platforms"
"github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/docker/reference"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "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 // NormalizeName normalizes the provided name according to the conventions by
// Podman and Buildah. If tag and digest are missing, the "latest" tag will be // 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/". // used. If it's a short name, it will be prefixed with "localhost/".

View File

@ -7,6 +7,61 @@ import (
"github.com/stretchr/testify/require" "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) { func TestNormalizeName(t *testing.T) {
const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

View File

@ -123,7 +123,12 @@ func TestPullPlatforms(t *testing.T) {
image, _, err = runtime.LookupImage(withTag, &LookupImageOptions{Architecture: "arm"}) image, _, err = runtime.LookupImage(withTag, &LookupImageOptions{Architecture: "arm"})
require.NoError(t, err, "lookup busybox - by 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) { func TestPullPolicy(t *testing.T) {

View File

@ -253,6 +253,8 @@ func (r *Runtime) LookupImage(name string, options *LookupImageOptions) (*Image,
if options.Variant == "" { if options.Variant == "" {
options.Variant = r.systemContext.VariantChoice 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 // First, check if we have an exact match in the storage. Maybe an ID
// or a fully-qualified image name. // 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 { 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 { 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 { 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 return true, nil