256 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
		
			7.3 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/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.Cause(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.Cause(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, ctx := 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(ctx, desc)
 | |
| 			if limiter != nil {
 | |
| 				limiter.Release(1)
 | |
| 			}
 | |
| 			if err != nil {
 | |
| 				if errors.Cause(err) == ErrSkipDesc {
 | |
| 					return nil // don't traverse the children.
 | |
| 				}
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			if len(children) > 0 {
 | |
| 				return Dispatch(ctx, 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 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 {
 | |
| 			info := content.Info{
 | |
| 				Digest: desc.Digest,
 | |
| 				Labels: map[string]string{},
 | |
| 			}
 | |
| 			fields := []string{}
 | |
| 			for i, ch := range children {
 | |
| 				info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String()
 | |
| 				fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i))
 | |
| 			}
 | |
| 
 | |
| 			_, 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.
 | |
| 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
 | |
| 	}
 | |
| }
 | 
