mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
ec7af4979f
Using int32 may result in negative size values when returned by the graphql API Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
431 lines
11 KiB
Go
431 lines
11 KiB
Go
package common
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
zerr "zotregistry.io/zot/errors"
|
|
zcommon "zotregistry.io/zot/pkg/common"
|
|
mConvert "zotregistry.io/zot/pkg/meta/convert"
|
|
proto_go "zotregistry.io/zot/pkg/meta/proto/gen"
|
|
mTypes "zotregistry.io/zot/pkg/meta/types"
|
|
)
|
|
|
|
func SignatureAlreadyExists(signatureSlice []mTypes.SignatureInfo, sm mTypes.SignatureMetadata) bool {
|
|
for _, sigInfo := range signatureSlice {
|
|
if sm.SignatureDigest == sigInfo.SignatureManifestDigest {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func ProtoSignatureAlreadyExists(signatureSlice []*proto_go.SignatureInfo, sm mTypes.SignatureMetadata) bool {
|
|
for _, sigInfo := range signatureSlice {
|
|
if sm.SignatureDigest == sigInfo.SignatureManifestDigest {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func ReferenceIsDigest(reference string) bool {
|
|
_, err := godigest.Parse(reference)
|
|
|
|
return err == nil
|
|
}
|
|
|
|
func ReferenceIsTag(reference string) bool {
|
|
return !ReferenceIsDigest(reference)
|
|
}
|
|
|
|
func ValidateRepoReferenceInput(repo, reference string, manifestDigest godigest.Digest) error {
|
|
if repo == "" {
|
|
return zerr.ErrEmptyRepoName
|
|
}
|
|
|
|
if reference == "" {
|
|
return zerr.ErrEmptyTag
|
|
}
|
|
|
|
if manifestDigest == "" {
|
|
return zerr.ErrEmptyDigest
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// These constants are meant used to describe how high or low in rank a match is.
|
|
// Note that the "higher rank" relates to a lower number so ranks are sorted in a
|
|
// ascending order.
|
|
const (
|
|
lowPriority = 100
|
|
mediumPriority = 10
|
|
highPriority = 1
|
|
perfectMatchPriority = 0
|
|
)
|
|
|
|
// RankRepoName associates a rank to a given repoName given a searchText.
|
|
// The importance of the value grows inversely proportional to the int value it has.
|
|
// For example: rank(1) > rank(10) > rank(100)...
|
|
func RankRepoName(searchText string, repoName string) int {
|
|
searchText = strings.Trim(searchText, "/")
|
|
searchTextSlice := strings.Split(searchText, "/")
|
|
repoNameSlice := strings.Split(repoName, "/")
|
|
|
|
if len(searchTextSlice) > len(repoNameSlice) {
|
|
return -1
|
|
}
|
|
|
|
if searchText == repoName {
|
|
return perfectMatchPriority
|
|
}
|
|
|
|
// searchText contains just 1 directory name
|
|
if len(searchTextSlice) == 1 {
|
|
lastNameInRepoPath := repoNameSlice[len(repoNameSlice)-1]
|
|
|
|
// searchText: "bar" | repoName: "foo/bar" lastNameInRepoPath: "bar"
|
|
if index := strings.Index(lastNameInRepoPath, searchText); index != -1 {
|
|
return (index + 1) * highPriority
|
|
}
|
|
|
|
firstNameInRepoPath := repoNameSlice[0]
|
|
|
|
// searchText: "foo" | repoName: "foo/bar" firstNameInRepoPath: "foo"
|
|
if index := strings.Index(firstNameInRepoPath, searchText); index != -1 {
|
|
return (index + 1) * mediumPriority
|
|
}
|
|
}
|
|
|
|
foundPrefixInRepoName := true
|
|
|
|
// searchText: "foo/bar/rep" | repoName: "foo/bar/baz/repo" foundPrefixInRepoName: true
|
|
// searchText: "foo/baz/rep" | repoName: "foo/bar/baz/repo" foundPrefixInRepoName: false
|
|
for i := 0; i < len(searchTextSlice)-1; i++ {
|
|
if searchTextSlice[i] != repoNameSlice[i] {
|
|
foundPrefixInRepoName = false
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if foundPrefixInRepoName {
|
|
lastNameInRepoPath := repoNameSlice[len(repoNameSlice)-1]
|
|
lastNameInSearchText := searchTextSlice[len(searchTextSlice)-1]
|
|
|
|
// searchText: "foo/bar/epo" | repoName: "foo/bar/baz/repo" -> Index(repo, epo) = 1
|
|
if index := strings.Index(lastNameInRepoPath, lastNameInSearchText); index != -1 {
|
|
return (index + 1) * highPriority
|
|
}
|
|
}
|
|
|
|
// searchText: "foo/bar/b" | repoName: "foo/bar/baz/repo"
|
|
if strings.HasPrefix(repoName, searchText) {
|
|
return mediumPriority
|
|
}
|
|
|
|
// searchText: "bar/ba" | repoName: "foo/bar/baz/repo"
|
|
if index := strings.Index(repoName, searchText); index != -1 {
|
|
return (index + 1) * lowPriority
|
|
}
|
|
|
|
// no match
|
|
return -1
|
|
}
|
|
|
|
func GetRepoTag(searchText string) (string, string, error) {
|
|
const repoTagCount = 2
|
|
|
|
splitSlice := strings.Split(searchText, ":")
|
|
|
|
if len(splitSlice) != repoTagCount {
|
|
return "", "", zerr.ErrInvalidRepoRefFormat
|
|
}
|
|
|
|
repo := strings.TrimSpace(splitSlice[0])
|
|
tag := strings.TrimSpace(splitSlice[1])
|
|
|
|
return repo, tag, nil
|
|
}
|
|
|
|
func MatchesArtifactTypes(descriptorMediaType string, artifactTypes []string) bool {
|
|
if len(artifactTypes) == 0 {
|
|
return true
|
|
}
|
|
|
|
found := false
|
|
|
|
for _, artifactType := range artifactTypes {
|
|
if artifactType != "" && descriptorMediaType != artifactType {
|
|
continue
|
|
}
|
|
|
|
found = true
|
|
|
|
break
|
|
}
|
|
|
|
return found
|
|
}
|
|
|
|
// CheckImageLastUpdated check if the given image is updated earlier than the current repoLastUpdated value
|
|
//
|
|
// It returns updated values for: repoLastUpdated, noImageChecked, isSigned.
|
|
func CheckImageLastUpdated(repoLastUpdated time.Time, isSigned bool, noImageChecked bool,
|
|
manifestFilterData mTypes.FilterData,
|
|
) (time.Time, bool, bool) {
|
|
if noImageChecked || repoLastUpdated.Before(manifestFilterData.LastUpdated) {
|
|
repoLastUpdated = manifestFilterData.LastUpdated
|
|
noImageChecked = false
|
|
|
|
isSigned = manifestFilterData.IsSigned
|
|
}
|
|
|
|
return repoLastUpdated, noImageChecked, isSigned
|
|
}
|
|
|
|
func AddImageMetaToRepoMeta(repoMeta *proto_go.RepoMeta, repoBlobs *proto_go.RepoBlobs, reference string,
|
|
imageMeta mTypes.ImageMeta,
|
|
) (*proto_go.RepoMeta, *proto_go.RepoBlobs) {
|
|
switch imageMeta.MediaType {
|
|
case ispec.MediaTypeImageManifest:
|
|
manifestData := imageMeta.Manifests[0]
|
|
|
|
vendor := GetVendor(manifestData.Manifest.Annotations)
|
|
if vendor == "" {
|
|
vendor = GetVendor(manifestData.Manifest.Annotations)
|
|
}
|
|
|
|
vendors := []string{}
|
|
if vendor != "" {
|
|
vendors = append(vendors, vendor)
|
|
}
|
|
|
|
platforms := []*proto_go.Platform{GetProtoPlatform(&manifestData.Config.Platform)}
|
|
if platforms[0].OS == "" && platforms[0].Architecture == "" {
|
|
platforms = []*proto_go.Platform{}
|
|
}
|
|
|
|
subBlobs := []string{manifestData.Manifest.Config.Digest.String()}
|
|
repoBlobs.Blobs[manifestData.Manifest.Config.Digest.String()] = &proto_go.BlobInfo{
|
|
Size: manifestData.Manifest.Config.Size,
|
|
}
|
|
|
|
for _, layer := range manifestData.Manifest.Layers {
|
|
subBlobs = append(subBlobs, layer.Digest.String())
|
|
repoBlobs.Blobs[layer.Digest.String()] = &proto_go.BlobInfo{Size: layer.Size}
|
|
}
|
|
|
|
lastUpdated := zcommon.GetImageLastUpdated(manifestData.Config)
|
|
|
|
repoBlobs.Blobs[imageMeta.Digest.String()] = &proto_go.BlobInfo{
|
|
Size: imageMeta.Size,
|
|
Vendors: vendors,
|
|
Platforms: platforms,
|
|
SubBlobs: subBlobs,
|
|
LastUpdated: mConvert.GetProtoTime(&lastUpdated),
|
|
}
|
|
case ispec.MediaTypeImageIndex:
|
|
subBlobs := []string{}
|
|
lastUpdated := time.Time{}
|
|
|
|
for _, manifest := range imageMeta.Index.Manifests {
|
|
subBlobs = append(subBlobs, manifest.Digest.String())
|
|
|
|
blobInfo := repoBlobs.Blobs[manifest.Digest.String()]
|
|
|
|
if blobInfo != nil && blobInfo.LastUpdated != nil {
|
|
if lastUpdated.Before(blobInfo.LastUpdated.AsTime()) {
|
|
lastUpdated = blobInfo.LastUpdated.AsTime()
|
|
}
|
|
}
|
|
}
|
|
|
|
repoBlobs.Blobs[imageMeta.Digest.String()] = &proto_go.BlobInfo{
|
|
Size: imageMeta.Size,
|
|
SubBlobs: subBlobs,
|
|
LastUpdated: mConvert.GetProtoTime(&lastUpdated),
|
|
}
|
|
}
|
|
|
|
// update info only when a tag is added
|
|
if zcommon.IsDigest(reference) {
|
|
return repoMeta, repoBlobs
|
|
}
|
|
|
|
size, platforms, vendors := recalculateAggregateFields(repoMeta, repoBlobs)
|
|
repoMeta.Vendors = vendors
|
|
repoMeta.Platforms = platforms
|
|
repoMeta.Size = size
|
|
|
|
imageBlobInfo := repoBlobs.Blobs[imageMeta.Digest.String()]
|
|
repoMeta.LastUpdatedImage = mConvert.GetProtoEarlierUpdatedImage(repoMeta.LastUpdatedImage,
|
|
&proto_go.RepoLastUpdatedImage{
|
|
LastUpdated: imageBlobInfo.LastUpdated,
|
|
MediaType: imageMeta.MediaType,
|
|
Digest: imageMeta.Digest.String(),
|
|
Tag: reference,
|
|
})
|
|
|
|
return repoMeta, repoBlobs
|
|
}
|
|
|
|
func RemoveImageFromRepoMeta(repoMeta *proto_go.RepoMeta, repoBlobs *proto_go.RepoBlobs, ref string,
|
|
) (*proto_go.RepoMeta, *proto_go.RepoBlobs) {
|
|
var updatedLastImage *proto_go.RepoLastUpdatedImage
|
|
|
|
updatedBlobs := map[string]*proto_go.BlobInfo{}
|
|
updatedSize := int64(0)
|
|
updatedVendors := []string{}
|
|
updatedPlatforms := []*proto_go.Platform{}
|
|
|
|
for tag, descriptor := range repoMeta.Tags {
|
|
if descriptor.Digest == "" {
|
|
continue
|
|
}
|
|
|
|
queue := []string{descriptor.Digest}
|
|
|
|
updatedLastImage = mConvert.GetProtoEarlierUpdatedImage(updatedLastImage, &proto_go.RepoLastUpdatedImage{
|
|
LastUpdated: repoBlobs.Blobs[descriptor.Digest].LastUpdated,
|
|
MediaType: descriptor.MediaType,
|
|
Digest: descriptor.Digest,
|
|
Tag: tag,
|
|
})
|
|
|
|
for len(queue) > 0 {
|
|
currentBlob := queue[0]
|
|
queue = queue[1:]
|
|
|
|
if _, found := updatedBlobs[currentBlob]; !found {
|
|
blobInfo := repoBlobs.Blobs[currentBlob]
|
|
|
|
updatedBlobs[currentBlob] = blobInfo
|
|
updatedSize += blobInfo.Size
|
|
updatedVendors = mConvert.AddVendors(updatedVendors, blobInfo.Vendors)
|
|
updatedPlatforms = mConvert.AddProtoPlatforms(updatedPlatforms, blobInfo.Platforms)
|
|
|
|
queue = append(queue, blobInfo.SubBlobs...)
|
|
}
|
|
}
|
|
}
|
|
|
|
repoMeta.Size = updatedSize
|
|
repoMeta.Vendors = updatedVendors
|
|
repoMeta.Platforms = updatedPlatforms
|
|
repoMeta.LastUpdatedImage = updatedLastImage
|
|
|
|
repoBlobs.Blobs = updatedBlobs
|
|
|
|
return repoMeta, repoBlobs
|
|
}
|
|
|
|
func recalculateAggregateFields(repoMeta *proto_go.RepoMeta, repoBlobs *proto_go.RepoBlobs,
|
|
) (int64, []*proto_go.Platform, []string) {
|
|
size := int64(0)
|
|
platforms := []*proto_go.Platform{}
|
|
vendors := []string{}
|
|
blobsMap := map[string]struct{}{}
|
|
|
|
for _, descriptor := range repoMeta.Tags {
|
|
if descriptor.Digest == "" {
|
|
continue
|
|
}
|
|
|
|
queue := []string{descriptor.Digest}
|
|
|
|
for len(queue) > 0 {
|
|
currentBlob := queue[0]
|
|
queue = queue[1:]
|
|
|
|
if _, found := blobsMap[currentBlob]; !found {
|
|
blobInfo := repoBlobs.Blobs[currentBlob]
|
|
if blobInfo == nil {
|
|
continue
|
|
}
|
|
|
|
blobsMap[currentBlob] = struct{}{}
|
|
size += blobInfo.Size
|
|
vendors = mConvert.AddVendors(vendors, blobInfo.Vendors)
|
|
platforms = mConvert.AddProtoPlatforms(platforms, blobInfo.Platforms)
|
|
|
|
queue = append(queue, blobInfo.SubBlobs...)
|
|
}
|
|
}
|
|
}
|
|
|
|
return size, platforms, vendors
|
|
}
|
|
|
|
func GetProtoPlatform(platform *ispec.Platform) *proto_go.Platform {
|
|
if platform == nil {
|
|
return nil
|
|
}
|
|
|
|
return &proto_go.Platform{
|
|
Architecture: getArch(platform.Architecture, platform.Variant),
|
|
OS: platform.OS,
|
|
}
|
|
}
|
|
|
|
func getArch(arch string, variant string) string {
|
|
if variant != "" {
|
|
arch = arch + "/" + variant
|
|
}
|
|
|
|
return arch
|
|
}
|
|
|
|
func GetVendor(annotations map[string]string) string {
|
|
return GetAnnotationValue(annotations, ispec.AnnotationVendor, "org.label-schema.vendor")
|
|
}
|
|
|
|
func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey string) string {
|
|
value, ok := annotations[annotationKey]
|
|
if !ok || value == "" {
|
|
value, ok = annotations[labelKey]
|
|
if !ok {
|
|
value = ""
|
|
}
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
func GetPartialImageMeta(imageIndexMeta mTypes.ImageMeta, imageMeta mTypes.ImageMeta) mTypes.ImageMeta {
|
|
partialImageMeta := imageIndexMeta
|
|
partialImageMeta.Manifests = imageMeta.Manifests
|
|
|
|
partialIndex := deref(imageIndexMeta.Index, ispec.Index{})
|
|
partialIndex.Manifests = getPartialManifestList(partialIndex.Manifests, imageMeta.Digest.String())
|
|
|
|
partialImageMeta.Index = &partialIndex
|
|
|
|
return partialImageMeta
|
|
}
|
|
|
|
func getPartialManifestList(descriptors []ispec.Descriptor, manifestDigest string) []ispec.Descriptor {
|
|
result := []ispec.Descriptor{}
|
|
|
|
for i := range descriptors {
|
|
if descriptors[i].Digest.String() == manifestDigest {
|
|
result = append(result, descriptors[i])
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func deref[T any](pointer *T, defaultVal T) T {
|
|
if pointer != nil {
|
|
return *pointer
|
|
}
|
|
|
|
return defaultVal
|
|
}
|