zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/meta/common/common.go (about)

     1  package common
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	godigest "github.com/opencontainers/go-digest"
     8  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
     9  
    10  	zerr "zotregistry.io/zot/errors"
    11  	zcommon "zotregistry.io/zot/pkg/common"
    12  	mConvert "zotregistry.io/zot/pkg/meta/convert"
    13  	proto_go "zotregistry.io/zot/pkg/meta/proto/gen"
    14  	mTypes "zotregistry.io/zot/pkg/meta/types"
    15  )
    16  
    17  func SignatureAlreadyExists(signatureSlice []mTypes.SignatureInfo, sm mTypes.SignatureMetadata) bool {
    18  	for _, sigInfo := range signatureSlice {
    19  		if sm.SignatureDigest == sigInfo.SignatureManifestDigest {
    20  			return true
    21  		}
    22  	}
    23  
    24  	return false
    25  }
    26  
    27  func ProtoSignatureAlreadyExists(signatureSlice []*proto_go.SignatureInfo, sm mTypes.SignatureMetadata) bool {
    28  	for _, sigInfo := range signatureSlice {
    29  		if sm.SignatureDigest == sigInfo.SignatureManifestDigest {
    30  			return true
    31  		}
    32  	}
    33  
    34  	return false
    35  }
    36  
    37  func ReferenceIsDigest(reference string) bool {
    38  	_, err := godigest.Parse(reference)
    39  
    40  	return err == nil
    41  }
    42  
    43  func ReferenceIsTag(reference string) bool {
    44  	return !ReferenceIsDigest(reference)
    45  }
    46  
    47  func ValidateRepoReferenceInput(repo, reference string, manifestDigest godigest.Digest) error {
    48  	if repo == "" {
    49  		return zerr.ErrEmptyRepoName
    50  	}
    51  
    52  	if reference == "" {
    53  		return zerr.ErrEmptyTag
    54  	}
    55  
    56  	if manifestDigest == "" {
    57  		return zerr.ErrEmptyDigest
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  // These constants are meant used to describe how high or low in rank a match is.
    64  // Note that the "higher rank" relates to a lower number so ranks are sorted in a
    65  // ascending order.
    66  const (
    67  	lowPriority          = 100
    68  	mediumPriority       = 10
    69  	highPriority         = 1
    70  	perfectMatchPriority = 0
    71  )
    72  
    73  // RankRepoName associates a rank to a given repoName given a searchText.
    74  // The importance of the value grows inversely proportional to the int value it has.
    75  // For example: rank(1) > rank(10) > rank(100)...
    76  func RankRepoName(searchText string, repoName string) int {
    77  	searchText = strings.Trim(searchText, "/")
    78  	searchTextSlice := strings.Split(searchText, "/")
    79  	repoNameSlice := strings.Split(repoName, "/")
    80  
    81  	if len(searchTextSlice) > len(repoNameSlice) {
    82  		return -1
    83  	}
    84  
    85  	if searchText == repoName {
    86  		return perfectMatchPriority
    87  	}
    88  
    89  	// searchText contains just 1 directory name
    90  	if len(searchTextSlice) == 1 {
    91  		lastNameInRepoPath := repoNameSlice[len(repoNameSlice)-1]
    92  
    93  		// searchText: "bar" | repoName: "foo/bar" lastNameInRepoPath: "bar"
    94  		if index := strings.Index(lastNameInRepoPath, searchText); index != -1 {
    95  			return (index + 1) * highPriority
    96  		}
    97  
    98  		firstNameInRepoPath := repoNameSlice[0]
    99  
   100  		// searchText: "foo" | repoName: "foo/bar" firstNameInRepoPath: "foo"
   101  		if index := strings.Index(firstNameInRepoPath, searchText); index != -1 {
   102  			return (index + 1) * mediumPriority
   103  		}
   104  	}
   105  
   106  	foundPrefixInRepoName := true
   107  
   108  	// searchText: "foo/bar/rep"  | repoName: "foo/bar/baz/repo" foundPrefixInRepoName: true
   109  	// searchText: "foo/baz/rep"  | repoName: "foo/bar/baz/repo" foundPrefixInRepoName: false
   110  	for i := 0; i < len(searchTextSlice)-1; i++ {
   111  		if searchTextSlice[i] != repoNameSlice[i] {
   112  			foundPrefixInRepoName = false
   113  
   114  			break
   115  		}
   116  	}
   117  
   118  	if foundPrefixInRepoName {
   119  		lastNameInRepoPath := repoNameSlice[len(repoNameSlice)-1]
   120  		lastNameInSearchText := searchTextSlice[len(searchTextSlice)-1]
   121  
   122  		// searchText: "foo/bar/epo"  | repoName: "foo/bar/baz/repo" -> Index(repo, epo) = 1
   123  		if index := strings.Index(lastNameInRepoPath, lastNameInSearchText); index != -1 {
   124  			return (index + 1) * highPriority
   125  		}
   126  	}
   127  
   128  	// searchText: "foo/bar/b"  | repoName: "foo/bar/baz/repo"
   129  	if strings.HasPrefix(repoName, searchText) {
   130  		return mediumPriority
   131  	}
   132  
   133  	// searchText: "bar/ba"  | repoName: "foo/bar/baz/repo"
   134  	if index := strings.Index(repoName, searchText); index != -1 {
   135  		return (index + 1) * lowPriority
   136  	}
   137  
   138  	// no match
   139  	return -1
   140  }
   141  
   142  func GetRepoTag(searchText string) (string, string, error) {
   143  	const repoTagCount = 2
   144  
   145  	splitSlice := strings.Split(searchText, ":")
   146  
   147  	if len(splitSlice) != repoTagCount {
   148  		return "", "", zerr.ErrInvalidRepoRefFormat
   149  	}
   150  
   151  	repo := strings.TrimSpace(splitSlice[0])
   152  	tag := strings.TrimSpace(splitSlice[1])
   153  
   154  	return repo, tag, nil
   155  }
   156  
   157  func MatchesArtifactTypes(descriptorMediaType string, artifactTypes []string) bool {
   158  	if len(artifactTypes) == 0 {
   159  		return true
   160  	}
   161  
   162  	found := false
   163  
   164  	for _, artifactType := range artifactTypes {
   165  		if artifactType != "" && descriptorMediaType != artifactType {
   166  			continue
   167  		}
   168  
   169  		found = true
   170  
   171  		break
   172  	}
   173  
   174  	return found
   175  }
   176  
   177  // CheckImageLastUpdated check if the given image is updated earlier than the current repoLastUpdated value
   178  //
   179  // It returns updated values for: repoLastUpdated, noImageChecked, isSigned.
   180  func CheckImageLastUpdated(repoLastUpdated time.Time, isSigned bool, noImageChecked bool,
   181  	manifestFilterData mTypes.FilterData,
   182  ) (time.Time, bool, bool) {
   183  	if noImageChecked || repoLastUpdated.Before(manifestFilterData.LastUpdated) {
   184  		repoLastUpdated = manifestFilterData.LastUpdated
   185  		noImageChecked = false
   186  
   187  		isSigned = manifestFilterData.IsSigned
   188  	}
   189  
   190  	return repoLastUpdated, noImageChecked, isSigned
   191  }
   192  
   193  func AddImageMetaToRepoMeta(repoMeta *proto_go.RepoMeta, repoBlobs *proto_go.RepoBlobs, reference string,
   194  	imageMeta mTypes.ImageMeta,
   195  ) (*proto_go.RepoMeta, *proto_go.RepoBlobs) {
   196  	switch imageMeta.MediaType {
   197  	case ispec.MediaTypeImageManifest:
   198  		manifestData := imageMeta.Manifests[0]
   199  
   200  		vendor := GetVendor(manifestData.Manifest.Annotations)
   201  		if vendor == "" {
   202  			vendor = GetVendor(manifestData.Manifest.Annotations)
   203  		}
   204  
   205  		vendors := []string{}
   206  		if vendor != "" {
   207  			vendors = append(vendors, vendor)
   208  		}
   209  
   210  		platforms := []*proto_go.Platform{GetProtoPlatform(&manifestData.Config.Platform)}
   211  		if platforms[0].OS == "" && platforms[0].Architecture == "" {
   212  			platforms = []*proto_go.Platform{}
   213  		}
   214  
   215  		subBlobs := []string{manifestData.Manifest.Config.Digest.String()}
   216  		repoBlobs.Blobs[manifestData.Manifest.Config.Digest.String()] = &proto_go.BlobInfo{
   217  			Size: manifestData.Manifest.Config.Size,
   218  		}
   219  
   220  		for _, layer := range manifestData.Manifest.Layers {
   221  			subBlobs = append(subBlobs, layer.Digest.String())
   222  			repoBlobs.Blobs[layer.Digest.String()] = &proto_go.BlobInfo{Size: layer.Size}
   223  		}
   224  
   225  		lastUpdated := zcommon.GetImageLastUpdated(manifestData.Config)
   226  
   227  		repoBlobs.Blobs[imageMeta.Digest.String()] = &proto_go.BlobInfo{
   228  			Size:        imageMeta.Size,
   229  			Vendors:     vendors,
   230  			Platforms:   platforms,
   231  			SubBlobs:    subBlobs,
   232  			LastUpdated: mConvert.GetProtoTime(&lastUpdated),
   233  		}
   234  	case ispec.MediaTypeImageIndex:
   235  		subBlobs := []string{}
   236  		for _, manifest := range imageMeta.Index.Manifests {
   237  			subBlobs = append(subBlobs, manifest.Digest.String())
   238  		}
   239  
   240  		repoBlobs.Blobs[imageMeta.Digest.String()] = &proto_go.BlobInfo{
   241  			Size:     imageMeta.Size,
   242  			SubBlobs: subBlobs,
   243  		}
   244  	}
   245  
   246  	// update info only when a tag is added
   247  	if zcommon.IsDigest(reference) {
   248  		return repoMeta, repoBlobs
   249  	}
   250  
   251  	size, platforms, vendors := recalculateAggregateFields(repoMeta, repoBlobs)
   252  	repoMeta.Vendors = vendors
   253  	repoMeta.Platforms = platforms
   254  	repoMeta.Size = int32(size)
   255  
   256  	imageBlobInfo := repoBlobs.Blobs[imageMeta.Digest.String()]
   257  	repoMeta.LastUpdatedImage = mConvert.GetProtoEarlierUpdatedImage(repoMeta.LastUpdatedImage,
   258  		&proto_go.RepoLastUpdatedImage{
   259  			LastUpdated: imageBlobInfo.LastUpdated,
   260  			MediaType:   imageMeta.MediaType,
   261  			Digest:      imageMeta.Digest.String(),
   262  			Tag:         reference,
   263  		})
   264  
   265  	return repoMeta, repoBlobs
   266  }
   267  
   268  func RemoveImageFromRepoMeta(repoMeta *proto_go.RepoMeta, repoBlobs *proto_go.RepoBlobs, ref string,
   269  ) (*proto_go.RepoMeta, *proto_go.RepoBlobs) {
   270  	var updatedLastImage *proto_go.RepoLastUpdatedImage
   271  
   272  	updatedBlobs := map[string]*proto_go.BlobInfo{}
   273  	updatedSize := int64(0)
   274  	updatedVendors := []string{}
   275  	updatedPlatforms := []*proto_go.Platform{}
   276  
   277  	for tag, descriptor := range repoMeta.Tags {
   278  		if descriptor.Digest == "" {
   279  			continue
   280  		}
   281  
   282  		queue := []string{descriptor.Digest}
   283  
   284  		updatedLastImage = mConvert.GetProtoEarlierUpdatedImage(updatedLastImage, &proto_go.RepoLastUpdatedImage{
   285  			LastUpdated: repoBlobs.Blobs[descriptor.Digest].LastUpdated,
   286  			MediaType:   descriptor.MediaType,
   287  			Digest:      descriptor.Digest,
   288  			Tag:         tag,
   289  		})
   290  
   291  		for len(queue) > 0 {
   292  			currentBlob := queue[0]
   293  			queue = queue[1:]
   294  
   295  			if _, found := updatedBlobs[currentBlob]; !found {
   296  				blobInfo := repoBlobs.Blobs[currentBlob]
   297  
   298  				updatedBlobs[currentBlob] = blobInfo
   299  				updatedSize += blobInfo.Size
   300  				updatedVendors = mConvert.AddVendors(updatedVendors, blobInfo.Vendors)
   301  				updatedPlatforms = mConvert.AddProtoPlatforms(updatedPlatforms, blobInfo.Platforms)
   302  
   303  				queue = append(queue, blobInfo.SubBlobs...)
   304  			}
   305  		}
   306  	}
   307  
   308  	repoMeta.Size = int32(updatedSize)
   309  	repoMeta.Vendors = updatedVendors
   310  	repoMeta.Platforms = updatedPlatforms
   311  	repoMeta.LastUpdatedImage = updatedLastImage
   312  
   313  	repoBlobs.Blobs = updatedBlobs
   314  
   315  	return repoMeta, repoBlobs
   316  }
   317  
   318  func recalculateAggregateFields(repoMeta *proto_go.RepoMeta, repoBlobs *proto_go.RepoBlobs,
   319  ) (int64, []*proto_go.Platform, []string) {
   320  	size := int64(0)
   321  	platforms := []*proto_go.Platform{}
   322  	vendors := []string{}
   323  	blobsMap := map[string]struct{}{}
   324  
   325  	for _, descriptor := range repoMeta.Tags {
   326  		if descriptor.Digest == "" {
   327  			continue
   328  		}
   329  
   330  		queue := []string{descriptor.Digest}
   331  
   332  		for len(queue) > 0 {
   333  			currentBlob := queue[0]
   334  			queue = queue[1:]
   335  
   336  			if _, found := blobsMap[currentBlob]; !found {
   337  				blobInfo := repoBlobs.Blobs[currentBlob]
   338  				if blobInfo == nil {
   339  					continue
   340  				}
   341  
   342  				blobsMap[currentBlob] = struct{}{}
   343  				size += blobInfo.Size
   344  				vendors = mConvert.AddVendors(vendors, blobInfo.Vendors)
   345  				platforms = mConvert.AddProtoPlatforms(platforms, blobInfo.Platforms)
   346  
   347  				queue = append(queue, blobInfo.SubBlobs...)
   348  			}
   349  		}
   350  	}
   351  
   352  	return size, platforms, vendors
   353  }
   354  
   355  func GetProtoPlatform(platform *ispec.Platform) *proto_go.Platform {
   356  	if platform == nil {
   357  		return nil
   358  	}
   359  
   360  	return &proto_go.Platform{
   361  		Architecture: getArch(platform.Architecture, platform.Variant),
   362  		OS:           platform.OS,
   363  	}
   364  }
   365  
   366  func getArch(arch string, variant string) string {
   367  	if variant != "" {
   368  		arch = arch + "/" + variant
   369  	}
   370  
   371  	return arch
   372  }
   373  
   374  func GetVendor(annotations map[string]string) string {
   375  	return GetAnnotationValue(annotations, ispec.AnnotationVendor, "org.label-schema.vendor")
   376  }
   377  
   378  func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey string) string {
   379  	value, ok := annotations[annotationKey]
   380  	if !ok || value == "" {
   381  		value, ok = annotations[labelKey]
   382  		if !ok {
   383  			value = ""
   384  		}
   385  	}
   386  
   387  	return value
   388  }