github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/images/handlers.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package images
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  
    24  	"github.com/containerd/containerd/content"
    25  	"github.com/containerd/containerd/errdefs"
    26  	"github.com/containerd/containerd/platforms"
    27  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    28  	"github.com/pkg/errors"
    29  	"golang.org/x/sync/errgroup"
    30  	"golang.org/x/sync/semaphore"
    31  )
    32  
    33  var (
    34  	// ErrSkipDesc is used to skip processing of a descriptor and
    35  	// its descendants.
    36  	ErrSkipDesc = fmt.Errorf("skip descriptor")
    37  
    38  	// ErrStopHandler is used to signify that the descriptor
    39  	// has been handled and should not be handled further.
    40  	// This applies only to a single descriptor in a handler
    41  	// chain and does not apply to descendant descriptors.
    42  	ErrStopHandler = fmt.Errorf("stop handler")
    43  )
    44  
    45  // Handler handles image manifests
    46  type Handler interface {
    47  	Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
    48  }
    49  
    50  // HandlerFunc function implementing the Handler interface
    51  type HandlerFunc func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
    52  
    53  // Handle image manifests
    54  func (fn HandlerFunc) Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
    55  	return fn(ctx, desc)
    56  }
    57  
    58  // Handlers returns a handler that will run the handlers in sequence.
    59  //
    60  // A handler may return `ErrStopHandler` to stop calling additional handlers
    61  func Handlers(handlers ...Handler) HandlerFunc {
    62  	return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
    63  		var children []ocispec.Descriptor
    64  		for _, handler := range handlers {
    65  			ch, err := handler.Handle(ctx, desc)
    66  			if err != nil {
    67  				if errors.Is(err, ErrStopHandler) {
    68  					break
    69  				}
    70  				return nil, err
    71  			}
    72  
    73  			children = append(children, ch...)
    74  		}
    75  
    76  		return children, nil
    77  	}
    78  }
    79  
    80  // Walk the resources of an image and call the handler for each. If the handler
    81  // decodes the sub-resources for each image,
    82  //
    83  // This differs from dispatch in that each sibling resource is considered
    84  // synchronously.
    85  func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
    86  	for _, desc := range descs {
    87  
    88  		children, err := handler.Handle(ctx, desc)
    89  		if err != nil {
    90  			if errors.Is(err, ErrSkipDesc) {
    91  				continue // don't traverse the children.
    92  			}
    93  			return err
    94  		}
    95  
    96  		if len(children) > 0 {
    97  			if err := Walk(ctx, handler, children...); err != nil {
    98  				return err
    99  			}
   100  		}
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  // Dispatch runs the provided handler for content specified by the descriptors.
   107  // If the handler decode subresources, they will be visited, as well.
   108  //
   109  // Handlers for siblings are run in parallel on the provided descriptors. A
   110  // handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse
   111  // any children.
   112  //
   113  // A concurrency limiter can be passed in to limit the number of concurrent
   114  // handlers running. When limiter is nil, there is no limit.
   115  //
   116  // Typically, this function will be used with `FetchHandler`, often composed
   117  // with other handlers.
   118  //
   119  // If any handler returns an error, the dispatch session will be canceled.
   120  func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error {
   121  	eg, ctx2 := errgroup.WithContext(ctx)
   122  	for _, desc := range descs {
   123  		desc := desc
   124  
   125  		if limiter != nil {
   126  			if err := limiter.Acquire(ctx, 1); err != nil {
   127  				return err
   128  			}
   129  		}
   130  
   131  		eg.Go(func() error {
   132  			desc := desc
   133  
   134  			children, err := handler.Handle(ctx2, desc)
   135  			if limiter != nil {
   136  				limiter.Release(1)
   137  			}
   138  			if err != nil {
   139  				if errors.Is(err, ErrSkipDesc) {
   140  					return nil // don't traverse the children.
   141  				}
   142  				return err
   143  			}
   144  
   145  			if len(children) > 0 {
   146  				return Dispatch(ctx2, handler, limiter, children...)
   147  			}
   148  
   149  			return nil
   150  		})
   151  	}
   152  
   153  	return eg.Wait()
   154  }
   155  
   156  // ChildrenHandler decodes well-known manifest types and returns their children.
   157  //
   158  // This is useful for supporting recursive fetch and other use cases where you
   159  // want to do a full walk of resources.
   160  //
   161  // One can also replace this with another implementation to allow descending of
   162  // arbitrary types.
   163  func ChildrenHandler(provider content.Provider) HandlerFunc {
   164  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   165  		return Children(ctx, provider, desc)
   166  	}
   167  }
   168  
   169  // SetChildrenLabels is a handler wrapper which sets labels for the content on
   170  // the children returned by the handler and passes through the children.
   171  // Must follow a handler that returns the children to be labeled.
   172  func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
   173  	return SetChildrenMappedLabels(manager, f, nil)
   174  }
   175  
   176  // SetChildrenMappedLabels is a handler wrapper which sets labels for the content on
   177  // the children returned by the handler and passes through the children.
   178  // Must follow a handler that returns the children to be labeled.
   179  // The label map allows the caller to control the labels per child descriptor.
   180  // For returned labels, the index of the child will be appended to the end
   181  // except for the first index when the returned label does not end with '.'.
   182  func SetChildrenMappedLabels(manager content.Manager, f HandlerFunc, labelMap func(ocispec.Descriptor) []string) HandlerFunc {
   183  	if labelMap == nil {
   184  		labelMap = ChildGCLabels
   185  	}
   186  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   187  		children, err := f(ctx, desc)
   188  		if err != nil {
   189  			return children, err
   190  		}
   191  
   192  		if len(children) > 0 {
   193  			var (
   194  				info = content.Info{
   195  					Digest: desc.Digest,
   196  					Labels: map[string]string{},
   197  				}
   198  				fields = []string{}
   199  				keys   = map[string]uint{}
   200  			)
   201  			for _, ch := range children {
   202  				labelKeys := labelMap(ch)
   203  				for _, key := range labelKeys {
   204  					idx := keys[key]
   205  					keys[key] = idx + 1
   206  					if idx > 0 || key[len(key)-1] == '.' {
   207  						key = fmt.Sprintf("%s%d", key, idx)
   208  					}
   209  
   210  					info.Labels[key] = ch.Digest.String()
   211  					fields = append(fields, "labels."+key)
   212  				}
   213  			}
   214  
   215  			_, err := manager.Update(ctx, info, fields...)
   216  			if err != nil {
   217  				return nil, err
   218  			}
   219  		}
   220  
   221  		return children, err
   222  	}
   223  }
   224  
   225  // FilterPlatforms is a handler wrapper which limits the descriptors returned
   226  // based on matching the specified platform matcher.
   227  func FilterPlatforms(f HandlerFunc, m platforms.Matcher) HandlerFunc {
   228  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   229  		children, err := f(ctx, desc)
   230  		if err != nil {
   231  			return children, err
   232  		}
   233  
   234  		var descs []ocispec.Descriptor
   235  
   236  		if m == nil {
   237  			descs = children
   238  		} else {
   239  			for _, d := range children {
   240  				if d.Platform == nil || m.Match(*d.Platform) {
   241  					descs = append(descs, d)
   242  				}
   243  			}
   244  		}
   245  
   246  		return descs, nil
   247  	}
   248  }
   249  
   250  // LimitManifests is a handler wrapper which filters the manifest descriptors
   251  // returned using the provided platform.
   252  // The results will be ordered according to the comparison operator and
   253  // use the ordering in the manifests for equal matches.
   254  // A limit of 0 or less is considered no limit.
   255  // A not found error is returned if no manifest is matched.
   256  func LimitManifests(f HandlerFunc, m platforms.MatchComparer, n int) HandlerFunc {
   257  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   258  		children, err := f(ctx, desc)
   259  		if err != nil {
   260  			return children, err
   261  		}
   262  
   263  		switch desc.MediaType {
   264  		case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList:
   265  			sort.SliceStable(children, func(i, j int) bool {
   266  				if children[i].Platform == nil {
   267  					return false
   268  				}
   269  				if children[j].Platform == nil {
   270  					return true
   271  				}
   272  				return m.Less(*children[i].Platform, *children[j].Platform)
   273  			})
   274  
   275  			if n > 0 {
   276  				if len(children) == 0 {
   277  					return children, errors.Wrap(errdefs.ErrNotFound, "no match for platform in manifest")
   278  				}
   279  				if len(children) > n {
   280  					children = children[:n]
   281  				}
   282  			}
   283  		default:
   284  			// only limit manifests from an index
   285  		}
   286  		return children, nil
   287  	}
   288  }