github.com/lalkh/containerd@v1.4.3/remotes/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 remotes
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/containerd/containerd/content"
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/containerd/containerd/images"
    29  	"github.com/containerd/containerd/log"
    30  	"github.com/containerd/containerd/platforms"
    31  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    32  	"github.com/pkg/errors"
    33  	"github.com/sirupsen/logrus"
    34  )
    35  
    36  type refKeyPrefix struct{}
    37  
    38  // WithMediaTypeKeyPrefix adds a custom key prefix for a media type which is used when storing
    39  // data in the content store from the FetchHandler.
    40  //
    41  // Used in `MakeRefKey` to determine what the key prefix should be.
    42  func WithMediaTypeKeyPrefix(ctx context.Context, mediaType, prefix string) context.Context {
    43  	var values map[string]string
    44  	if v := ctx.Value(refKeyPrefix{}); v != nil {
    45  		values = v.(map[string]string)
    46  	} else {
    47  		values = make(map[string]string)
    48  	}
    49  
    50  	values[mediaType] = prefix
    51  	return context.WithValue(ctx, refKeyPrefix{}, values)
    52  }
    53  
    54  // MakeRefKey returns a unique reference for the descriptor. This reference can be
    55  // used to lookup ongoing processes related to the descriptor. This function
    56  // may look to the context to namespace the reference appropriately.
    57  func MakeRefKey(ctx context.Context, desc ocispec.Descriptor) string {
    58  	if v := ctx.Value(refKeyPrefix{}); v != nil {
    59  		values := v.(map[string]string)
    60  		if prefix := values[desc.MediaType]; prefix != "" {
    61  			return prefix + "-" + desc.Digest.String()
    62  		}
    63  	}
    64  
    65  	switch mt := desc.MediaType; {
    66  	case mt == images.MediaTypeDockerSchema2Manifest || mt == ocispec.MediaTypeImageManifest:
    67  		return "manifest-" + desc.Digest.String()
    68  	case mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex:
    69  		return "index-" + desc.Digest.String()
    70  	case images.IsLayerType(mt):
    71  		return "layer-" + desc.Digest.String()
    72  	case images.IsKnownConfig(mt):
    73  		return "config-" + desc.Digest.String()
    74  	default:
    75  		log.G(ctx).Warnf("reference for unknown type: %s", mt)
    76  		return "unknown-" + desc.Digest.String()
    77  	}
    78  }
    79  
    80  // FetchHandler returns a handler that will fetch all content into the ingester
    81  // discovered in a call to Dispatch. Use with ChildrenHandler to do a full
    82  // recursive fetch.
    83  func FetchHandler(ingester content.Ingester, fetcher Fetcher) images.HandlerFunc {
    84  	return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
    85  		ctx = log.WithLogger(ctx, log.G(ctx).WithFields(logrus.Fields{
    86  			"digest":    desc.Digest,
    87  			"mediatype": desc.MediaType,
    88  			"size":      desc.Size,
    89  		}))
    90  
    91  		switch desc.MediaType {
    92  		case images.MediaTypeDockerSchema1Manifest:
    93  			return nil, fmt.Errorf("%v not supported", desc.MediaType)
    94  		default:
    95  			err := fetch(ctx, ingester, fetcher, desc)
    96  			return nil, err
    97  		}
    98  	}
    99  }
   100  
   101  func fetch(ctx context.Context, ingester content.Ingester, fetcher Fetcher, desc ocispec.Descriptor) error {
   102  	log.G(ctx).Debug("fetch")
   103  
   104  	cw, err := content.OpenWriter(ctx, ingester, content.WithRef(MakeRefKey(ctx, desc)), content.WithDescriptor(desc))
   105  	if err != nil {
   106  		if errdefs.IsAlreadyExists(err) {
   107  			return nil
   108  		}
   109  		return err
   110  	}
   111  	defer cw.Close()
   112  
   113  	ws, err := cw.Status()
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	if ws.Offset == desc.Size {
   119  		// If writer is already complete, commit and return
   120  		err := cw.Commit(ctx, desc.Size, desc.Digest)
   121  		if err != nil && !errdefs.IsAlreadyExists(err) {
   122  			return errors.Wrapf(err, "failed commit on ref %q", ws.Ref)
   123  		}
   124  		return nil
   125  	}
   126  
   127  	rc, err := fetcher.Fetch(ctx, desc)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	defer rc.Close()
   132  
   133  	return content.Copy(ctx, cw, rc, desc.Size, desc.Digest)
   134  }
   135  
   136  // PushHandler returns a handler that will push all content from the provider
   137  // using a writer from the pusher.
   138  func PushHandler(pusher Pusher, provider content.Provider) images.HandlerFunc {
   139  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   140  		ctx = log.WithLogger(ctx, log.G(ctx).WithFields(logrus.Fields{
   141  			"digest":    desc.Digest,
   142  			"mediatype": desc.MediaType,
   143  			"size":      desc.Size,
   144  		}))
   145  
   146  		err := push(ctx, provider, pusher, desc)
   147  		return nil, err
   148  	}
   149  }
   150  
   151  func push(ctx context.Context, provider content.Provider, pusher Pusher, desc ocispec.Descriptor) error {
   152  	log.G(ctx).Debug("push")
   153  
   154  	cw, err := pusher.Push(ctx, desc)
   155  	if err != nil {
   156  		if !errdefs.IsAlreadyExists(err) {
   157  			return err
   158  		}
   159  
   160  		return nil
   161  	}
   162  	defer cw.Close()
   163  
   164  	ra, err := provider.ReaderAt(ctx, desc)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	defer ra.Close()
   169  
   170  	rd := io.NewSectionReader(ra, 0, desc.Size)
   171  	return content.Copy(ctx, cw, rd, desc.Size, desc.Digest)
   172  }
   173  
   174  // PushContent pushes content specified by the descriptor from the provider.
   175  //
   176  // Base handlers can be provided which will be called before any push specific
   177  // handlers.
   178  func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, store content.Store, platform platforms.MatchComparer, wrapper func(h images.Handler) images.Handler) error {
   179  	var m sync.Mutex
   180  	manifestStack := []ocispec.Descriptor{}
   181  
   182  	filterHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   183  		switch desc.MediaType {
   184  		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
   185  			images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
   186  			m.Lock()
   187  			manifestStack = append(manifestStack, desc)
   188  			m.Unlock()
   189  			return nil, images.ErrStopHandler
   190  		default:
   191  			return nil, nil
   192  		}
   193  	})
   194  
   195  	pushHandler := PushHandler(pusher, store)
   196  
   197  	platformFilterhandler := images.FilterPlatforms(images.ChildrenHandler(store), platform)
   198  
   199  	annotateHandler := annotateDistributionSourceHandler(platformFilterhandler, store)
   200  
   201  	var handler images.Handler = images.Handlers(
   202  		annotateHandler,
   203  		filterHandler,
   204  		pushHandler,
   205  	)
   206  	if wrapper != nil {
   207  		handler = wrapper(handler)
   208  	}
   209  
   210  	if err := images.Dispatch(ctx, handler, nil, desc); err != nil {
   211  		return err
   212  	}
   213  
   214  	// Iterate in reverse order as seen, parent always uploaded after child
   215  	for i := len(manifestStack) - 1; i >= 0; i-- {
   216  		_, err := pushHandler(ctx, manifestStack[i])
   217  		if err != nil {
   218  			// TODO(estesp): until we have a more complete method for index push, we need to report
   219  			// missing dependencies in an index/manifest list by sensing the "400 Bad Request"
   220  			// as a marker for this problem
   221  			if (manifestStack[i].MediaType == ocispec.MediaTypeImageIndex ||
   222  				manifestStack[i].MediaType == images.MediaTypeDockerSchema2ManifestList) &&
   223  				errors.Cause(err) != nil && strings.Contains(errors.Cause(err).Error(), "400 Bad Request") {
   224  				return errors.Wrap(err, "manifest list/index references to blobs and/or manifests are missing in your target registry")
   225  			}
   226  			return err
   227  		}
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  // FilterManifestByPlatformHandler allows Handler to handle non-target
   234  // platform's manifest and configuration data.
   235  func FilterManifestByPlatformHandler(f images.HandlerFunc, m platforms.Matcher) images.HandlerFunc {
   236  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   237  		children, err := f(ctx, desc)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  
   242  		// no platform information
   243  		if desc.Platform == nil || m == nil {
   244  			return children, nil
   245  		}
   246  
   247  		var descs []ocispec.Descriptor
   248  		switch desc.MediaType {
   249  		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
   250  			if m.Match(*desc.Platform) {
   251  				descs = children
   252  			} else {
   253  				for _, child := range children {
   254  					if child.MediaType == images.MediaTypeDockerSchema2Config ||
   255  						child.MediaType == ocispec.MediaTypeImageConfig {
   256  
   257  						descs = append(descs, child)
   258  					}
   259  				}
   260  			}
   261  		default:
   262  			descs = children
   263  		}
   264  		return descs, nil
   265  	}
   266  }
   267  
   268  // annotateDistributionSourceHandler add distribution source label into
   269  // annotation of config or blob descriptor.
   270  func annotateDistributionSourceHandler(f images.HandlerFunc, manager content.Manager) images.HandlerFunc {
   271  	return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   272  		children, err := f(ctx, desc)
   273  		if err != nil {
   274  			return nil, err
   275  		}
   276  
   277  		// only add distribution source for the config or blob data descriptor
   278  		switch desc.MediaType {
   279  		case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
   280  			images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
   281  		default:
   282  			return children, nil
   283  		}
   284  
   285  		for i := range children {
   286  			child := children[i]
   287  
   288  			info, err := manager.Info(ctx, child.Digest)
   289  			if err != nil {
   290  				return nil, err
   291  			}
   292  
   293  			for k, v := range info.Labels {
   294  				if !strings.HasPrefix(k, "containerd.io/distribution.source.") {
   295  					continue
   296  				}
   297  
   298  				if child.Annotations == nil {
   299  					child.Annotations = map[string]string{}
   300  				}
   301  				child.Annotations[k] = v
   302  			}
   303  
   304  			children[i] = child
   305  		}
   306  		return children, nil
   307  	}
   308  }