github.com/demonoid81/containerd@v1.3.4/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/platforms"
    26  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    27  	"github.com/pkg/errors"
    28  	"golang.org/x/sync/errgroup"
    29  	"golang.org/x/sync/semaphore"
    30  )
    31  
    32  var (
    33  	// ErrSkipDesc is used to skip processing of a descriptor and
    34  	// its descendants.
    35  	ErrSkipDesc = fmt.Errorf("skip descriptor")
    36  
    37  	// ErrStopHandler is used to signify that the descriptor
    38  	// has been handled and should not be handled further.
    39  	// This applies only to a single descriptor in a handler
    40  	// chain and does not apply to descendant descriptors.
    41  	ErrStopHandler = fmt.Errorf("stop handler")
    42  )
    43  
    44  // Handler handles image manifests
    45  type Handler interface {
    46  	Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
    47  }
    48  
    49  // HandlerFunc function implementing the Handler interface
    50  type HandlerFunc func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
    51  
    52  // Handle image manifests
    53  func (fn HandlerFunc) Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
    54  	return fn(ctx, desc)
    55  }
    56  
    57  // Handlers returns a handler that will run the handlers in sequence.
    58  //
    59  // A handler may return `ErrStopHandler` to stop calling additional handlers
    60  func Handlers(handlers ...Handler) HandlerFunc {
    61  	return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
    62  		var children []ocispec.Descriptor
    63  		for _, handler := range handlers {
    64  			ch, err := handler.Handle(ctx, desc)
    65  			if err != nil {
    66  				if errors.Cause(err) == ErrStopHandler {
    67  					break
    68  				}
    69  				return nil, err
    70  			}
    71  
    72  			children = append(children, ch...)
    73  		}
    74  
    75  		return children, nil
    76  	}
    77  }
    78  
    79  // Walk the resources of an image and call the handler for each. If the handler
    80  // decodes the sub-resources for each image,
    81  //
    82  // This differs from dispatch in that each sibling resource is considered
    83  // synchronously.
    84  func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
    85  	for _, desc := range descs {
    86  
    87  		children, err := handler.Handle(ctx, desc)
    88  		if err != nil {
    89  			if errors.Cause(err) == ErrSkipDesc {
    90  				continue // don't traverse the children.
    91  			}
    92  			return err
    93  		}
    94  
    95  		if len(children) > 0 {
    96  			if err := Walk(ctx, handler, children...); err != nil {
    97  				return err
    98  			}
    99  		}
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // Dispatch runs the provided handler for content specified by the descriptors.
   106  // If the handler decode subresources, they will be visited, as well.
   107  //
   108  // Handlers for siblings are run in parallel on the provided descriptors. A
   109  // handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse
   110  // any children.
   111  //
   112  // A concurrency limiter can be passed in to limit the number of concurrent
   113  // handlers running. When limiter is nil, there is no limit.
   114  //
   115  // Typically, this function will be used with `FetchHandler`, often composed
   116  // with other handlers.
   117  //
   118  // If any handler returns an error, the dispatch session will be canceled.
   119  func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error {
   120  	eg, ctx2 := errgroup.WithContext(ctx)
   121  	for _, desc := range descs {
   122  		desc := desc
   123  
   124  		if limiter != nil {
   125  			if err := limiter.Acquire(ctx, 1); err != nil {
   126  				return err
   127  			}
   128  		}
   129  
   130  		eg.Go(func() error {
   131  			desc := desc
   132  
   133  			children, err := handler.Handle(ctx2, desc)
   134  			if limiter != nil {
   135  				limiter.Release(1)
   136  			}
   137  			if err != nil {
   138  				if errors.Cause(err) == ErrSkipDesc {
   139  					return nil // don't traverse the children.
   140  				}
   141  				return err
   142  			}
   143  
   144  			if len(children) > 0 {
   145  				return Dispatch(ctx2, handler, limiter, children...)
   146  			}
   147  
   148  			return nil
   149  		})
   150  	}
   151  
   152  	return eg.Wait()
   153  }
   154  
   155  // ChildrenHandler decodes well-known manifest types and returns their children.
   156  //
   157  // This is useful for supporting recursive fetch and other use cases where you
   158  // want to do a full walk of resources.
   159  //
   160  // One can also replace this with another implementation to allow descending of
   161  // arbitrary types.
   162  func ChildrenHandler(provider content.Provider) HandlerFunc {
   163  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   164  		return Children(ctx, provider, desc)
   165  	}
   166  }
   167  
   168  // SetChildrenLabels is a handler wrapper which sets labels for the content on
   169  // the children returned by the handler and passes through the children.
   170  // Must follow a handler that returns the children to be labeled.
   171  func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
   172  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   173  		children, err := f(ctx, desc)
   174  		if err != nil {
   175  			return children, err
   176  		}
   177  
   178  		if len(children) > 0 {
   179  			info := content.Info{
   180  				Digest: desc.Digest,
   181  				Labels: map[string]string{},
   182  			}
   183  			fields := []string{}
   184  			for i, ch := range children {
   185  				info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String()
   186  				fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i))
   187  			}
   188  
   189  			_, err := manager.Update(ctx, info, fields...)
   190  			if err != nil {
   191  				return nil, err
   192  			}
   193  		}
   194  
   195  		return children, err
   196  	}
   197  }
   198  
   199  // FilterPlatforms is a handler wrapper which limits the descriptors returned
   200  // based on matching the specified platform matcher.
   201  func FilterPlatforms(f HandlerFunc, m platforms.Matcher) HandlerFunc {
   202  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   203  		children, err := f(ctx, desc)
   204  		if err != nil {
   205  			return children, err
   206  		}
   207  
   208  		var descs []ocispec.Descriptor
   209  
   210  		if m == nil {
   211  			descs = children
   212  		} else {
   213  			for _, d := range children {
   214  				if d.Platform == nil || m.Match(*d.Platform) {
   215  					descs = append(descs, d)
   216  				}
   217  			}
   218  		}
   219  
   220  		return descs, nil
   221  	}
   222  }
   223  
   224  // LimitManifests is a handler wrapper which filters the manifest descriptors
   225  // returned using the provided platform.
   226  // The results will be ordered according to the comparison operator and
   227  // use the ordering in the manifests for equal matches.
   228  // A limit of 0 or less is considered no limit.
   229  func LimitManifests(f HandlerFunc, m platforms.MatchComparer, n int) HandlerFunc {
   230  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   231  		children, err := f(ctx, desc)
   232  		if err != nil {
   233  			return children, err
   234  		}
   235  
   236  		switch desc.MediaType {
   237  		case ocispec.MediaTypeImageIndex, MediaTypeDockerSchema2ManifestList:
   238  			sort.SliceStable(children, func(i, j int) bool {
   239  				if children[i].Platform == nil {
   240  					return false
   241  				}
   242  				if children[j].Platform == nil {
   243  					return true
   244  				}
   245  				return m.Less(*children[i].Platform, *children[j].Platform)
   246  			})
   247  
   248  			if n > 0 && len(children) > n {
   249  				children = children[:n]
   250  			}
   251  		default:
   252  			// only limit manifests from an index
   253  		}
   254  		return children, nil
   255  	}
   256  }