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 }