github.com/demonoid81/containerd@v1.3.4/pull.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 containerd
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/containerd/containerd/errdefs"
    23  	"github.com/containerd/containerd/images"
    24  	"github.com/containerd/containerd/platforms"
    25  	"github.com/containerd/containerd/remotes"
    26  	"github.com/containerd/containerd/remotes/docker"
    27  	"github.com/containerd/containerd/remotes/docker/schema1"
    28  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    29  	"github.com/pkg/errors"
    30  	"golang.org/x/sync/semaphore"
    31  )
    32  
    33  // Pull downloads the provided content into containerd's content store
    34  // and returns a platform specific image object
    35  func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (_ Image, retErr error) {
    36  	pullCtx := defaultRemoteContext()
    37  	for _, o := range opts {
    38  		if err := o(c, pullCtx); err != nil {
    39  			return nil, err
    40  		}
    41  	}
    42  
    43  	if pullCtx.PlatformMatcher == nil {
    44  		if len(pullCtx.Platforms) > 1 {
    45  			return nil, errors.New("cannot pull multiplatform image locally, try Fetch")
    46  		} else if len(pullCtx.Platforms) == 0 {
    47  			pullCtx.PlatformMatcher = c.platform
    48  		} else {
    49  			p, err := platforms.Parse(pullCtx.Platforms[0])
    50  			if err != nil {
    51  				return nil, errors.Wrapf(err, "invalid platform %s", pullCtx.Platforms[0])
    52  			}
    53  
    54  			pullCtx.PlatformMatcher = platforms.Only(p)
    55  		}
    56  	}
    57  
    58  	ctx, done, err := c.WithLease(ctx)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	defer done(ctx)
    63  
    64  	var unpacks int32
    65  	if pullCtx.Unpack {
    66  		// unpacker only supports schema 2 image, for schema 1 this is noop.
    67  		u, err := c.newUnpacker(ctx, pullCtx)
    68  		if err != nil {
    69  			return nil, errors.Wrap(err, "create unpacker")
    70  		}
    71  		unpackWrapper, eg := u.handlerWrapper(ctx, &unpacks)
    72  		defer func() {
    73  			if retErr != nil {
    74  				// Forcibly stop the unpacker if there is
    75  				// an error.
    76  				eg.Cancel()
    77  			}
    78  			if err := eg.Wait(); err != nil {
    79  				if retErr == nil {
    80  					retErr = errors.Wrap(err, "unpack")
    81  				}
    82  			}
    83  		}()
    84  		wrapper := pullCtx.HandlerWrapper
    85  		pullCtx.HandlerWrapper = func(h images.Handler) images.Handler {
    86  			if wrapper == nil {
    87  				return unpackWrapper(h)
    88  			}
    89  			return wrapper(unpackWrapper(h))
    90  		}
    91  	}
    92  
    93  	img, err := c.fetch(ctx, pullCtx, ref, 1)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	i := NewImageWithPlatform(c, img, pullCtx.PlatformMatcher)
    99  
   100  	if pullCtx.Unpack {
   101  		if unpacks == 0 {
   102  			// Try to unpack is none is done previously.
   103  			// This is at least required for schema 1 image.
   104  			if err := i.Unpack(ctx, pullCtx.Snapshotter, pullCtx.UnpackOpts...); err != nil {
   105  				return nil, errors.Wrapf(err, "failed to unpack image on snapshotter %s", pullCtx.Snapshotter)
   106  			}
   107  		}
   108  	}
   109  
   110  	return i, nil
   111  }
   112  
   113  func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string, limit int) (images.Image, error) {
   114  	store := c.ContentStore()
   115  	name, desc, err := rCtx.Resolver.Resolve(ctx, ref)
   116  	if err != nil {
   117  		return images.Image{}, errors.Wrapf(err, "failed to resolve reference %q", ref)
   118  	}
   119  
   120  	fetcher, err := rCtx.Resolver.Fetcher(ctx, name)
   121  	if err != nil {
   122  		return images.Image{}, errors.Wrapf(err, "failed to get fetcher for %q", name)
   123  	}
   124  
   125  	var (
   126  		handler images.Handler
   127  
   128  		isConvertible bool
   129  		converterFunc func(context.Context, ocispec.Descriptor) (ocispec.Descriptor, error)
   130  		limiter       *semaphore.Weighted
   131  	)
   132  
   133  	if desc.MediaType == images.MediaTypeDockerSchema1Manifest && rCtx.ConvertSchema1 {
   134  		schema1Converter := schema1.NewConverter(store, fetcher)
   135  
   136  		handler = images.Handlers(append(rCtx.BaseHandlers, schema1Converter)...)
   137  
   138  		isConvertible = true
   139  
   140  		converterFunc = func(ctx context.Context, _ ocispec.Descriptor) (ocispec.Descriptor, error) {
   141  			return schema1Converter.Convert(ctx)
   142  		}
   143  	} else {
   144  		// Get all the children for a descriptor
   145  		childrenHandler := images.ChildrenHandler(store)
   146  		// Set any children labels for that content
   147  		childrenHandler = images.SetChildrenLabels(store, childrenHandler)
   148  		if rCtx.AllMetadata {
   149  			// Filter manifests by platforms but allow to handle manifest
   150  			// and configuration for not-target platforms
   151  			childrenHandler = remotes.FilterManifestByPlatformHandler(childrenHandler, rCtx.PlatformMatcher)
   152  		} else {
   153  			// Filter children by platforms if specified.
   154  			childrenHandler = images.FilterPlatforms(childrenHandler, rCtx.PlatformMatcher)
   155  		}
   156  		// Sort and limit manifests if a finite number is needed
   157  		if limit > 0 {
   158  			childrenHandler = images.LimitManifests(childrenHandler, rCtx.PlatformMatcher, limit)
   159  		}
   160  
   161  		// set isConvertible to true if there is application/octet-stream media type
   162  		convertibleHandler := images.HandlerFunc(
   163  			func(_ context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   164  				if desc.MediaType == docker.LegacyConfigMediaType {
   165  					isConvertible = true
   166  				}
   167  
   168  				return []ocispec.Descriptor{}, nil
   169  			},
   170  		)
   171  
   172  		appendDistSrcLabelHandler, err := docker.AppendDistributionSourceLabel(store, ref)
   173  		if err != nil {
   174  			return images.Image{}, err
   175  		}
   176  
   177  		handlers := append(rCtx.BaseHandlers,
   178  			remotes.FetchHandler(store, fetcher),
   179  			convertibleHandler,
   180  			childrenHandler,
   181  			appendDistSrcLabelHandler,
   182  		)
   183  
   184  		handler = images.Handlers(handlers...)
   185  
   186  		converterFunc = func(ctx context.Context, desc ocispec.Descriptor) (ocispec.Descriptor, error) {
   187  			return docker.ConvertManifest(ctx, store, desc)
   188  		}
   189  	}
   190  
   191  	if rCtx.HandlerWrapper != nil {
   192  		handler = rCtx.HandlerWrapper(handler)
   193  	}
   194  
   195  	if rCtx.MaxConcurrentDownloads > 0 {
   196  		limiter = semaphore.NewWeighted(int64(rCtx.MaxConcurrentDownloads))
   197  	}
   198  
   199  	if err := images.Dispatch(ctx, handler, limiter, desc); err != nil {
   200  		return images.Image{}, err
   201  	}
   202  
   203  	if isConvertible {
   204  		if desc, err = converterFunc(ctx, desc); err != nil {
   205  			return images.Image{}, err
   206  		}
   207  	}
   208  
   209  	img := images.Image{
   210  		Name:   name,
   211  		Target: desc,
   212  		Labels: rCtx.Labels,
   213  	}
   214  
   215  	is := c.ImageService()
   216  	for {
   217  		if created, err := is.Create(ctx, img); err != nil {
   218  			if !errdefs.IsAlreadyExists(err) {
   219  				return images.Image{}, err
   220  			}
   221  
   222  			updated, err := is.Update(ctx, img)
   223  			if err != nil {
   224  				// if image was removed, try create again
   225  				if errdefs.IsNotFound(err) {
   226  					continue
   227  				}
   228  				return images.Image{}, err
   229  			}
   230  
   231  			img = updated
   232  		} else {
   233  			img = created
   234  		}
   235  
   236  		return img, nil
   237  	}
   238  }