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