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 }