github.com/demonoid81/containerd@v1.3.4/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 }