From d8b7d14afb49b00db1a8db635207fe06bd78e442 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Fri, 11 Jun 2021 16:22:44 +0200 Subject: [PATCH] libimage: lookup images by custom platform Allow for looking up images via customizable arch, os and variant. This prevents `podman run --arch=xxx` from redundantly pulling down the image if needed. Context: containers/podman/issues/10648 Signed-off-by: Valentin Rothberg --- libimage/pull.go | 7 +++++- libimage/pull_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++ libimage/runtime.go | 51 ++++++++++++++++++++++++++------------- 3 files changed, 97 insertions(+), 17 deletions(-) diff --git a/libimage/pull.go b/libimage/pull.go index 0271f0051..22cb1a567 100644 --- a/libimage/pull.go +++ b/libimage/pull.go @@ -349,7 +349,12 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str // resolved name for pulling. Assume we're doing a `pull foo`. // If there's already a local image "localhost/foo", then we should // attempt pulling that instead of doing the full short-name dance. - localImage, resolvedImageName, err = r.LookupImage(imageName, nil) + lookupOptions := &LookupImageOptions{ + Architecture: options.Architecture, + OS: options.OS, + Variant: options.Variant, + } + localImage, resolvedImageName, err = r.LookupImage(imageName, lookupOptions) if err != nil && errors.Cause(err) != storage.ErrImageUnknown { logrus.Errorf("Looking up %s in local storage: %v", imageName, err) } diff --git a/libimage/pull_test.go b/libimage/pull_test.go index 22f760499..50857c3c8 100644 --- a/libimage/pull_test.go +++ b/libimage/pull_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + goruntime "runtime" "testing" "github.com/containers/common/pkg/config" @@ -67,3 +68,58 @@ func TestPull(t *testing.T) { assert.True(t, rmReports[0].Removed) } } + +func TestPullPlatforms(t *testing.T) { + runtime, cleanup := testNewRuntime(t) + defer cleanup() + ctx := context.Background() + pullOptions := &PullOptions{} + pullOptions.Writer = os.Stdout + + localArch := goruntime.GOARCH + localOS := goruntime.GOOS + + pulledImages, err := runtime.Pull(ctx, "busybox", config.PullPolicyAlways, pullOptions) + require.NoError(t, err, "pull busybox") + require.Len(t, pulledImages, 1) + + image, _, err := runtime.LookupImage("busybox", nil) + require.NoError(t, err, "lookup busybox") + require.NotNil(t, image, "lookup busybox") + + image, _, err = runtime.LookupImage("busybox", &LookupImageOptions{IgnorePlatform: true}) + require.NoError(t, err, "lookup busybox - ign. platform") + require.NotNil(t, image, "lookup busybox - ing. platform") + + image, _, err = runtime.LookupImage("busybox", &LookupImageOptions{Architecture: localArch}) + require.NoError(t, err, "lookup busybox - by local arch") + require.NotNil(t, image, "lookup busybox - by local arch") + + image, _, err = runtime.LookupImage("busybox", &LookupImageOptions{OS: localOS}) + require.NoError(t, err, "lookup busybox - by local arch") + require.NotNil(t, image, "lookup busybox - by local arch") + + _, _, err = runtime.LookupImage("busybox", &LookupImageOptions{Architecture: "bogus"}) + require.Error(t, err, "lookup busybox - bogus arch") + + _, _, err = runtime.LookupImage("busybox", &LookupImageOptions{OS: "bogus"}) + require.Error(t, err, "lookup busybox - bogus OS") + + pullOptions.Architecture = "arm" + pulledImages, err = runtime.Pull(ctx, "busybox", config.PullPolicyAlways, pullOptions) + require.NoError(t, err, "pull busybox - arm") + require.Len(t, pulledImages, 1) + + if localArch != "arm" { + _, _, err = runtime.LookupImage("busybox", nil) + require.Error(t, err, "lookup busybox - local arch != arm") + } + + image, _, err = runtime.LookupImage("busybox", &LookupImageOptions{Architecture: "arm"}) + require.NoError(t, err, "lookup busybox - by arm") + require.NotNil(t, image, "lookup busybox - by local arch") + + image, _, err = runtime.LookupImage("busybox", &LookupImageOptions{IgnorePlatform: true}) + require.NoError(t, err, "lookup busybox - ign. platform") + require.NotNil(t, image, "lookup busybox - ing. platform") +} diff --git a/libimage/runtime.go b/libimage/runtime.go index 550c49031..2019ba265 100644 --- a/libimage/runtime.go +++ b/libimage/runtime.go @@ -153,6 +153,13 @@ type LookupImageOptions struct { // the platform does not matter, for instance, for image removal. IgnorePlatform bool + // Lookup an image matching the specified architecture. + Architecture string + // Lookup an image matching the specified OS. + OS string + // Lookup an image matching the specified variant. + Variant string + // If set, do not look for items/instances in the manifest list that // match the current platform but return the manifest list as is. lookupManifest bool @@ -204,6 +211,25 @@ func (r *Runtime) LookupImage(name string, options *LookupImageOptions) (*Image, name = strings.TrimPrefix(name, "sha256:") } + // Set the platform for matching local images. + if !options.IgnorePlatform { + if options.Architecture == "" { + options.Architecture = r.systemContext.ArchitectureChoice + } + if options.Architecture == "" { + options.Architecture = runtime.GOARCH + } + if options.OS == "" { + options.OS = r.systemContext.OSChoice + } + if options.OS == "" { + options.OS = runtime.GOOS + } + if options.Variant == "" { + options.Variant = r.systemContext.VariantChoice + } + } + // First, check if we have an exact match in the storage. Maybe an ID // or a fully-qualified image name. img, err := r.lookupImageInLocalStorage(name, name, options) @@ -289,7 +315,7 @@ func (r *Runtime) lookupImageInLocalStorage(name, candidate string, options *Loo if err != nil { return nil, err } - instance, err := manifestList.LookupInstance(context.Background(), "", "", "") + instance, err := manifestList.LookupInstance(context.Background(), options.Architecture, options.OS, options.Variant) if err != nil { // NOTE: If we are not looking for a specific platform // and already found the manifest list, then return it @@ -310,7 +336,7 @@ func (r *Runtime) lookupImageInLocalStorage(name, candidate string, options *Loo return image, nil } - matches, err := imageReferenceMatchesContext(context.Background(), ref, &r.systemContext) + matches, err := r.imageReferenceMatchesContext(ref, options) if err != nil { return nil, err } @@ -422,12 +448,13 @@ func (r *Runtime) ResolveName(name string) (string, error) { } // imageReferenceMatchesContext return true if the specified reference matches -// the platform (os, arch, variant) as specified by the system context. -func imageReferenceMatchesContext(ctx context.Context, ref types.ImageReference, sys *types.SystemContext) (bool, error) { - if sys == nil { +// the platform (os, arch, variant) as specified by the lookup options. +func (r *Runtime) imageReferenceMatchesContext(ref types.ImageReference, options *LookupImageOptions) (bool, error) { + if options.IgnorePlatform { return true, nil } - img, err := ref.NewImage(ctx, sys) + ctx := context.Background() + img, err := ref.NewImage(ctx, &r.systemContext) if err != nil { return false, err } @@ -436,16 +463,8 @@ func imageReferenceMatchesContext(ctx context.Context, ref types.ImageReference, if err != nil { return false, err } - osChoice := sys.OSChoice - if osChoice == "" { - osChoice = runtime.GOOS - } - arch := sys.ArchitectureChoice - if arch == "" { - arch = runtime.GOARCH - } - if osChoice == data.Os && arch == data.Architecture { - if sys.VariantChoice == "" || sys.VariantChoice == data.Variant { + if options.OS == data.Os && options.Architecture == data.Architecture { + if options.Variant == "" || options.Variant == data.Variant { return true, nil } }