diff --git a/client.go b/client.go index 099244916..fd20c3dd0 100644 --- a/client.go +++ b/client.go @@ -338,7 +338,7 @@ func (c *Client) Fetch(ctx context.Context, ref string, opts ...RemoteOpt) (imag } defer done(ctx) - return c.fetch(ctx, fetchCtx, ref) + return c.fetch(ctx, fetchCtx, ref, 0) } // Pull downloads the provided content into containerd's content store @@ -372,7 +372,7 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image } defer done(ctx) - img, err := c.fetch(ctx, pullCtx, ref) + img, err := c.fetch(ctx, pullCtx, ref, 1) if err != nil { return nil, err } @@ -388,7 +388,7 @@ func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image return i, nil } -func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string) (images.Image, error) { +func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string, limit int) (images.Image, error) { store := c.ContentStore() name, desc, err := rCtx.Resolver.Resolve(ctx, ref) if err != nil { @@ -414,6 +414,10 @@ func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string) (im childrenHandler = images.SetChildrenLabels(store, childrenHandler) // Filter children by platforms childrenHandler = images.FilterPlatforms(childrenHandler, rCtx.PlatformMatcher) + // Sort and limit manifests if a finite number is needed + if limit > 0 { + childrenHandler = images.LimitManifests(childrenHandler, rCtx.PlatformMatcher, limit) + } handler = images.Handlers(append(rCtx.BaseHandlers, remotes.FetchHandler(store, fetcher), diff --git a/images/handlers.go b/images/handlers.go index 4b8c26f32..230a9caf8 100644 --- a/images/handlers.go +++ b/images/handlers.go @@ -19,6 +19,7 @@ package images import ( "context" "fmt" + "sort" "github.com/containerd/containerd/content" "github.com/containerd/containerd/platforms" @@ -182,44 +183,6 @@ func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc { } } -// FilterPlatformList is a handler wrapper which limits the descriptors returned -// by a handler to the specified platforms. -func FilterPlatformList(f HandlerFunc, platformList ...string) HandlerFunc { - return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { - children, err := f(ctx, desc) - if err != nil { - return children, err - } - - if len(platformList) == 0 { - return children, nil - } - - var m platforms.Matcher - - if len(platformList) > 0 { - ps := make([]ocispec.Platform, len(platformList)) - for i, platform := range platformList { - p, err := platforms.Parse(platform) - if err != nil { - return nil, err - } - ps[i] = p - } - m = platforms.Any(ps...) - } - - var descs []ocispec.Descriptor - for _, d := range children { - if d.Platform == nil || m.Match(*d.Platform) { - descs = append(descs, d) - } - } - - return descs, nil - } -} - // FilterPlatforms is a handler wrapper which limits the descriptors returned // based on matching the specified platform matcher. func FilterPlatforms(f HandlerFunc, m platforms.Matcher) HandlerFunc { @@ -244,3 +207,37 @@ func FilterPlatforms(f HandlerFunc, m platforms.Matcher) HandlerFunc { return descs, nil } } + +// LimitManifests is a handler wrapper which filters the manifest descriptors +// returned using the provided platform. +// The results will be ordered according to the comparison operator and +// use the ordering in the manifests for equal matches. +// A limit of 0 or less is considered no limit. +func LimitManifests(f HandlerFunc, m platforms.MatchComparer, n int) HandlerFunc { + return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + children, err := f(ctx, desc) + if err != nil { + return children, err + } + + switch desc.MediaType { + case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList: + sort.SliceStable(children, func(i, j int) bool { + if children[i].Platform == nil { + return false + } + if children[j].Platform == nil { + return true + } + return m.Less(*children[i].Platform, *children[j].Platform) + }) + + if n > 0 && len(children) > n { + children = children[:n] + } + default: + // only limit manifests from an index + } + return children, nil + } +}