Give control of the content labeling process for children to the client. This allows the client to control the names associated with the labels and filter out labels. Signed-off-by: Derek McGowan <derek@mcg.dev>
		
			
				
	
	
		
			289 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright The containerd Authors.
 | 
						|
 | 
						|
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
   you may not use this file except in compliance with the License.
 | 
						|
   You may obtain a copy of the License at
 | 
						|
 | 
						|
       http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
   Unless required by applicable law or agreed to in writing, software
 | 
						|
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
   See the License for the specific language governing permissions and
 | 
						|
   limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package images
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	"github.com/containerd/containerd/content"
 | 
						|
	"github.com/containerd/containerd/errdefs"
 | 
						|
	"github.com/containerd/containerd/platforms"
 | 
						|
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"golang.org/x/sync/errgroup"
 | 
						|
	"golang.org/x/sync/semaphore"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// ErrSkipDesc is used to skip processing of a descriptor and
 | 
						|
	// its descendants.
 | 
						|
	ErrSkipDesc = fmt.Errorf("skip descriptor")
 | 
						|
 | 
						|
	// ErrStopHandler is used to signify that the descriptor
 | 
						|
	// has been handled and should not be handled further.
 | 
						|
	// This applies only to a single descriptor in a handler
 | 
						|
	// chain and does not apply to descendant descriptors.
 | 
						|
	ErrStopHandler = fmt.Errorf("stop handler")
 | 
						|
)
 | 
						|
 | 
						|
// Handler handles image manifests
 | 
						|
type Handler interface {
 | 
						|
	Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
 | 
						|
}
 | 
						|
 | 
						|
// HandlerFunc function implementing the Handler interface
 | 
						|
type HandlerFunc func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
 | 
						|
 | 
						|
// Handle image manifests
 | 
						|
func (fn HandlerFunc) Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
 | 
						|
	return fn(ctx, desc)
 | 
						|
}
 | 
						|
 | 
						|
// Handlers returns a handler that will run the handlers in sequence.
 | 
						|
//
 | 
						|
// A handler may return `ErrStopHandler` to stop calling additional handlers
 | 
						|
func Handlers(handlers ...Handler) HandlerFunc {
 | 
						|
	return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
 | 
						|
		var children []ocispec.Descriptor
 | 
						|
		for _, handler := range handlers {
 | 
						|
			ch, err := handler.Handle(ctx, desc)
 | 
						|
			if err != nil {
 | 
						|
				if errors.Is(err, ErrStopHandler) {
 | 
						|
					break
 | 
						|
				}
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
 | 
						|
			children = append(children, ch...)
 | 
						|
		}
 | 
						|
 | 
						|
		return children, nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Walk the resources of an image and call the handler for each. If the handler
 | 
						|
// decodes the sub-resources for each image,
 | 
						|
//
 | 
						|
// This differs from dispatch in that each sibling resource is considered
 | 
						|
// synchronously.
 | 
						|
func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
 | 
						|
	for _, desc := range descs {
 | 
						|
 | 
						|
		children, err := handler.Handle(ctx, desc)
 | 
						|
		if err != nil {
 | 
						|
			if errors.Is(err, ErrSkipDesc) {
 | 
						|
				continue // don't traverse the children.
 | 
						|
			}
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		if len(children) > 0 {
 | 
						|
			if err := Walk(ctx, handler, children...); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Dispatch runs the provided handler for content specified by the descriptors.
 | 
						|
// If the handler decode subresources, they will be visited, as well.
 | 
						|
//
 | 
						|
// Handlers for siblings are run in parallel on the provided descriptors. A
 | 
						|
// handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse
 | 
						|
// any children.
 | 
						|
//
 | 
						|
// A concurrency limiter can be passed in to limit the number of concurrent
 | 
						|
// handlers running. When limiter is nil, there is no limit.
 | 
						|
//
 | 
						|
// Typically, this function will be used with `FetchHandler`, often composed
 | 
						|
// with other handlers.
 | 
						|
//
 | 
						|
// If any handler returns an error, the dispatch session will be canceled.
 | 
						|
func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error {
 | 
						|
	eg, ctx2 := errgroup.WithContext(ctx)
 | 
						|
	for _, desc := range descs {
 | 
						|
		desc := desc
 | 
						|
 | 
						|
		if limiter != nil {
 | 
						|
			if err := limiter.Acquire(ctx, 1); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		eg.Go(func() error {
 | 
						|
			desc := desc
 | 
						|
 | 
						|
			children, err := handler.Handle(ctx2, desc)
 | 
						|
			if limiter != nil {
 | 
						|
				limiter.Release(1)
 | 
						|
			}
 | 
						|
			if err != nil {
 | 
						|
				if errors.Is(err, ErrSkipDesc) {
 | 
						|
					return nil // don't traverse the children.
 | 
						|
				}
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			if len(children) > 0 {
 | 
						|
				return Dispatch(ctx2, handler, limiter, children...)
 | 
						|
			}
 | 
						|
 | 
						|
			return nil
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	return eg.Wait()
 | 
						|
}
 | 
						|
 | 
						|
// ChildrenHandler decodes well-known manifest types and returns their children.
 | 
						|
//
 | 
						|
// This is useful for supporting recursive fetch and other use cases where you
 | 
						|
// want to do a full walk of resources.
 | 
						|
//
 | 
						|
// One can also replace this with another implementation to allow descending of
 | 
						|
// arbitrary types.
 | 
						|
func ChildrenHandler(provider content.Provider) HandlerFunc {
 | 
						|
	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
 | 
						|
		return Children(ctx, provider, desc)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SetChildrenLabels is a handler wrapper which sets labels for the content on
 | 
						|
// the children returned by the handler and passes through the children.
 | 
						|
// Must follow a handler that returns the children to be labeled.
 | 
						|
func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
 | 
						|
	return SetChildrenMappedLabels(manager, f, nil)
 | 
						|
}
 | 
						|
 | 
						|
// SetChildrenMappedLabels is a handler wrapper which sets labels for the content on
 | 
						|
// the children returned by the handler and passes through the children.
 | 
						|
// Must follow a handler that returns the children to be labeled.
 | 
						|
// The label map allows the caller to control the labels per child descriptor.
 | 
						|
// For returned labels, the index of the child will be appended to the end
 | 
						|
// except for the first index when the returned label does not end with '.'.
 | 
						|
func SetChildrenMappedLabels(manager content.Manager, f HandlerFunc, labelMap func(ocispec.Descriptor) []string) HandlerFunc {
 | 
						|
	if labelMap == nil {
 | 
						|
		labelMap = ChildGCLabels
 | 
						|
	}
 | 
						|
	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
 | 
						|
		children, err := f(ctx, desc)
 | 
						|
		if err != nil {
 | 
						|
			return children, err
 | 
						|
		}
 | 
						|
 | 
						|
		if len(children) > 0 {
 | 
						|
			var (
 | 
						|
				info = content.Info{
 | 
						|
					Digest: desc.Digest,
 | 
						|
					Labels: map[string]string{},
 | 
						|
				}
 | 
						|
				fields = []string{}
 | 
						|
				keys   = map[string]uint{}
 | 
						|
			)
 | 
						|
			for _, ch := range children {
 | 
						|
				labelKeys := labelMap(ch)
 | 
						|
				for _, key := range labelKeys {
 | 
						|
					idx := keys[key]
 | 
						|
					keys[key] = idx + 1
 | 
						|
					if idx > 0 || key[len(key)-1] == '.' {
 | 
						|
						key = fmt.Sprintf("%s%d", key, idx)
 | 
						|
					}
 | 
						|
 | 
						|
					info.Labels[key] = ch.Digest.String()
 | 
						|
					fields = append(fields, "labels."+key)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			_, err := manager.Update(ctx, info, fields...)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return children, err
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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 {
 | 
						|
	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
 | 
						|
		children, err := f(ctx, desc)
 | 
						|
		if err != nil {
 | 
						|
			return children, err
 | 
						|
		}
 | 
						|
 | 
						|
		var descs []ocispec.Descriptor
 | 
						|
 | 
						|
		if m == nil {
 | 
						|
			descs = children
 | 
						|
		} else {
 | 
						|
			for _, d := range children {
 | 
						|
				if d.Platform == nil || m.Match(*d.Platform) {
 | 
						|
					descs = append(descs, d)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		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.
 | 
						|
// A not found error is returned if no manifest is matched.
 | 
						|
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 {
 | 
						|
				if len(children) == 0 {
 | 
						|
					return children, errors.Wrap(errdefs.ErrNotFound, "no match for platform in manifest")
 | 
						|
				}
 | 
						|
				if len(children) > n {
 | 
						|
					children = children[:n]
 | 
						|
				}
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			// only limit manifests from an index
 | 
						|
		}
 | 
						|
		return children, nil
 | 
						|
	}
 | 
						|
}
 |