zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/storage/common/common.go (about) 1 package storage 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "math/rand" 10 "path" 11 "strings" 12 "time" 13 14 "github.com/docker/distribution/registry/storage/driver" 15 godigest "github.com/opencontainers/go-digest" 16 "github.com/opencontainers/image-spec/schema" 17 imeta "github.com/opencontainers/image-spec/specs-go" 18 ispec "github.com/opencontainers/image-spec/specs-go/v1" 19 20 zerr "zotregistry.dev/zot/errors" 21 zcommon "zotregistry.dev/zot/pkg/common" 22 "zotregistry.dev/zot/pkg/extensions/monitoring" 23 zlog "zotregistry.dev/zot/pkg/log" 24 "zotregistry.dev/zot/pkg/scheduler" 25 storageConstants "zotregistry.dev/zot/pkg/storage/constants" 26 storageTypes "zotregistry.dev/zot/pkg/storage/types" 27 ) 28 29 const ( 30 manifestWithEmptyLayersErrMsg = "layers: Array must have at least 1 items" 31 cosignSignatureTagSuffix = "sig" 32 ) 33 34 func GetTagsByIndex(index ispec.Index) []string { 35 tags := make([]string, 0) 36 37 for _, manifest := range index.Manifests { 38 v, ok := manifest.Annotations[ispec.AnnotationRefName] 39 if ok { 40 tags = append(tags, v) 41 } 42 } 43 44 return tags 45 } 46 47 func GetManifestDescByReference(index ispec.Index, reference string) (ispec.Descriptor, bool) { 48 var manifestDesc ispec.Descriptor 49 50 for _, manifest := range index.Manifests { 51 if reference == manifest.Digest.String() { 52 return manifest, true 53 } 54 55 v, ok := manifest.Annotations[ispec.AnnotationRefName] 56 if ok && v == reference { 57 return manifest, true 58 } 59 } 60 61 return manifestDesc, false 62 } 63 64 func ValidateManifest(imgStore storageTypes.ImageStore, repo, reference, mediaType string, body []byte, 65 log zlog.Logger, 66 ) (godigest.Digest, error) { 67 // validate the manifest 68 if !IsSupportedMediaType(mediaType) { 69 log.Debug().Interface("actual", mediaType). 70 Msg("bad manifest media type") 71 72 return "", zerr.ErrBadManifest 73 } 74 75 if len(body) == 0 { 76 log.Debug().Int("len", len(body)).Msg("invalid body length") 77 78 return "", zerr.ErrBadManifest 79 } 80 81 switch mediaType { 82 case ispec.MediaTypeImageManifest: 83 var manifest ispec.Manifest 84 85 // validate manifest 86 if err := ValidateManifestSchema(body); err != nil { 87 log.Error().Err(err).Msg("failed to validate OCIv1 image manifest schema") 88 89 return "", zerr.NewError(zerr.ErrBadManifest).AddDetail("jsonSchemaValidation", err.Error()) 90 } 91 92 if err := json.Unmarshal(body, &manifest); err != nil { 93 log.Error().Err(err).Msg("failed to unmarshal JSON") 94 95 return "", zerr.ErrBadManifest 96 } 97 98 // validate blobs only for known media types 99 if manifest.Config.MediaType == ispec.MediaTypeImageConfig || 100 manifest.Config.MediaType == ispec.MediaTypeEmptyJSON { 101 // validate config blob - a lightweight check if the blob is present 102 ok, _, _, err := imgStore.StatBlob(repo, manifest.Config.Digest) 103 if !ok || err != nil { 104 log.Error().Err(err).Str("digest", manifest.Config.Digest.String()). 105 Msg("failed to stat blob due to missing config blob") 106 107 return "", zerr.ErrBadManifest 108 } 109 110 // validate layers - a lightweight check if the blob is present 111 for _, layer := range manifest.Layers { 112 if IsNonDistributable(layer.MediaType) { 113 log.Debug().Str("digest", layer.Digest.String()).Str("mediaType", layer.MediaType). 114 Msg("skip checking non-distributable layer exists") 115 116 continue 117 } 118 119 ok, _, _, err := imgStore.StatBlob(repo, layer.Digest) 120 if !ok || err != nil { 121 log.Error().Err(err).Str("digest", layer.Digest.String()). 122 Msg("failed to validate manifest due to missing layer blob") 123 124 return "", zerr.ErrBadManifest 125 } 126 } 127 } 128 case ispec.MediaTypeImageIndex: 129 // validate manifest 130 if err := ValidateImageIndexSchema(body); err != nil { 131 log.Error().Err(err).Msg("failed to validate OCIv1 image index manifest schema") 132 133 return "", zerr.NewError(zerr.ErrBadManifest).AddDetail("jsonSchemaValidation", err.Error()) 134 } 135 136 var indexManifest ispec.Index 137 if err := json.Unmarshal(body, &indexManifest); err != nil { 138 log.Error().Err(err).Msg("failed to unmarshal JSON") 139 140 return "", zerr.ErrBadManifest 141 } 142 143 for _, manifest := range indexManifest.Manifests { 144 if ok, _, _, err := imgStore.StatBlob(repo, manifest.Digest); !ok || err != nil { 145 log.Error().Err(err).Str("digest", manifest.Digest.String()). 146 Msg("failed to stat manifest due to missing manifest blob") 147 148 return "", zerr.ErrBadManifest 149 } 150 } 151 } 152 153 return "", nil 154 } 155 156 func GetAndValidateRequestDigest(body []byte, digestStr string, log zlog.Logger) (godigest.Digest, error) { 157 bodyDigest := godigest.FromBytes(body) 158 159 d, err := godigest.Parse(digestStr) 160 if err == nil { 161 if d.String() != bodyDigest.String() { 162 log.Error().Str("actual", bodyDigest.String()).Str("expected", d.String()). 163 Msg("failed to validate manifest digest") 164 165 return "", zerr.ErrBadManifest 166 } 167 } 168 169 return bodyDigest, err 170 } 171 172 /* 173 CheckIfIndexNeedsUpdate verifies if an index needs to be updated given a new manifest descriptor. 174 175 Returns whether or not index needs update, in the latter case it will also return the previous digest. 176 */ 177 func CheckIfIndexNeedsUpdate(index *ispec.Index, desc *ispec.Descriptor, 178 log zlog.Logger, 179 ) (bool, godigest.Digest, error) { 180 var oldDgst godigest.Digest 181 182 var reference string 183 184 tag, ok := desc.Annotations[ispec.AnnotationRefName] 185 if ok { 186 reference = tag 187 } else { 188 reference = desc.Digest.String() 189 } 190 191 updateIndex := true 192 193 for midx, manifest := range index.Manifests { 194 manifest := manifest 195 if reference == manifest.Digest.String() { 196 // nothing changed, so don't update 197 updateIndex = false 198 199 break 200 } 201 202 v, ok := manifest.Annotations[ispec.AnnotationRefName] 203 if ok && v == reference { 204 if manifest.Digest.String() == desc.Digest.String() { 205 // nothing changed, so don't update 206 updateIndex = false 207 208 break 209 } 210 211 // manifest contents have changed for the same tag, 212 // so update index.json descriptor 213 log.Info(). 214 Int64("old size", manifest.Size). 215 Int64("new size", desc.Size). 216 Str("old digest", manifest.Digest.String()). 217 Str("new digest", desc.Digest.String()). 218 Str("old mediaType", manifest.MediaType). 219 Str("new mediaType", desc.MediaType). 220 Msg("updating existing tag with new manifest contents") 221 222 // changing media-type is disallowed! 223 if manifest.MediaType != desc.MediaType { 224 err := zerr.ErrBadManifest 225 log.Error().Err(err). 226 Str("old mediaType", manifest.MediaType). 227 Str("new mediaType", desc.MediaType).Msg("cannot change media-type") 228 reason := fmt.Sprintf("changing manifest media-type from \"%s\" to \"%s\" is disallowed", 229 manifest.MediaType, desc.MediaType) 230 231 return false, "", zerr.NewError(err).AddDetail("reason", reason) 232 } 233 234 oldDesc := *desc 235 236 desc = &manifest 237 oldDgst = manifest.Digest 238 desc.Size = oldDesc.Size 239 desc.Digest = oldDesc.Digest 240 241 index.Manifests = append(index.Manifests[:midx], index.Manifests[midx+1:]...) 242 243 break 244 } 245 } 246 247 return updateIndex, oldDgst, nil 248 } 249 250 // GetIndex returns the contents of index.json. 251 func GetIndex(imgStore storageTypes.ImageStore, repo string, log zlog.Logger) (ispec.Index, error) { 252 var index ispec.Index 253 254 buf, err := imgStore.GetIndexContent(repo) 255 if err != nil { 256 if errors.As(err, &driver.PathNotFoundError{}) { 257 return index, zerr.ErrRepoNotFound 258 } 259 260 return index, err 261 } 262 263 if err := json.Unmarshal(buf, &index); err != nil { 264 log.Error().Err(err).Str("dir", path.Join(imgStore.RootDir(), repo)).Msg("invalid JSON") 265 266 return index, zerr.ErrRepoBadVersion 267 } 268 269 return index, nil 270 } 271 272 // GetImageIndex returns a multiarch type image. 273 func GetImageIndex(imgStore storageTypes.ImageStore, repo string, digest godigest.Digest, log zlog.Logger, 274 ) (ispec.Index, error) { 275 var imageIndex ispec.Index 276 277 if err := digest.Validate(); err != nil { 278 return imageIndex, err 279 } 280 281 buf, err := imgStore.GetBlobContent(repo, digest) 282 if err != nil { 283 return imageIndex, err 284 } 285 286 indexPath := path.Join(imgStore.RootDir(), repo, "blobs", 287 digest.Algorithm().String(), digest.Encoded()) 288 289 if err := json.Unmarshal(buf, &imageIndex); err != nil { 290 log.Error().Err(err).Str("path", indexPath).Msg("invalid JSON") 291 292 return imageIndex, err 293 } 294 295 return imageIndex, nil 296 } 297 298 func GetImageManifest(imgStore storageTypes.ImageStore, repo string, digest godigest.Digest, log zlog.Logger, 299 ) (ispec.Manifest, error) { 300 var manifestContent ispec.Manifest 301 302 manifestBlob, err := imgStore.GetBlobContent(repo, digest) 303 if err != nil { 304 return manifestContent, err 305 } 306 307 manifestPath := path.Join(imgStore.RootDir(), repo, "blobs", 308 digest.Algorithm().String(), digest.Encoded()) 309 310 if err := json.Unmarshal(manifestBlob, &manifestContent); err != nil { 311 log.Error().Err(err).Str("path", manifestPath).Msg("invalid JSON") 312 313 return manifestContent, err 314 } 315 316 return manifestContent, nil 317 } 318 319 func RemoveManifestDescByReference(index *ispec.Index, reference string, detectCollisions bool, 320 ) (ispec.Descriptor, error) { 321 var removedManifest ispec.Descriptor 322 323 var found bool 324 325 foundCount := 0 326 327 var outIndex ispec.Index 328 329 for _, manifest := range index.Manifests { 330 tag, ok := manifest.Annotations[ispec.AnnotationRefName] 331 if ok && tag == reference { 332 removedManifest = manifest 333 found = true 334 foundCount++ 335 336 continue 337 } else if reference == manifest.Digest.String() { 338 removedManifest = manifest 339 found = true 340 foundCount++ 341 342 continue 343 } 344 345 outIndex.Manifests = append(outIndex.Manifests, manifest) 346 } 347 348 if foundCount > 1 && detectCollisions { 349 return ispec.Descriptor{}, zerr.ErrManifestConflict 350 } else if !found { 351 return ispec.Descriptor{}, zerr.ErrManifestNotFound 352 } 353 354 index.Manifests = outIndex.Manifests 355 356 return removedManifest, nil 357 } 358 359 /* 360 Unmarshal an image index and for all manifests in that 361 index, ensure that they do not have a name or they are not in other 362 manifest indexes else GC can never clean them. 363 */ 364 func UpdateIndexWithPrunedImageManifests(imgStore storageTypes.ImageStore, index *ispec.Index, repo string, 365 desc ispec.Descriptor, oldDgst godigest.Digest, log zlog.Logger, 366 ) error { 367 if (desc.MediaType == ispec.MediaTypeImageIndex) && (oldDgst != "") { 368 otherImgIndexes := []ispec.Descriptor{} 369 370 for _, manifest := range index.Manifests { 371 if manifest.MediaType == ispec.MediaTypeImageIndex { 372 otherImgIndexes = append(otherImgIndexes, manifest) 373 } 374 } 375 376 otherImgIndexes = append(otherImgIndexes, desc) 377 378 prunedManifests, err := PruneImageManifestsFromIndex(imgStore, repo, oldDgst, *index, otherImgIndexes, log) 379 if err != nil { 380 return err 381 } 382 383 index.Manifests = prunedManifests 384 } 385 386 return nil 387 } 388 389 /* 390 Before an image index manifest is pushed to a repo, its constituent manifests 391 are pushed first, so when updating/removing this image index manifest, we also 392 need to determine if there are other image index manifests which refer to the 393 same constitutent manifests so that they can be garbage-collected correctly 394 395 PruneImageManifestsFromIndex is a helper routine to achieve this. 396 */ 397 func PruneImageManifestsFromIndex(imgStore storageTypes.ImageStore, repo string, digest godigest.Digest, //nolint:gocyclo,lll 398 outIndex ispec.Index, otherImgIndexes []ispec.Descriptor, log zlog.Logger, 399 ) ([]ispec.Descriptor, error) { 400 dir := path.Join(imgStore.RootDir(), repo) 401 402 indexPath := path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded()) 403 404 buf, err := imgStore.GetBlobContent(repo, digest) 405 if err != nil { 406 return nil, err 407 } 408 409 var imgIndex ispec.Index 410 if err := json.Unmarshal(buf, &imgIndex); err != nil { 411 log.Error().Err(err).Str("path", indexPath).Msg("invalid JSON") 412 413 return nil, err 414 } 415 416 inUse := map[string]uint{} 417 418 for _, manifest := range imgIndex.Manifests { 419 inUse[manifest.Digest.Encoded()]++ 420 } 421 422 for _, otherIndex := range otherImgIndexes { 423 oindex, err := GetImageIndex(imgStore, repo, otherIndex.Digest, log) 424 if err != nil { 425 return nil, err 426 } 427 428 for _, omanifest := range oindex.Manifests { 429 _, ok := inUse[omanifest.Digest.Encoded()] 430 if ok { 431 inUse[omanifest.Digest.Encoded()]++ 432 } 433 } 434 } 435 436 prunedManifests := []ispec.Descriptor{} 437 438 // for all manifests in the index, skip those that either have a tag or 439 // are used in other imgIndexes 440 for _, outManifest := range outIndex.Manifests { 441 if outManifest.MediaType != ispec.MediaTypeImageManifest { 442 prunedManifests = append(prunedManifests, outManifest) 443 444 continue 445 } 446 447 _, ok := outManifest.Annotations[ispec.AnnotationRefName] 448 if ok { 449 prunedManifests = append(prunedManifests, outManifest) 450 451 continue 452 } 453 454 count, ok := inUse[outManifest.Digest.Encoded()] 455 if !ok { 456 prunedManifests = append(prunedManifests, outManifest) 457 458 continue 459 } 460 461 if count != 1 { 462 // this manifest is in use in other image indexes 463 prunedManifests = append(prunedManifests, outManifest) 464 465 continue 466 } 467 } 468 469 return prunedManifests, nil 470 } 471 472 func isBlobReferencedInImageManifest(imgStore storageTypes.ImageStore, repo string, 473 bdigest, mdigest godigest.Digest, log zlog.Logger, 474 ) (bool, error) { 475 if bdigest == mdigest { 476 return true, nil 477 } 478 479 manifestContent, err := GetImageManifest(imgStore, repo, mdigest, log) 480 if err != nil { 481 log.Error().Err(err).Str("repo", repo).Str("digest", mdigest.String()).Str("component", "gc"). 482 Msg("failed to read manifest image") 483 484 return false, err 485 } 486 487 if bdigest == manifestContent.Config.Digest { 488 return true, nil 489 } 490 491 for _, layer := range manifestContent.Layers { 492 if bdigest == layer.Digest { 493 return true, nil 494 } 495 } 496 497 return false, nil 498 } 499 500 func IsBlobReferencedInImageIndex(imgStore storageTypes.ImageStore, repo string, 501 digest godigest.Digest, index ispec.Index, log zlog.Logger, 502 ) (bool, error) { 503 for _, desc := range index.Manifests { 504 var found bool 505 506 switch desc.MediaType { 507 case ispec.MediaTypeImageIndex: 508 indexImage, err := GetImageIndex(imgStore, repo, desc.Digest, log) 509 if err != nil { 510 log.Error().Err(err).Str("repository", repo).Str("digest", desc.Digest.String()). 511 Msg("failed to read multiarch(index) image") 512 513 return false, err 514 } 515 516 found, _ = IsBlobReferencedInImageIndex(imgStore, repo, digest, indexImage, log) 517 case ispec.MediaTypeImageManifest: 518 found, _ = isBlobReferencedInImageManifest(imgStore, repo, digest, desc.Digest, log) 519 default: 520 log.Warn().Str("mediatype", desc.MediaType).Msg("unknown media-type") 521 // should return true for digests found in index.json even if we don't know it's mediatype 522 if digest == desc.Digest { 523 found = true 524 } 525 } 526 527 if found { 528 return true, nil 529 } 530 } 531 532 return false, nil 533 } 534 535 func IsBlobReferenced(imgStore storageTypes.ImageStore, repo string, 536 digest godigest.Digest, log zlog.Logger, 537 ) (bool, error) { 538 dir := path.Join(imgStore.RootDir(), repo) 539 if !imgStore.DirExists(dir) { 540 return false, zerr.ErrRepoNotFound 541 } 542 543 index, err := GetIndex(imgStore, repo, log) 544 if err != nil { 545 return false, err 546 } 547 548 return IsBlobReferencedInImageIndex(imgStore, repo, digest, index, log) 549 } 550 551 func ApplyLinter(imgStore storageTypes.ImageStore, linter Lint, repo string, descriptor ispec.Descriptor, 552 ) (bool, error) { 553 pass := true 554 555 // we'll skip anything that's not a image manifest 556 if descriptor.MediaType != ispec.MediaTypeImageManifest { 557 return pass, nil 558 } 559 560 if linter != nil && !IsSignature(descriptor) { 561 // lint new index with new manifest before writing to disk 562 pass, err := linter.Lint(repo, descriptor.Digest, imgStore) 563 if err != nil { 564 return false, err 565 } 566 567 if !pass { 568 return false, zerr.ErrImageLintAnnotations 569 } 570 } 571 572 return pass, nil 573 } 574 575 func IsSignature(descriptor ispec.Descriptor) bool { 576 tag := descriptor.Annotations[ispec.AnnotationRefName] 577 578 switch descriptor.MediaType { 579 case ispec.MediaTypeImageManifest: 580 // is cosgin signature 581 if strings.HasPrefix(tag, "sha256-") && strings.HasSuffix(tag, cosignSignatureTagSuffix) { 582 return true 583 } 584 585 // is cosign signature (OCI 1.1 support) 586 if descriptor.ArtifactType == zcommon.ArtifactTypeCosign { 587 return true 588 } 589 590 // is notation signature 591 if descriptor.ArtifactType == zcommon.ArtifactTypeNotation { 592 return true 593 } 594 default: 595 return false 596 } 597 598 return false 599 } 600 601 func GetReferrers(imgStore storageTypes.ImageStore, repo string, gdigest godigest.Digest, artifactTypes []string, 602 log zlog.Logger, 603 ) (ispec.Index, error) { 604 nilIndex := ispec.Index{} 605 606 if err := gdigest.Validate(); err != nil { 607 return nilIndex, err 608 } 609 610 dir := path.Join(imgStore.RootDir(), repo) 611 if !imgStore.DirExists(dir) { 612 return nilIndex, zerr.ErrRepoNotFound 613 } 614 615 index, err := GetIndex(imgStore, repo, log) 616 if err != nil { 617 return nilIndex, err 618 } 619 620 result := []ispec.Descriptor{} 621 622 for _, descriptor := range index.Manifests { 623 if descriptor.Digest == gdigest { 624 continue 625 } 626 627 buf, err := imgStore.GetBlobContent(repo, descriptor.Digest) 628 if err != nil { 629 log.Error().Err(err).Str("blob", imgStore.BlobPath(repo, descriptor.Digest)).Msg("failed to read manifest") 630 631 if errors.Is(err, zerr.ErrBlobNotFound) { 632 return nilIndex, zerr.ErrManifestNotFound 633 } 634 635 return nilIndex, err 636 } 637 638 switch descriptor.MediaType { 639 case ispec.MediaTypeImageManifest: 640 var manifestContent ispec.Manifest 641 642 if err := json.Unmarshal(buf, &manifestContent); err != nil { 643 log.Error().Err(err).Str("manifest digest", descriptor.Digest.String()).Msg("invalid JSON") 644 645 return nilIndex, err 646 } 647 648 if manifestContent.Subject == nil || manifestContent.Subject.Digest != gdigest { 649 continue 650 } 651 652 // filter by artifact type 653 manifestArtifactType := zcommon.GetManifestArtifactType(manifestContent) 654 655 if len(artifactTypes) > 0 && !zcommon.Contains(artifactTypes, manifestArtifactType) { 656 continue 657 } 658 659 result = append(result, ispec.Descriptor{ 660 MediaType: descriptor.MediaType, 661 ArtifactType: manifestArtifactType, 662 Size: descriptor.Size, 663 Digest: descriptor.Digest, 664 Annotations: manifestContent.Annotations, 665 }) 666 case ispec.MediaTypeImageIndex: 667 var indexContent ispec.Index 668 669 if err := json.Unmarshal(buf, &indexContent); err != nil { 670 log.Error().Err(err).Str("manifest digest", descriptor.Digest.String()).Msg("invalid JSON") 671 672 return nilIndex, err 673 } 674 675 if indexContent.Subject == nil || indexContent.Subject.Digest != gdigest { 676 continue 677 } 678 679 indexArtifactType := zcommon.GetIndexArtifactType(indexContent) 680 681 if len(artifactTypes) > 0 && !zcommon.Contains(artifactTypes, indexArtifactType) { 682 continue 683 } 684 685 result = append(result, ispec.Descriptor{ 686 MediaType: descriptor.MediaType, 687 ArtifactType: indexArtifactType, 688 Size: descriptor.Size, 689 Digest: descriptor.Digest, 690 Annotations: indexContent.Annotations, 691 }) 692 } 693 } 694 695 index = ispec.Index{ 696 Versioned: imeta.Versioned{SchemaVersion: storageConstants.SchemaVersion}, 697 MediaType: ispec.MediaTypeImageIndex, 698 Manifests: result, 699 Annotations: map[string]string{}, 700 } 701 702 return index, nil 703 } 704 705 // Get blob descriptor from it's manifest contents, if blob can not be found it will return error. 706 func GetBlobDescriptorFromRepo(imgStore storageTypes.ImageStore, repo string, blobDigest godigest.Digest, 707 log zlog.Logger, 708 ) (ispec.Descriptor, error) { 709 index, err := GetIndex(imgStore, repo, log) 710 if err != nil { 711 return ispec.Descriptor{}, err 712 } 713 714 return GetBlobDescriptorFromIndex(imgStore, index, repo, blobDigest, log) 715 } 716 717 func GetBlobDescriptorFromIndex(imgStore storageTypes.ImageStore, index ispec.Index, repo string, 718 blobDigest godigest.Digest, log zlog.Logger, 719 ) (ispec.Descriptor, error) { 720 for _, desc := range index.Manifests { 721 if desc.Digest == blobDigest { 722 return desc, nil 723 } 724 725 switch desc.MediaType { 726 case ispec.MediaTypeImageManifest: 727 if foundDescriptor, err := getBlobDescriptorFromManifest(imgStore, repo, blobDigest, desc, log); err == nil { 728 return foundDescriptor, nil 729 } 730 case ispec.MediaTypeImageIndex: 731 indexImage, err := GetImageIndex(imgStore, repo, desc.Digest, log) 732 if err != nil { 733 return ispec.Descriptor{}, err 734 } 735 736 if foundDescriptor, err := GetBlobDescriptorFromIndex(imgStore, indexImage, repo, blobDigest, log); err == nil { 737 return foundDescriptor, nil 738 } 739 } 740 } 741 742 return ispec.Descriptor{}, zerr.ErrBlobNotFound 743 } 744 745 func getBlobDescriptorFromManifest(imgStore storageTypes.ImageStore, repo string, blobDigest godigest.Digest, 746 desc ispec.Descriptor, log zlog.Logger, 747 ) (ispec.Descriptor, error) { 748 manifest, err := GetImageManifest(imgStore, repo, desc.Digest, log) 749 if err != nil { 750 return ispec.Descriptor{}, err 751 } 752 753 if manifest.Config.Digest == blobDigest { 754 return manifest.Config, nil 755 } 756 757 for _, layer := range manifest.Layers { 758 if layer.Digest == blobDigest { 759 return layer, nil 760 } 761 } 762 763 return ispec.Descriptor{}, zerr.ErrBlobNotFound 764 } 765 766 func IsSupportedMediaType(mediaType string) bool { 767 return mediaType == ispec.MediaTypeImageIndex || 768 mediaType == ispec.MediaTypeImageManifest 769 } 770 771 func IsNonDistributable(mediaType string) bool { 772 return mediaType == ispec.MediaTypeImageLayerNonDistributable || //nolint:staticcheck 773 mediaType == ispec.MediaTypeImageLayerNonDistributableGzip || //nolint:staticcheck 774 mediaType == ispec.MediaTypeImageLayerNonDistributableZstd //nolint:staticcheck 775 } 776 777 func ValidateManifestSchema(buf []byte) error { 778 if err := schema.ValidatorMediaTypeManifest.Validate(bytes.NewBuffer(buf)); err != nil { 779 if !IsEmptyLayersError(err) { 780 return err 781 } 782 } 783 784 return nil 785 } 786 787 func ValidateImageIndexSchema(buf []byte) error { 788 if err := schema.ValidatorMediaTypeImageIndex.Validate(bytes.NewBuffer(buf)); err != nil { 789 return err 790 } 791 792 return nil 793 } 794 795 func IsEmptyLayersError(err error) bool { 796 var validationErr schema.ValidationError 797 if errors.As(err, &validationErr) { 798 if len(validationErr.Errs) == 1 && strings.Contains(err.Error(), manifestWithEmptyLayersErrMsg) { 799 return true 800 } else { 801 return false 802 } 803 } 804 805 return false 806 } 807 808 /* 809 DedupeTaskGenerator takes all blobs paths found in the storage.imagestore and groups them by digest 810 811 for each digest and based on the dedupe value it will dedupe or restore deduped blobs to the original state(undeduped)\ 812 by creating a task for each digest and pushing it to the task scheduler. 813 */ 814 type DedupeTaskGenerator struct { 815 ImgStore storageTypes.ImageStore 816 // storage dedupe value 817 Dedupe bool 818 // store blobs paths grouped by digest 819 digest godigest.Digest 820 duplicateBlobs []string 821 /* store processed digest, used for iterating duplicateBlobs one by one 822 and generating a task for each unprocessed one*/ 823 lastDigests []godigest.Digest 824 done bool 825 repos []string // list of repos on which we run dedupe 826 Log zlog.Logger 827 } 828 829 func (gen *DedupeTaskGenerator) Name() string { 830 return "DedupeTaskGenerator" 831 } 832 833 func (gen *DedupeTaskGenerator) Next() (scheduler.Task, error) { 834 var err error 835 836 /* at first run get from storage currently found repositories so that we skip the ones that gets synced/uploaded 837 while this generator runs, there are deduped/restored inline, no need to run dedupe/restore again */ 838 if len(gen.repos) == 0 { 839 gen.repos, err = gen.ImgStore.GetRepositories() 840 if err != nil { 841 //nolint: dupword 842 gen.Log.Error().Err(err).Str("component", "dedupe").Msg("failed to get list of repositories") 843 844 return nil, err 845 } 846 847 // if still no repos 848 if len(gen.repos) == 0 { 849 gen.Log.Info().Str("component", "dedupe").Msg("no repositories found in storage, finished.") 850 851 // no repositories in storage, no need to continue 852 gen.done = true 853 854 return nil, nil 855 } 856 } 857 858 // get all blobs from storage.imageStore and group them by digest 859 gen.digest, gen.duplicateBlobs, err = gen.ImgStore.GetNextDigestWithBlobPaths(gen.repos, gen.lastDigests) 860 if err != nil { 861 gen.Log.Error().Err(err).Str("component", "dedupe").Msg("failed to get next digest") 862 863 return nil, err 864 } 865 866 // if no digests left, then mark the task generator as done 867 if gen.digest == "" { 868 gen.Log.Info().Str("component", "dedupe").Msg("no digests left, finished") 869 870 gen.done = true 871 872 return nil, nil 873 } 874 875 // mark digest as processed before running its task 876 gen.lastDigests = append(gen.lastDigests, gen.digest) 877 878 // generate rebuild dedupe task for this digest 879 return newDedupeTask(gen.ImgStore, gen.digest, gen.Dedupe, gen.duplicateBlobs, gen.Log), nil 880 } 881 882 func (gen *DedupeTaskGenerator) IsDone() bool { 883 return gen.done 884 } 885 886 func (gen *DedupeTaskGenerator) IsReady() bool { 887 return true 888 } 889 890 func (gen *DedupeTaskGenerator) Reset() { 891 gen.lastDigests = []godigest.Digest{} 892 gen.duplicateBlobs = []string{} 893 gen.repos = []string{} 894 gen.digest = "" 895 gen.done = false 896 } 897 898 type dedupeTask struct { 899 imgStore storageTypes.ImageStore 900 // digest of duplicateBLobs 901 digest godigest.Digest 902 // blobs paths with the same digest ^ 903 duplicateBlobs []string 904 dedupe bool 905 log zlog.Logger 906 } 907 908 func newDedupeTask(imgStore storageTypes.ImageStore, digest godigest.Digest, dedupe bool, 909 duplicateBlobs []string, log zlog.Logger, 910 ) *dedupeTask { 911 return &dedupeTask{imgStore, digest, duplicateBlobs, dedupe, log} 912 } 913 914 func (dt *dedupeTask) DoWork(ctx context.Context) error { 915 // run task 916 err := dt.imgStore.RunDedupeForDigest(ctx, dt.digest, dt.dedupe, dt.duplicateBlobs) //nolint: contextcheck 917 if err != nil { 918 // log it 919 dt.log.Error().Err(err).Str("digest", dt.digest.String()).Str("component", "dedupe"). 920 Msg("failed to rebuild digest") 921 } 922 923 return err 924 } 925 926 func (dt *dedupeTask) String() string { 927 return fmt.Sprintf("{Name: %s, digest: %s, dedupe: %t}", 928 dt.Name(), dt.digest, dt.dedupe) 929 } 930 931 func (dt *dedupeTask) Name() string { 932 return "DedupeTask" 933 } 934 935 type StorageMetricsInitGenerator struct { 936 ImgStore storageTypes.ImageStore 937 done bool 938 Metrics monitoring.MetricServer 939 lastRepo string 940 nextRun time.Time 941 rand *rand.Rand 942 Log zlog.Logger 943 MaxDelay int 944 } 945 946 func (gen *StorageMetricsInitGenerator) Name() string { 947 return "StorageMetricsInitGenerator" 948 } 949 950 func (gen *StorageMetricsInitGenerator) Next() (scheduler.Task, error) { 951 if gen.lastRepo == "" && gen.nextRun.IsZero() { 952 gen.rand = rand.New(rand.NewSource(time.Now().UTC().UnixNano())) //nolint: gosec 953 } 954 955 delay := gen.rand.Intn(gen.MaxDelay) 956 957 gen.nextRun = time.Now().Add(time.Duration(delay) * time.Second) 958 959 repo, err := gen.ImgStore.GetNextRepository(gen.lastRepo) 960 if err != nil { 961 return nil, err 962 } 963 964 gen.Log.Debug().Str("repo", repo).Int("randomDelay", delay).Msg("generate task for storage metrics") 965 966 if repo == "" { 967 gen.done = true 968 969 return nil, nil 970 } 971 gen.lastRepo = repo 972 973 return NewStorageMetricsTask(gen.ImgStore, gen.Metrics, repo, gen.Log), nil 974 } 975 976 func (gen *StorageMetricsInitGenerator) IsDone() bool { 977 return gen.done 978 } 979 980 func (gen *StorageMetricsInitGenerator) IsReady() bool { 981 return time.Now().After(gen.nextRun) 982 } 983 984 func (gen *StorageMetricsInitGenerator) Reset() { 985 gen.lastRepo = "" 986 gen.done = false 987 gen.nextRun = time.Time{} 988 } 989 990 type smTask struct { 991 imgStore storageTypes.ImageStore 992 metrics monitoring.MetricServer 993 repo string 994 log zlog.Logger 995 } 996 997 func NewStorageMetricsTask(imgStore storageTypes.ImageStore, metrics monitoring.MetricServer, repo string, 998 log zlog.Logger, 999 ) *smTask { 1000 return &smTask{imgStore, metrics, repo, log} 1001 } 1002 1003 func (smt *smTask) DoWork(ctx context.Context) error { 1004 // run task 1005 monitoring.SetStorageUsage(smt.metrics, smt.imgStore.RootDir(), smt.repo) 1006 smt.log.Debug().Str("component", "monitoring").Msg("computed storage usage for repo " + smt.repo) 1007 1008 return nil 1009 } 1010 1011 func (smt *smTask) String() string { 1012 return fmt.Sprintf("{Name: \"%s\", repo: \"%s\"}", 1013 smt.Name(), smt.repo) 1014 } 1015 1016 func (smt *smTask) Name() string { 1017 return "StorageMetricsTask" 1018 }