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  }