github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/distribution/push_v2.go (about) 1 package distribution // import "github.com/docker/docker/distribution" 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "runtime" 9 "sort" 10 "strings" 11 "sync" 12 13 "github.com/docker/distribution" 14 "github.com/docker/distribution/manifest/schema1" 15 "github.com/docker/distribution/manifest/schema2" 16 "github.com/docker/distribution/reference" 17 "github.com/docker/distribution/registry/api/errcode" 18 "github.com/docker/distribution/registry/client" 19 apitypes "github.com/docker/docker/api/types" 20 "github.com/docker/docker/distribution/metadata" 21 "github.com/docker/docker/distribution/xfer" 22 "github.com/docker/docker/layer" 23 "github.com/docker/docker/pkg/ioutils" 24 "github.com/docker/docker/pkg/progress" 25 "github.com/docker/docker/pkg/stringid" 26 "github.com/docker/docker/registry" 27 "github.com/opencontainers/go-digest" 28 "github.com/sirupsen/logrus" 29 ) 30 31 const ( 32 smallLayerMaximumSize = 100 * (1 << 10) // 100KB 33 middleLayerMaximumSize = 10 * (1 << 20) // 10MB 34 ) 35 36 type v2Pusher struct { 37 v2MetadataService metadata.V2MetadataService 38 ref reference.Named 39 endpoint registry.APIEndpoint 40 repoInfo *registry.RepositoryInfo 41 config *ImagePushConfig 42 repo distribution.Repository 43 44 // pushState is state built by the Upload functions. 45 pushState pushState 46 } 47 48 type pushState struct { 49 sync.Mutex 50 // remoteLayers is the set of layers known to exist on the remote side. 51 // This avoids redundant queries when pushing multiple tags that 52 // involve the same layers. It is also used to fill in digest and size 53 // information when building the manifest. 54 remoteLayers map[layer.DiffID]distribution.Descriptor 55 // confirmedV2 is set to true if we confirm we're talking to a v2 56 // registry. This is used to limit fallbacks to the v1 protocol. 57 confirmedV2 bool 58 hasAuthInfo bool 59 } 60 61 func (p *v2Pusher) Push(ctx context.Context) (err error) { 62 p.pushState.remoteLayers = make(map[layer.DiffID]distribution.Descriptor) 63 64 p.repo, p.pushState.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull") 65 p.pushState.hasAuthInfo = p.config.AuthConfig.RegistryToken != "" || (p.config.AuthConfig.Username != "" && p.config.AuthConfig.Password != "") 66 if err != nil { 67 logrus.Debugf("Error getting v2 registry: %v", err) 68 return err 69 } 70 71 if err = p.pushV2Repository(ctx); err != nil { 72 if continueOnError(err, p.endpoint.Mirror) { 73 return fallbackError{ 74 err: err, 75 confirmedV2: p.pushState.confirmedV2, 76 transportOK: true, 77 } 78 } 79 } 80 return err 81 } 82 83 func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) { 84 if namedTagged, isNamedTagged := p.ref.(reference.NamedTagged); isNamedTagged { 85 imageID, err := p.config.ReferenceStore.Get(p.ref) 86 if err != nil { 87 return fmt.Errorf("tag does not exist: %s", reference.FamiliarString(p.ref)) 88 } 89 90 return p.pushV2Tag(ctx, namedTagged, imageID) 91 } 92 93 if !reference.IsNameOnly(p.ref) { 94 return errors.New("cannot push a digest reference") 95 } 96 97 // Pull all tags 98 pushed := 0 99 for _, association := range p.config.ReferenceStore.ReferencesByName(p.ref) { 100 if namedTagged, isNamedTagged := association.Ref.(reference.NamedTagged); isNamedTagged { 101 pushed++ 102 if err := p.pushV2Tag(ctx, namedTagged, association.ID); err != nil { 103 return err 104 } 105 } 106 } 107 108 if pushed == 0 { 109 return fmt.Errorf("no tags to push for %s", reference.FamiliarName(p.repoInfo.Name)) 110 } 111 112 return nil 113 } 114 115 func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id digest.Digest) error { 116 logrus.Debugf("Pushing repository: %s", reference.FamiliarString(ref)) 117 118 imgConfig, err := p.config.ImageStore.Get(id) 119 if err != nil { 120 return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err) 121 } 122 123 rootfs, err := p.config.ImageStore.RootFSFromConfig(imgConfig) 124 if err != nil { 125 return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err) 126 } 127 128 platform, err := p.config.ImageStore.PlatformFromConfig(imgConfig) 129 if err != nil { 130 return fmt.Errorf("unable to get platform for image %s: %s", reference.FamiliarString(ref), err) 131 } 132 133 l, err := p.config.LayerStores[platform.OS].Get(rootfs.ChainID()) 134 if err != nil { 135 return fmt.Errorf("failed to get top layer from image: %v", err) 136 } 137 defer l.Release() 138 139 hmacKey, err := metadata.ComputeV2MetadataHMACKey(p.config.AuthConfig) 140 if err != nil { 141 return fmt.Errorf("failed to compute hmac key of auth config: %v", err) 142 } 143 144 var descriptors []xfer.UploadDescriptor 145 146 descriptorTemplate := v2PushDescriptor{ 147 v2MetadataService: p.v2MetadataService, 148 hmacKey: hmacKey, 149 repoInfo: p.repoInfo.Name, 150 ref: p.ref, 151 endpoint: p.endpoint, 152 repo: p.repo, 153 pushState: &p.pushState, 154 } 155 156 // Loop bounds condition is to avoid pushing the base layer on Windows. 157 for range rootfs.DiffIDs { 158 descriptor := descriptorTemplate 159 descriptor.layer = l 160 descriptor.checkedDigests = make(map[digest.Digest]struct{}) 161 descriptors = append(descriptors, &descriptor) 162 163 l = l.Parent() 164 } 165 166 if err := p.config.UploadManager.Upload(ctx, descriptors, p.config.ProgressOutput); err != nil { 167 return err 168 } 169 170 // Try schema2 first 171 builder := schema2.NewManifestBuilder(p.repo.Blobs(ctx), p.config.ConfigMediaType, imgConfig) 172 manifest, err := manifestFromBuilder(ctx, builder, descriptors) 173 if err != nil { 174 return err 175 } 176 177 manSvc, err := p.repo.Manifests(ctx) 178 if err != nil { 179 return err 180 } 181 182 putOptions := []distribution.ManifestServiceOption{distribution.WithTag(ref.Tag())} 183 if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil { 184 if runtime.GOOS == "windows" || p.config.TrustKey == nil || p.config.RequireSchema2 { 185 logrus.Warnf("failed to upload schema2 manifest: %v", err) 186 return err 187 } 188 189 logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err) 190 191 // Note: this fallback is deprecated, see log messages below 192 manifestRef, err := reference.WithTag(p.repo.Named(), ref.Tag()) 193 if err != nil { 194 return err 195 } 196 builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, manifestRef, imgConfig) 197 manifest, err = manifestFromBuilder(ctx, builder, descriptors) 198 if err != nil { 199 return err 200 } 201 202 if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil { 203 return err 204 } 205 206 // schema2 failed but schema1 succeeded 207 msg := fmt.Sprintf("[DEPRECATION NOTICE] registry v2 schema1 support will be removed in an upcoming release. Please contact admins of the %s registry NOW to avoid future disruption. More information at https://docs.docker.com/registry/spec/deprecated-schema-v1/", reference.Domain(ref)) 208 logrus.Warn(msg) 209 progress.Message(p.config.ProgressOutput, "", msg) 210 } 211 212 var canonicalManifest []byte 213 214 switch v := manifest.(type) { 215 case *schema1.SignedManifest: 216 canonicalManifest = v.Canonical 217 case *schema2.DeserializedManifest: 218 _, canonicalManifest, err = v.Payload() 219 if err != nil { 220 return err 221 } 222 } 223 224 manifestDigest := digest.FromBytes(canonicalManifest) 225 progress.Messagef(p.config.ProgressOutput, "", "%s: digest: %s size: %d", ref.Tag(), manifestDigest, len(canonicalManifest)) 226 227 if err := addDigestReference(p.config.ReferenceStore, ref, manifestDigest, id); err != nil { 228 return err 229 } 230 231 // Signal digest to the trust client so it can sign the 232 // push, if appropriate. 233 progress.Aux(p.config.ProgressOutput, apitypes.PushResult{Tag: ref.Tag(), Digest: manifestDigest.String(), Size: len(canonicalManifest)}) 234 235 return nil 236 } 237 238 func manifestFromBuilder(ctx context.Context, builder distribution.ManifestBuilder, descriptors []xfer.UploadDescriptor) (distribution.Manifest, error) { 239 // descriptors is in reverse order; iterate backwards to get references 240 // appended in the right order. 241 for i := len(descriptors) - 1; i >= 0; i-- { 242 if err := builder.AppendReference(descriptors[i].(*v2PushDescriptor)); err != nil { 243 return nil, err 244 } 245 } 246 247 return builder.Build(ctx) 248 } 249 250 type v2PushDescriptor struct { 251 layer PushLayer 252 v2MetadataService metadata.V2MetadataService 253 hmacKey []byte 254 repoInfo reference.Named 255 ref reference.Named 256 endpoint registry.APIEndpoint 257 repo distribution.Repository 258 pushState *pushState 259 remoteDescriptor distribution.Descriptor 260 // a set of digests whose presence has been checked in a target repository 261 checkedDigests map[digest.Digest]struct{} 262 } 263 264 func (pd *v2PushDescriptor) Key() string { 265 return "v2push:" + pd.ref.Name() + " " + pd.layer.DiffID().String() 266 } 267 268 func (pd *v2PushDescriptor) ID() string { 269 return stringid.TruncateID(pd.layer.DiffID().String()) 270 } 271 272 func (pd *v2PushDescriptor) DiffID() layer.DiffID { 273 return pd.layer.DiffID() 274 } 275 276 func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) { 277 // Skip foreign layers unless this registry allows nondistributable artifacts. 278 if !pd.endpoint.AllowNondistributableArtifacts { 279 if fs, ok := pd.layer.(distribution.Describable); ok { 280 if d := fs.Descriptor(); len(d.URLs) > 0 { 281 progress.Update(progressOutput, pd.ID(), "Skipped foreign layer") 282 return d, nil 283 } 284 } 285 } 286 287 diffID := pd.DiffID() 288 289 pd.pushState.Lock() 290 if descriptor, ok := pd.pushState.remoteLayers[diffID]; ok { 291 // it is already known that the push is not needed and 292 // therefore doing a stat is unnecessary 293 pd.pushState.Unlock() 294 progress.Update(progressOutput, pd.ID(), "Layer already exists") 295 return descriptor, nil 296 } 297 pd.pushState.Unlock() 298 299 maxMountAttempts, maxExistenceChecks, checkOtherRepositories := getMaxMountAndExistenceCheckAttempts(pd.layer) 300 301 // Do we have any metadata associated with this layer's DiffID? 302 v2Metadata, err := pd.v2MetadataService.GetMetadata(diffID) 303 if err == nil { 304 // check for blob existence in the target repository 305 descriptor, exists, err := pd.layerAlreadyExists(ctx, progressOutput, diffID, true, 1, v2Metadata) 306 if exists || err != nil { 307 return descriptor, err 308 } 309 } 310 311 // if digest was empty or not saved, or if blob does not exist on the remote repository, 312 // then push the blob. 313 bs := pd.repo.Blobs(ctx) 314 315 var layerUpload distribution.BlobWriter 316 317 // Attempt to find another repository in the same registry to mount the layer from to avoid an unnecessary upload 318 candidates := getRepositoryMountCandidates(pd.repoInfo, pd.hmacKey, maxMountAttempts, v2Metadata) 319 isUnauthorizedError := false 320 for _, mountCandidate := range candidates { 321 logrus.Debugf("attempting to mount layer %s (%s) from %s", diffID, mountCandidate.Digest, mountCandidate.SourceRepository) 322 createOpts := []distribution.BlobCreateOption{} 323 324 if len(mountCandidate.SourceRepository) > 0 { 325 namedRef, err := reference.ParseNormalizedNamed(mountCandidate.SourceRepository) 326 if err != nil { 327 logrus.Errorf("failed to parse source repository reference %v: %v", reference.FamiliarString(namedRef), err) 328 pd.v2MetadataService.Remove(mountCandidate) 329 continue 330 } 331 332 // Candidates are always under same domain, create remote reference 333 // with only path to set mount from with 334 remoteRef, err := reference.WithName(reference.Path(namedRef)) 335 if err != nil { 336 logrus.Errorf("failed to make remote reference out of %q: %v", reference.Path(namedRef), err) 337 continue 338 } 339 340 canonicalRef, err := reference.WithDigest(reference.TrimNamed(remoteRef), mountCandidate.Digest) 341 if err != nil { 342 logrus.Errorf("failed to make canonical reference: %v", err) 343 continue 344 } 345 346 createOpts = append(createOpts, client.WithMountFrom(canonicalRef)) 347 } 348 349 // send the layer 350 lu, err := bs.Create(ctx, createOpts...) 351 switch err := err.(type) { 352 case nil: 353 // noop 354 case distribution.ErrBlobMounted: 355 progress.Updatef(progressOutput, pd.ID(), "Mounted from %s", err.From.Name()) 356 357 err.Descriptor.MediaType = schema2.MediaTypeLayer 358 359 pd.pushState.Lock() 360 pd.pushState.confirmedV2 = true 361 pd.pushState.remoteLayers[diffID] = err.Descriptor 362 pd.pushState.Unlock() 363 364 // Cache mapping from this layer's DiffID to the blobsum 365 if err := pd.v2MetadataService.TagAndAdd(diffID, pd.hmacKey, metadata.V2Metadata{ 366 Digest: err.Descriptor.Digest, 367 SourceRepository: pd.repoInfo.Name(), 368 }); err != nil { 369 return distribution.Descriptor{}, xfer.DoNotRetry{Err: err} 370 } 371 return err.Descriptor, nil 372 case errcode.Errors: 373 for _, e := range err { 374 switch e := e.(type) { 375 case errcode.Error: 376 if e.Code == errcode.ErrorCodeUnauthorized { 377 // when unauthorized error that indicate user don't has right to push layer to register 378 logrus.Debugln("failed to push layer to registry because unauthorized error") 379 isUnauthorizedError = true 380 } 381 default: 382 } 383 } 384 default: 385 logrus.Infof("failed to mount layer %s (%s) from %s: %v", diffID, mountCandidate.Digest, mountCandidate.SourceRepository, err) 386 } 387 388 // when error is unauthorizedError and user don't hasAuthInfo that's the case user don't has right to push layer to register 389 // and he hasn't login either, in this case candidate cache should be removed 390 if len(mountCandidate.SourceRepository) > 0 && 391 !(isUnauthorizedError && !pd.pushState.hasAuthInfo) && 392 (metadata.CheckV2MetadataHMAC(&mountCandidate, pd.hmacKey) || 393 len(mountCandidate.HMAC) == 0) { 394 cause := "blob mount failure" 395 if err != nil { 396 cause = fmt.Sprintf("an error: %v", err.Error()) 397 } 398 logrus.Debugf("removing association between layer %s and %s due to %s", mountCandidate.Digest, mountCandidate.SourceRepository, cause) 399 pd.v2MetadataService.Remove(mountCandidate) 400 } 401 402 if lu != nil { 403 // cancel previous upload 404 cancelLayerUpload(ctx, mountCandidate.Digest, layerUpload) 405 layerUpload = lu 406 } 407 } 408 409 if maxExistenceChecks-len(pd.checkedDigests) > 0 { 410 // do additional layer existence checks with other known digests if any 411 descriptor, exists, err := pd.layerAlreadyExists(ctx, progressOutput, diffID, checkOtherRepositories, maxExistenceChecks-len(pd.checkedDigests), v2Metadata) 412 if exists || err != nil { 413 return descriptor, err 414 } 415 } 416 417 logrus.Debugf("Pushing layer: %s", diffID) 418 if layerUpload == nil { 419 layerUpload, err = bs.Create(ctx) 420 if err != nil { 421 return distribution.Descriptor{}, retryOnError(err) 422 } 423 } 424 defer layerUpload.Close() 425 // upload the blob 426 return pd.uploadUsingSession(ctx, progressOutput, diffID, layerUpload) 427 } 428 429 func (pd *v2PushDescriptor) SetRemoteDescriptor(descriptor distribution.Descriptor) { 430 pd.remoteDescriptor = descriptor 431 } 432 433 func (pd *v2PushDescriptor) Descriptor() distribution.Descriptor { 434 return pd.remoteDescriptor 435 } 436 437 func (pd *v2PushDescriptor) uploadUsingSession( 438 ctx context.Context, 439 progressOutput progress.Output, 440 diffID layer.DiffID, 441 layerUpload distribution.BlobWriter, 442 ) (distribution.Descriptor, error) { 443 var reader io.ReadCloser 444 445 contentReader, err := pd.layer.Open() 446 if err != nil { 447 return distribution.Descriptor{}, retryOnError(err) 448 } 449 450 size, _ := pd.layer.Size() 451 452 reader = progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, contentReader), progressOutput, size, pd.ID(), "Pushing") 453 454 switch m := pd.layer.MediaType(); m { 455 case schema2.MediaTypeUncompressedLayer: 456 compressedReader, compressionDone := compress(reader) 457 defer func(closer io.Closer) { 458 closer.Close() 459 <-compressionDone 460 }(reader) 461 reader = compressedReader 462 case schema2.MediaTypeLayer: 463 default: 464 reader.Close() 465 return distribution.Descriptor{}, fmt.Errorf("unsupported layer media type %s", m) 466 } 467 468 digester := digest.Canonical.Digester() 469 tee := io.TeeReader(reader, digester.Hash()) 470 471 nn, err := layerUpload.ReadFrom(tee) 472 reader.Close() 473 if err != nil { 474 return distribution.Descriptor{}, retryOnError(err) 475 } 476 477 pushDigest := digester.Digest() 478 if _, err := layerUpload.Commit(ctx, distribution.Descriptor{Digest: pushDigest}); err != nil { 479 return distribution.Descriptor{}, retryOnError(err) 480 } 481 482 logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn) 483 progress.Update(progressOutput, pd.ID(), "Pushed") 484 485 // Cache mapping from this layer's DiffID to the blobsum 486 if err := pd.v2MetadataService.TagAndAdd(diffID, pd.hmacKey, metadata.V2Metadata{ 487 Digest: pushDigest, 488 SourceRepository: pd.repoInfo.Name(), 489 }); err != nil { 490 return distribution.Descriptor{}, xfer.DoNotRetry{Err: err} 491 } 492 493 desc := distribution.Descriptor{ 494 Digest: pushDigest, 495 MediaType: schema2.MediaTypeLayer, 496 Size: nn, 497 } 498 499 pd.pushState.Lock() 500 // If Commit succeeded, that's an indication that the remote registry speaks the v2 protocol. 501 pd.pushState.confirmedV2 = true 502 pd.pushState.remoteLayers[diffID] = desc 503 pd.pushState.Unlock() 504 505 return desc, nil 506 } 507 508 // layerAlreadyExists checks if the registry already knows about any of the metadata passed in the "metadata" 509 // slice. If it finds one that the registry knows about, it returns the known digest and "true". If 510 // "checkOtherRepositories" is true, stat will be performed also with digests mapped to any other repository 511 // (not just the target one). 512 func (pd *v2PushDescriptor) layerAlreadyExists( 513 ctx context.Context, 514 progressOutput progress.Output, 515 diffID layer.DiffID, 516 checkOtherRepositories bool, 517 maxExistenceCheckAttempts int, 518 v2Metadata []metadata.V2Metadata, 519 ) (desc distribution.Descriptor, exists bool, err error) { 520 // filter the metadata 521 candidates := []metadata.V2Metadata{} 522 for _, meta := range v2Metadata { 523 if len(meta.SourceRepository) > 0 && !checkOtherRepositories && meta.SourceRepository != pd.repoInfo.Name() { 524 continue 525 } 526 candidates = append(candidates, meta) 527 } 528 // sort the candidates by similarity 529 sortV2MetadataByLikenessAndAge(pd.repoInfo, pd.hmacKey, candidates) 530 531 digestToMetadata := make(map[digest.Digest]*metadata.V2Metadata) 532 // an array of unique blob digests ordered from the best mount candidates to worst 533 layerDigests := []digest.Digest{} 534 for i := 0; i < len(candidates); i++ { 535 if len(layerDigests) >= maxExistenceCheckAttempts { 536 break 537 } 538 meta := &candidates[i] 539 if _, exists := digestToMetadata[meta.Digest]; exists { 540 // keep reference just to the first mapping (the best mount candidate) 541 continue 542 } 543 if _, exists := pd.checkedDigests[meta.Digest]; exists { 544 // existence of this digest has already been tested 545 continue 546 } 547 digestToMetadata[meta.Digest] = meta 548 layerDigests = append(layerDigests, meta.Digest) 549 } 550 551 attempts: 552 for _, dgst := range layerDigests { 553 meta := digestToMetadata[dgst] 554 logrus.Debugf("Checking for presence of layer %s (%s) in %s", diffID, dgst, pd.repoInfo.Name()) 555 desc, err = pd.repo.Blobs(ctx).Stat(ctx, dgst) 556 pd.checkedDigests[meta.Digest] = struct{}{} 557 switch err { 558 case nil: 559 if m, ok := digestToMetadata[desc.Digest]; !ok || m.SourceRepository != pd.repoInfo.Name() || !metadata.CheckV2MetadataHMAC(m, pd.hmacKey) { 560 // cache mapping from this layer's DiffID to the blobsum 561 if err := pd.v2MetadataService.TagAndAdd(diffID, pd.hmacKey, metadata.V2Metadata{ 562 Digest: desc.Digest, 563 SourceRepository: pd.repoInfo.Name(), 564 }); err != nil { 565 return distribution.Descriptor{}, false, xfer.DoNotRetry{Err: err} 566 } 567 } 568 desc.MediaType = schema2.MediaTypeLayer 569 exists = true 570 break attempts 571 case distribution.ErrBlobUnknown: 572 if meta.SourceRepository == pd.repoInfo.Name() { 573 // remove the mapping to the target repository 574 pd.v2MetadataService.Remove(*meta) 575 } 576 default: 577 logrus.WithError(err).Debugf("Failed to check for presence of layer %s (%s) in %s", diffID, dgst, pd.repoInfo.Name()) 578 } 579 } 580 581 if exists { 582 progress.Update(progressOutput, pd.ID(), "Layer already exists") 583 pd.pushState.Lock() 584 pd.pushState.remoteLayers[diffID] = desc 585 pd.pushState.Unlock() 586 } 587 588 return desc, exists, nil 589 } 590 591 // getMaxMountAndExistenceCheckAttempts returns a maximum number of cross repository mount attempts from 592 // source repositories of target registry, maximum number of layer existence checks performed on the target 593 // repository and whether the check shall be done also with digests mapped to different repositories. The 594 // decision is based on layer size. The smaller the layer, the fewer attempts shall be made because the cost 595 // of upload does not outweigh a latency. 596 func getMaxMountAndExistenceCheckAttempts(layer PushLayer) (maxMountAttempts, maxExistenceCheckAttempts int, checkOtherRepositories bool) { 597 size, err := layer.Size() 598 switch { 599 // big blob 600 case size > middleLayerMaximumSize: 601 // 1st attempt to mount the blob few times 602 // 2nd few existence checks with digests associated to any repository 603 // then fallback to upload 604 return 4, 3, true 605 606 // middle sized blobs; if we could not get the size, assume we deal with middle sized blob 607 case size > smallLayerMaximumSize, err != nil: 608 // 1st attempt to mount blobs of average size few times 609 // 2nd try at most 1 existence check if there's an existing mapping to the target repository 610 // then fallback to upload 611 return 3, 1, false 612 613 // small blobs, do a minimum number of checks 614 default: 615 return 1, 1, false 616 } 617 } 618 619 // getRepositoryMountCandidates returns an array of v2 metadata items belonging to the given registry. The 620 // array is sorted from youngest to oldest. If requireRegistryMatch is true, the resulting array will contain 621 // only metadata entries having registry part of SourceRepository matching the part of repoInfo. 622 func getRepositoryMountCandidates( 623 repoInfo reference.Named, 624 hmacKey []byte, 625 max int, 626 v2Metadata []metadata.V2Metadata, 627 ) []metadata.V2Metadata { 628 candidates := []metadata.V2Metadata{} 629 for _, meta := range v2Metadata { 630 sourceRepo, err := reference.ParseNamed(meta.SourceRepository) 631 if err != nil || reference.Domain(repoInfo) != reference.Domain(sourceRepo) { 632 continue 633 } 634 // target repository is not a viable candidate 635 if meta.SourceRepository == repoInfo.Name() { 636 continue 637 } 638 candidates = append(candidates, meta) 639 } 640 641 sortV2MetadataByLikenessAndAge(repoInfo, hmacKey, candidates) 642 if max >= 0 && len(candidates) > max { 643 // select the youngest metadata 644 candidates = candidates[:max] 645 } 646 647 return candidates 648 } 649 650 // byLikeness is a sorting container for v2 metadata candidates for cross repository mount. The 651 // candidate "a" is preferred over "b": 652 // 653 // 1. if it was hashed using the same AuthConfig as the one used to authenticate to target repository and the 654 // "b" was not 655 // 2. if a number of its repository path components exactly matching path components of target repository is higher 656 type byLikeness struct { 657 arr []metadata.V2Metadata 658 hmacKey []byte 659 pathComponents []string 660 } 661 662 func (bla byLikeness) Less(i, j int) bool { 663 aMacMatch := metadata.CheckV2MetadataHMAC(&bla.arr[i], bla.hmacKey) 664 bMacMatch := metadata.CheckV2MetadataHMAC(&bla.arr[j], bla.hmacKey) 665 if aMacMatch != bMacMatch { 666 return aMacMatch 667 } 668 aMatch := numOfMatchingPathComponents(bla.arr[i].SourceRepository, bla.pathComponents) 669 bMatch := numOfMatchingPathComponents(bla.arr[j].SourceRepository, bla.pathComponents) 670 return aMatch > bMatch 671 } 672 func (bla byLikeness) Swap(i, j int) { 673 bla.arr[i], bla.arr[j] = bla.arr[j], bla.arr[i] 674 } 675 func (bla byLikeness) Len() int { return len(bla.arr) } 676 677 func sortV2MetadataByLikenessAndAge(repoInfo reference.Named, hmacKey []byte, marr []metadata.V2Metadata) { 678 // reverse the metadata array to shift the newest entries to the beginning 679 for i := 0; i < len(marr)/2; i++ { 680 marr[i], marr[len(marr)-i-1] = marr[len(marr)-i-1], marr[i] 681 } 682 // keep equal entries ordered from the youngest to the oldest 683 sort.Stable(byLikeness{ 684 arr: marr, 685 hmacKey: hmacKey, 686 pathComponents: getPathComponents(repoInfo.Name()), 687 }) 688 } 689 690 // numOfMatchingPathComponents returns a number of path components in "pth" that exactly match "matchComponents". 691 func numOfMatchingPathComponents(pth string, matchComponents []string) int { 692 pthComponents := getPathComponents(pth) 693 i := 0 694 for ; i < len(pthComponents) && i < len(matchComponents); i++ { 695 if matchComponents[i] != pthComponents[i] { 696 return i 697 } 698 } 699 return i 700 } 701 702 func getPathComponents(path string) []string { 703 return strings.Split(path, "/") 704 } 705 706 func cancelLayerUpload(ctx context.Context, dgst digest.Digest, layerUpload distribution.BlobWriter) { 707 if layerUpload != nil { 708 logrus.Debugf("cancelling upload of blob %s", dgst) 709 err := layerUpload.Cancel(ctx) 710 if err != nil { 711 logrus.Warnf("failed to cancel upload: %v", err) 712 } 713 } 714 }