zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/search_test.go (about) 1 //go:build search 2 // +build search 3 4 package search_test 5 6 import ( 7 "context" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "net/url" 14 "os" 15 "path" 16 "strconv" 17 "strings" 18 "testing" 19 "time" 20 21 guuid "github.com/gofrs/uuid" 22 regTypes "github.com/google/go-containerregistry/pkg/v1/types" 23 notreg "github.com/notaryproject/notation-go/registry" 24 godigest "github.com/opencontainers/go-digest" 25 "github.com/opencontainers/image-spec/specs-go" 26 ispec "github.com/opencontainers/image-spec/specs-go/v1" 27 . "github.com/smartystreets/goconvey/convey" 28 "gopkg.in/resty.v1" 29 30 zerr "zotregistry.dev/zot/errors" 31 "zotregistry.dev/zot/pkg/api" 32 "zotregistry.dev/zot/pkg/api/config" 33 "zotregistry.dev/zot/pkg/api/constants" 34 zcommon "zotregistry.dev/zot/pkg/common" 35 extconf "zotregistry.dev/zot/pkg/extensions/config" 36 "zotregistry.dev/zot/pkg/extensions/monitoring" 37 cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve" 38 cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model" 39 "zotregistry.dev/zot/pkg/log" 40 mTypes "zotregistry.dev/zot/pkg/meta/types" 41 "zotregistry.dev/zot/pkg/storage" 42 "zotregistry.dev/zot/pkg/storage/local" 43 storageTypes "zotregistry.dev/zot/pkg/storage/types" 44 . "zotregistry.dev/zot/pkg/test/common" 45 . "zotregistry.dev/zot/pkg/test/image-utils" 46 "zotregistry.dev/zot/pkg/test/mocks" 47 ociutils "zotregistry.dev/zot/pkg/test/oci-utils" 48 "zotregistry.dev/zot/pkg/test/signature" 49 tskip "zotregistry.dev/zot/pkg/test/skip" 50 ) 51 52 const ( 53 graphqlQueryPrefix = constants.FullSearchPrefix 54 DBFileName = "meta.db" 55 ) 56 57 var ( 58 ErrTestError = errors.New("test error") 59 ErrPutManifest = errors.New("can't put manifest") 60 ) 61 62 func readFileAndSearchString(filePath string, stringToMatch string, timeout time.Duration) (bool, error) { 63 ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) 64 defer cancelFunc() 65 66 for { 67 select { 68 case <-ctx.Done(): 69 return false, nil 70 default: 71 content, err := os.ReadFile(filePath) 72 if err != nil { 73 return false, err 74 } 75 76 if strings.Contains(string(content), stringToMatch) { 77 return true, nil 78 } 79 } 80 } 81 } 82 83 func verifyRepoSummaryFields(t *testing.T, 84 actualRepoSummary, expectedRepoSummary *zcommon.RepoSummary, 85 ) { 86 t.Helper() 87 88 t.Logf("Verify RepoSummary \n%v \nmatches fields of \n%v", 89 actualRepoSummary, expectedRepoSummary, 90 ) 91 92 So(actualRepoSummary.Name, ShouldEqual, expectedRepoSummary.Name) 93 So(actualRepoSummary.LastUpdated, ShouldEqual, expectedRepoSummary.LastUpdated) 94 So(actualRepoSummary.Size, ShouldEqual, expectedRepoSummary.Size) 95 So(len(actualRepoSummary.Vendors), ShouldEqual, len(expectedRepoSummary.Vendors)) 96 97 for index, vendor := range actualRepoSummary.Vendors { 98 So(vendor, ShouldEqual, expectedRepoSummary.Vendors[index]) 99 } 100 101 So(len(actualRepoSummary.Platforms), ShouldEqual, len(expectedRepoSummary.Platforms)) 102 103 for index, platform := range actualRepoSummary.Platforms { 104 So(platform.Os, ShouldEqual, expectedRepoSummary.Platforms[index].Os) 105 So(platform.Arch, ShouldEqual, expectedRepoSummary.Platforms[index].Arch) 106 } 107 108 So(actualRepoSummary.NewestImage.Tag, ShouldEqual, expectedRepoSummary.NewestImage.Tag) 109 verifyImageSummaryFields(t, &actualRepoSummary.NewestImage, &expectedRepoSummary.NewestImage) 110 } 111 112 func verifyImageSummaryFields(t *testing.T, 113 actualImageSummary, expectedImageSummary *zcommon.ImageSummary, 114 ) { 115 t.Helper() 116 117 t.Logf("Verify ImageSummary \n%v \nmatches fields of \n%v", 118 actualImageSummary, expectedImageSummary, 119 ) 120 121 So(actualImageSummary.Tag, ShouldEqual, expectedImageSummary.Tag) 122 So(actualImageSummary.LastUpdated, ShouldEqual, expectedImageSummary.LastUpdated) 123 So(actualImageSummary.Size, ShouldEqual, expectedImageSummary.Size) 124 So(actualImageSummary.IsSigned, ShouldEqual, expectedImageSummary.IsSigned) 125 So(actualImageSummary.Vendor, ShouldEqual, expectedImageSummary.Vendor) 126 So(actualImageSummary.Title, ShouldEqual, expectedImageSummary.Title) 127 So(actualImageSummary.Description, ShouldEqual, expectedImageSummary.Description) 128 So(actualImageSummary.Source, ShouldEqual, expectedImageSummary.Source) 129 So(actualImageSummary.Documentation, ShouldEqual, expectedImageSummary.Documentation) 130 So(actualImageSummary.Licenses, ShouldEqual, expectedImageSummary.Licenses) 131 132 So(len(actualImageSummary.Manifests), ShouldEqual, len(expectedImageSummary.Manifests)) 133 134 for i := range actualImageSummary.Manifests { 135 So(actualImageSummary.Manifests[i].Platform.Os, ShouldEqual, expectedImageSummary.Manifests[i].Platform.Os) 136 So(actualImageSummary.Manifests[i].Platform.Arch, ShouldEqual, expectedImageSummary.Manifests[i].Platform.Arch) 137 So(len(actualImageSummary.Manifests[i].History), ShouldEqual, len(expectedImageSummary.Manifests[i].History)) 138 139 expectedHistories := expectedImageSummary.Manifests[i].History 140 141 for index, history := range actualImageSummary.Manifests[i].History { 142 // Digest could be empty string if the history entry is not associated with a layer 143 So(history.Layer.Digest, ShouldEqual, expectedHistories[index].Layer.Digest) 144 So(history.Layer.Size, ShouldEqual, expectedHistories[index].Layer.Size) 145 So( 146 history.HistoryDescription.Author, 147 ShouldEqual, 148 expectedHistories[index].HistoryDescription.Author, 149 ) 150 So( 151 history.HistoryDescription.Created, 152 ShouldEqual, 153 expectedHistories[index].HistoryDescription.Created, 154 ) 155 So( 156 history.HistoryDescription.CreatedBy, 157 ShouldEqual, 158 expectedHistories[index].HistoryDescription.CreatedBy, 159 ) 160 So( 161 history.HistoryDescription.EmptyLayer, 162 ShouldEqual, 163 expectedHistories[index].HistoryDescription.EmptyLayer, 164 ) 165 So( 166 history.HistoryDescription.Comment, 167 ShouldEqual, 168 expectedHistories[index].HistoryDescription.Comment, 169 ) 170 } 171 } 172 } 173 174 func uploadNewRepoTag(tag string, repoName string, baseURL string, layers [][]byte) error { 175 created := time.Now() 176 config := ispec.Image{ 177 Created: &created, 178 Platform: ispec.Platform{ 179 Architecture: "amd64", 180 OS: "linux", 181 }, 182 RootFS: ispec.RootFS{ 183 Type: "layers", 184 DiffIDs: []godigest.Digest{}, 185 }, 186 Author: "ZotUser", 187 } 188 189 configBlob, err := json.Marshal(config) 190 So(err, ShouldBeNil) 191 192 configDigest := godigest.FromBytes(configBlob) 193 194 manifest := ispec.Manifest{ 195 Versioned: specs.Versioned{ 196 SchemaVersion: 2, 197 }, 198 Config: ispec.Descriptor{ 199 MediaType: "application/vnd.oci.image.config.v1+json", 200 Digest: configDigest, 201 Size: int64(len(configBlob)), 202 }, 203 Layers: []ispec.Descriptor{ 204 { 205 MediaType: "application/vnd.oci.image.layer.v1.tar", 206 Digest: godigest.FromBytes(layers[0]), 207 Size: int64(len(layers[0])), 208 }, 209 }, 210 } 211 212 err = UploadImage( 213 Image{ 214 Manifest: manifest, 215 Config: config, 216 Layers: layers, 217 }, baseURL, repoName, tag, 218 ) 219 220 return err 221 } 222 223 func getMockCveScanner(metaDB mTypes.MetaDB) cveinfo.Scanner { 224 // MetaDB loaded with initial data, mock the scanner 225 // Setup test CVE data in mock scanner 226 getCveResults := func(image string) map[string]cvemodel.CVE { 227 if image == "zot-cve-test:0.0.1" || image == "a/zot-cve-test:0.0.1" || 228 image == "zot-test:0.0.1" || image == "a/zot-test:0.0.1" || 229 strings.Contains(image, "sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495") || 230 strings.Contains(image, "sha256:db573b0146a853af339bde42256a810b911d89c9252d055e0218de53690e031e") || 231 strings.Contains(image, "sha256:0d8d7cae58478b43cde65d4fd495e1b4ab3f6404450b17e75d7e04eb2040d297") { 232 return map[string]cvemodel.CVE{ 233 "CVE1": { 234 ID: "CVE1", 235 Severity: "MEDIUM", 236 Title: "Title CVE1", 237 Description: "Description CVE1", 238 }, 239 "CVE2": { 240 ID: "CVE2", 241 Severity: "HIGH", 242 Title: "Title CVE2", 243 Description: "Description CVE2", 244 }, 245 "CVE3": { 246 ID: "CVE3", 247 Severity: "LOW", 248 Title: "Title CVE3", 249 Description: "Description CVE3", 250 }, 251 "CVE4": { 252 ID: "CVE4", 253 Severity: "CRITICAL", 254 Title: "Title CVE4", 255 Description: "Description CVE4", 256 }, 257 } 258 } 259 260 if image == "test-repo:latest" || 261 strings.Contains(image, "sha256:9f8e1a125c4fb03a0f157d75999b73284ccc5cba18eb772e4643e3499343607e") || 262 strings.Contains(image, "sha256:5c6e8dd5dce1c488fc79d02690b11ff7a190fad21885297101dbd531f0db3597") { 263 return map[string]cvemodel.CVE{ 264 "CVE1": { 265 ID: "CVE1", 266 Severity: "MEDIUM", 267 Title: "Title CVE1", 268 Description: "Description CVE1", 269 }, 270 "CVE2": { 271 ID: "CVE2", 272 Severity: "HIGH", 273 Title: "Title CVE2", 274 Description: "Description CVE2", 275 }, 276 "CVE3": { 277 ID: "CVE3", 278 Severity: "LOW", 279 Title: "Title CVE3", 280 Description: "Description CVE3", 281 }, 282 "CVE4": { 283 ID: "CVE4", 284 Severity: "CRITICAL", 285 Title: "Title CVE4", 286 Description: "Description CVE4", 287 }, 288 } 289 } 290 291 // By default the image has no vulnerabilities 292 return map[string]cvemodel.CVE{} 293 } 294 295 scanner := mocks.CveScannerMock{ 296 ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { 297 return getCveResults(image), nil 298 }, 299 GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE { 300 return getCveResults(digestStr) 301 }, 302 IsResultCachedFn: func(digestStr string) bool { 303 return true 304 }, 305 IsImageFormatScannableFn: func(repo string, reference string) (bool, error) { 306 // Almost same logic compared to actual Trivy specific implementation 307 imageDir := repo 308 inputTag := reference 309 310 repoMeta, err := metaDB.GetRepoMeta(context.Background(), imageDir) 311 if err != nil { 312 return false, err 313 } 314 315 manifestDigestStr := reference 316 317 if zcommon.IsTag(reference) { 318 var ok bool 319 320 descriptor, ok := repoMeta.Tags[inputTag] 321 if !ok { 322 return false, zerr.ErrTagMetaNotFound 323 } 324 325 manifestDigestStr = descriptor.Digest 326 } 327 328 manifestDigest, err := godigest.Parse(manifestDigestStr) 329 if err != nil { 330 return false, err 331 } 332 333 manifestData, err := metaDB.GetImageMeta(manifestDigest) 334 if err != nil { 335 return false, err 336 } 337 338 for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers { 339 switch imageLayer.MediaType { 340 case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer): 341 342 return true, nil 343 default: 344 345 return false, zerr.ErrScanNotSupported 346 } 347 } 348 349 return false, nil 350 }, 351 } 352 353 return &scanner 354 } 355 356 func TestRepoListWithNewestImage(t *testing.T) { 357 Convey("Test repoListWithNewestImage by tag with HTTP", t, func() { 358 subpath := "/a" 359 port := GetFreePort() 360 baseURL := GetBaseURL(port) 361 conf := config.New() 362 conf.HTTP.Port = port 363 rootDir := t.TempDir() 364 subRootDir := t.TempDir() 365 conf.Storage.RootDirectory = rootDir 366 conf.Storage.SubPaths = make(map[string]config.StorageConfig) 367 conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir} 368 defaultVal := true 369 conf.Extensions = &extconf.ExtensionConfig{ 370 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 371 } 372 373 conf.Extensions.Search.CVE = nil 374 375 ctlr := api.NewController(conf) 376 377 ctlrManager := NewControllerManager(ctlr) 378 ctlrManager.StartAndWait(port) 379 defer ctlrManager.StopServer() 380 381 uploadedImage := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Build() 382 383 err := UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1") 384 So(err, ShouldBeNil) 385 386 err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1") 387 So(err, ShouldBeNil) 388 389 err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1") 390 So(err, ShouldBeNil) 391 392 err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1") 393 So(err, ShouldBeNil) 394 395 resp, err := resty.R().Get(baseURL + "/v2/") 396 So(resp, ShouldNotBeNil) 397 So(err, ShouldBeNil) 398 So(resp.StatusCode(), ShouldEqual, 200) 399 400 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix) 401 So(resp, ShouldNotBeNil) 402 So(err, ShouldBeNil) 403 So(resp.StatusCode(), ShouldEqual, 422) 404 405 Convey("Test repoListWithNewestImage with pagination", func() { 406 query := `{ 407 RepoListWithNewestImage(requestedPage:{ 408 limit: 2 409 offset: 0 410 sortBy: UPDATE_TIME 411 }){ 412 Page{ 413 ItemCount 414 TotalCount 415 } 416 Results{ 417 Name 418 NewestImage{ 419 Tag 420 } 421 } 422 } 423 }` 424 425 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 426 "?query=" + url.QueryEscape(query)) 427 So(resp, ShouldNotBeNil) 428 So(err, ShouldBeNil) 429 So(resp.StatusCode(), ShouldEqual, 200) 430 431 var responseStruct zcommon.RepoWithNewestImageResponse 432 err = json.Unmarshal(resp.Body(), &responseStruct) 433 So(err, ShouldBeNil) 434 So(len(responseStruct.Results), ShouldEqual, 2) 435 So(responseStruct.Page.ItemCount, ShouldEqual, 2) 436 So(responseStruct.Page.TotalCount, ShouldEqual, 4) 437 }) 438 439 Convey("Test repoListWithNewestImage with pagination, no limit or offset", func() { 440 query := `{ 441 RepoListWithNewestImage(requestedPage:{ 442 limit: 0 443 offset: 0 444 sortBy: UPDATE_TIME 445 }){ 446 Page{ 447 ItemCount 448 TotalCount 449 } 450 Results{ 451 Name 452 NewestImage{ 453 Tag 454 } 455 } 456 } 457 }` 458 459 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 460 "?query=" + url.QueryEscape(query)) 461 So(resp, ShouldNotBeNil) 462 So(err, ShouldBeNil) 463 So(resp.StatusCode(), ShouldEqual, 200) 464 465 var responseStruct zcommon.RepoWithNewestImageResponse 466 err = json.Unmarshal(resp.Body(), &responseStruct) 467 So(err, ShouldBeNil) 468 So(len(responseStruct.Results), ShouldEqual, 4) 469 So(responseStruct.Page.ItemCount, ShouldEqual, 4) 470 So(responseStruct.Page.TotalCount, ShouldEqual, 4) 471 }) 472 473 Convey("Test repoListWithNewestImage multiple", func() { 474 query := `{RepoListWithNewestImage{ 475 Results{ 476 Name 477 NewestImage{ 478 Tag 479 } 480 } 481 }}` 482 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 483 "?query=" + url.QueryEscape(query)) 484 So(resp, ShouldNotBeNil) 485 So(err, ShouldBeNil) 486 So(resp.StatusCode(), ShouldEqual, 200) 487 488 var responseStruct zcommon.RepoWithNewestImageResponse 489 err = json.Unmarshal(resp.Body(), &responseStruct) 490 So(err, ShouldBeNil) 491 So(len(responseStruct.Results), ShouldEqual, 4) 492 493 images := responseStruct.Results 494 So(images[0].NewestImage.Tag, ShouldEqual, "0.0.1") 495 496 query = `{ 497 RepoListWithNewestImage(requestedPage: { 498 limit: 1 499 offset: 0 500 sortBy: UPDATE_TIME 501 }){ 502 Results{ 503 Name 504 NewestImage{ 505 Tag 506 } 507 } 508 } 509 }` 510 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 511 "?query=" + url.QueryEscape(query)) 512 So(resp, ShouldNotBeNil) 513 So(err, ShouldBeNil) 514 So(resp.StatusCode(), ShouldEqual, 200) 515 516 err = json.Unmarshal(resp.Body(), &responseStruct) 517 So(err, ShouldBeNil) 518 So(len(responseStruct.Results), ShouldEqual, 1) 519 520 repos := responseStruct.Results 521 So(repos[0].NewestImage.Tag, ShouldEqual, "0.0.1") 522 523 query = `{ 524 RepoListWithNewestImage{ 525 Results{ 526 Name 527 NewestImage{ 528 Tag 529 Vulnerabilities{ 530 MaxSeverity 531 UnknownCount 532 LowCount 533 MediumCount 534 HighCount 535 CriticalCount 536 Count 537 } 538 } 539 } 540 } 541 }` 542 543 // Verify we don't return any vulnerabilities if CVE scanning is disabled 544 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 545 "?query=" + url.QueryEscape(query)) 546 So(resp, ShouldNotBeNil) 547 So(err, ShouldBeNil) 548 So(resp.StatusCode(), ShouldEqual, 200) 549 550 err = json.Unmarshal(resp.Body(), &responseStruct) 551 So(err, ShouldBeNil) 552 So(len(responseStruct.Results), ShouldEqual, 4) 553 554 images = responseStruct.Results 555 So(images[0].NewestImage.Tag, ShouldEqual, "0.0.1") 556 So(images[0].NewestImage.Vulnerabilities.Count, ShouldEqual, 0) 557 So(images[0].NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0) 558 So(images[0].NewestImage.Vulnerabilities.LowCount, ShouldEqual, 0) 559 So(images[0].NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 0) 560 So(images[0].NewestImage.Vulnerabilities.HighCount, ShouldEqual, 0) 561 So(images[0].NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 0) 562 So(images[0].NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "") 563 564 query = `{ 565 RepoListWithNewestImage{ 566 Results{ 567 Name 568 NewestImage{ 569 Tag 570 } 571 } 572 } 573 }` 574 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 575 "?query=" + url.QueryEscape(query)) 576 So(resp, ShouldNotBeNil) 577 So(err, ShouldBeNil) 578 579 err = os.Chmod(rootDir, 0o000) 580 if err != nil { 581 panic(err) 582 } 583 584 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 585 "?query=" + url.QueryEscape(query)) 586 So(resp, ShouldNotBeNil) 587 So(err, ShouldBeNil) 588 So(resp.StatusCode(), ShouldEqual, 200) 589 590 err = json.Unmarshal(resp.Body(), &responseStruct) 591 So(err, ShouldBeNil) 592 So(responseStruct.Errors, ShouldBeNil) // Even if permissions fail data is coming from the DB 593 594 err = os.Chmod(rootDir, 0o755) 595 if err != nil { 596 panic(err) 597 } 598 599 manifestDigest := uploadedImage.ManifestDescriptor.Digest 600 configDigest := uploadedImage.ConfigDescriptor.Digest 601 602 // Delete config blob and try. 603 err = os.Remove(path.Join(subRootDir, "a/zot-test/blobs/sha256", configDigest.Encoded())) 604 if err != nil { 605 panic(err) 606 } 607 608 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 609 "?query=" + url.QueryEscape(query)) 610 So(resp, ShouldNotBeNil) 611 So(err, ShouldBeNil) 612 So(resp.StatusCode(), ShouldEqual, 200) 613 614 err = os.Remove(path.Join(subRootDir, "a/zot-test/blobs/sha256", 615 manifestDigest.Encoded())) 616 if err != nil { 617 panic(err) 618 } 619 620 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 621 "?query=" + url.QueryEscape(query)) 622 So(resp, ShouldNotBeNil) 623 So(err, ShouldBeNil) 624 So(resp.StatusCode(), ShouldEqual, 200) 625 626 err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", configDigest.Encoded())) 627 if err != nil { 628 panic(err) 629 } 630 631 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 632 "?query=" + url.QueryEscape(query)) 633 So(resp, ShouldNotBeNil) 634 So(err, ShouldBeNil) 635 So(resp.StatusCode(), ShouldEqual, 200) 636 637 // Delete manifest blob also and try 638 err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", manifestDigest.Encoded())) 639 if err != nil { 640 panic(err) 641 } 642 643 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + 644 "?query=" + url.QueryEscape(query)) 645 So(resp, ShouldNotBeNil) 646 So(err, ShouldBeNil) 647 So(resp.StatusCode(), ShouldEqual, 200) 648 }) 649 }) 650 651 Convey("Test repoListWithNewestImage with vulnerability scan enabled", t, func() { 652 subpath := "/a" 653 port := GetFreePort() 654 baseURL := GetBaseURL(port) 655 conf := config.New() 656 conf.HTTP.Port = port 657 rootDir := t.TempDir() 658 subRootDir := t.TempDir() 659 conf.Storage.RootDirectory = rootDir 660 conf.Storage.SubPaths = make(map[string]config.StorageConfig) 661 conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir} 662 defaultVal := true 663 664 updateDuration, _ := time.ParseDuration("1h") 665 trivyConfig := &extconf.TrivyConfig{ 666 DBRepository: "ghcr.io/project-zot/trivy-db", 667 } 668 cveConfig := &extconf.CVEConfig{ 669 UpdateInterval: updateDuration, 670 Trivy: trivyConfig, 671 } 672 searchConfig := &extconf.SearchConfig{ 673 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 674 CVE: cveConfig, 675 } 676 conf.Extensions = &extconf.ExtensionConfig{ 677 Search: searchConfig, 678 } 679 680 // we won't use the logging config feature as we want logs in both 681 // stdout and a file 682 logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") 683 So(err, ShouldBeNil) 684 logPath := logFile.Name() 685 defer os.Remove(logPath) 686 687 writers := io.MultiWriter(os.Stdout, logFile) 688 689 ctlr := api.NewController(conf) 690 ctlr.Log.Logger = ctlr.Log.Output(writers) 691 692 if err := ctlr.Init(); err != nil { 693 panic(err) 694 } 695 696 ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) 697 698 go func() { 699 if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) { 700 panic(err) 701 } 702 }() 703 704 defer ctlr.Shutdown() 705 706 substring := "{\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000," + 707 "\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"}}}" 708 found, err := readFileAndSearchString(logPath, substring, 2*time.Minute) 709 So(found, ShouldBeTrue) 710 So(err, ShouldBeNil) 711 712 found, err = readFileAndSearchString(logPath, "updating cve-db", 2*time.Minute) 713 So(found, ShouldBeTrue) 714 So(err, ShouldBeNil) 715 716 found, err = readFileAndSearchString(logPath, 717 "cve-db update completed, next update scheduled after interval", 4*time.Minute) 718 So(found, ShouldBeTrue) 719 So(err, ShouldBeNil) 720 721 WaitTillServerReady(baseURL) 722 723 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix) 724 So(resp, ShouldNotBeNil) 725 So(err, ShouldBeNil) 726 So(resp.StatusCode(), ShouldEqual, 422) 727 728 uploadedImage := CreateDefaultImage() 729 730 err = UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1") 731 So(err, ShouldBeNil) 732 733 err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1") 734 So(err, ShouldBeNil) 735 736 err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1") 737 So(err, ShouldBeNil) 738 739 err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1") 740 So(err, ShouldBeNil) 741 742 query := `{ 743 RepoListWithNewestImage{ 744 Results{ 745 Name 746 NewestImage{ 747 Tag 748 Digest 749 Vulnerabilities{ 750 MaxSeverity 751 UnknownCount 752 LowCount 753 MediumCount 754 HighCount 755 CriticalCount 756 Count 757 } 758 } 759 } 760 } 761 }` 762 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 763 So(resp, ShouldNotBeNil) 764 So(err, ShouldBeNil) 765 So(resp.StatusCode(), ShouldEqual, 200) 766 767 var responseStruct zcommon.RepoWithNewestImageResponse 768 err = json.Unmarshal(resp.Body(), &responseStruct) 769 So(err, ShouldBeNil) 770 So(len(responseStruct.Results), ShouldEqual, 4) 771 772 repos := responseStruct.Results 773 So(repos[0].NewestImage.Tag, ShouldEqual, "0.0.1") 774 775 for _, repo := range repos { 776 vulnerabilities := repo.NewestImage.Vulnerabilities 777 So(vulnerabilities, ShouldNotBeNil) 778 t.Logf("Found vulnerability summary %v", vulnerabilities) 779 // Depends on test data, but current tested images contain hundreds 780 So(vulnerabilities.Count, ShouldEqual, 4) 781 So(vulnerabilities.UnknownCount, ShouldEqual, 0) 782 So(vulnerabilities.LowCount, ShouldEqual, 1) 783 So(vulnerabilities.MediumCount, ShouldEqual, 1) 784 So(vulnerabilities.HighCount, ShouldEqual, 1) 785 So(vulnerabilities.CriticalCount, ShouldEqual, 1) 786 So(vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL") 787 } 788 }) 789 } 790 791 func TestGetReferrersGQL(t *testing.T) { 792 Convey("get referrers", t, func() { 793 port := GetFreePort() 794 baseURL := GetBaseURL(port) 795 conf := config.New() 796 conf.HTTP.Port = port 797 conf.Storage.RootDirectory = t.TempDir() 798 799 defaultVal := true 800 conf.Extensions = &extconf.ExtensionConfig{ 801 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 802 Lint: &extconf.LintConfig{ 803 BaseConfig: extconf.BaseConfig{ 804 Enable: &defaultVal, 805 }, 806 }, 807 } 808 809 gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix) 810 811 conf.Extensions.Search.CVE = nil 812 813 ctlr := api.NewController(conf) 814 ctlrManager := NewControllerManager(ctlr) 815 ctlrManager.StartAndWait(port) 816 defer ctlrManager.StopServer() 817 818 // ======================= 819 820 uploadedImage := CreateImageWith().RandomLayers(1, 1000).DefaultConfig().Build() 821 822 repo := "artifact-ref" 823 824 err := UploadImage(uploadedImage, baseURL, repo, "1.0") 825 So(err, ShouldBeNil) 826 827 manifestDigest := uploadedImage.ManifestDescriptor.Digest 828 manifestSize := uploadedImage.ManifestDescriptor.Size 829 830 subjectDescriptor := &ispec.Descriptor{ 831 MediaType: "application/vnd.oci.image.manifest.v1+json", 832 Size: manifestSize, 833 Digest: manifestDigest, 834 } 835 836 artifactContentBlob := []byte("test artifact") 837 artifactContentBlobSize := int64(len(artifactContentBlob)) 838 artifactContentType := "application/octet-stream" 839 artifactContentBlobDigest := godigest.FromBytes(artifactContentBlob) 840 artifactType := "com.artifact.test/type1" 841 842 artifactImg := Image{ 843 Manifest: ispec.Manifest{ 844 Layers: []ispec.Descriptor{ 845 { 846 MediaType: artifactContentType, 847 Digest: artifactContentBlobDigest, 848 Size: artifactContentBlobSize, 849 }, 850 }, 851 Subject: subjectDescriptor, 852 ArtifactType: artifactType, 853 Config: ispec.Descriptor{ 854 MediaType: ispec.MediaTypeEmptyJSON, 855 Digest: ispec.DescriptorEmptyJSON.Digest, 856 Data: ispec.DescriptorEmptyJSON.Data, 857 }, 858 MediaType: ispec.MediaTypeImageManifest, 859 Annotations: map[string]string{ 860 "com.artifact.format": "test", 861 }, 862 }, 863 Config: ispec.Image{}, 864 Layers: [][]byte{artifactContentBlob}, 865 } 866 867 artifactImg.Manifest.SchemaVersion = 2 868 869 artifactManifestBlob, err := json.Marshal(artifactImg.Manifest) 870 So(err, ShouldBeNil) 871 artifactManifestDigest := godigest.FromBytes(artifactManifestBlob) 872 873 err = UploadImage(artifactImg, baseURL, repo, artifactManifestDigest.String()) 874 So(err, ShouldBeNil) 875 876 gqlQuery := ` 877 { 878 Referrers( 879 repo: "%s", digest: "%s", type: ""){ 880 ArtifactType, 881 Digest, 882 MediaType, 883 Size, 884 Annotations{ 885 Key 886 Value 887 } 888 } 889 }` 890 891 strQuery := fmt.Sprintf(gqlQuery, repo, manifestDigest.String()) 892 893 targetURL := fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) 894 895 resp, err := resty.R().Get(targetURL) 896 So(resp, ShouldNotBeNil) 897 So(err, ShouldBeNil) 898 So(resp.StatusCode(), ShouldEqual, 200) 899 So(resp.Body(), ShouldNotBeNil) 900 901 referrersResp := &zcommon.ReferrersResp{} 902 903 err = json.Unmarshal(resp.Body(), referrersResp) 904 So(err, ShouldBeNil) 905 So(referrersResp.Errors, ShouldBeNil) 906 So(referrersResp.Referrers[0].ArtifactType, ShouldEqual, artifactType) 907 So(referrersResp.Referrers[0].MediaType, ShouldEqual, ispec.MediaTypeImageManifest) 908 909 So(referrersResp.Referrers[0].Annotations[0].Key, ShouldEqual, "com.artifact.format") 910 So(referrersResp.Referrers[0].Annotations[0].Value, ShouldEqual, "test") 911 912 So(referrersResp.Referrers[0].Digest, ShouldEqual, artifactManifestDigest.String()) 913 }) 914 915 Convey("referrers for image index", t, func() { 916 port := GetFreePort() 917 baseURL := GetBaseURL(port) 918 conf := config.New() 919 conf.HTTP.Port = port 920 conf.Storage.RootDirectory = t.TempDir() 921 conf.Storage.GC = false 922 923 defaultVal := true 924 conf.Extensions = &extconf.ExtensionConfig{ 925 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 926 Lint: &extconf.LintConfig{ 927 BaseConfig: extconf.BaseConfig{ 928 Enable: &defaultVal, 929 }, 930 }, 931 } 932 933 gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix) 934 935 conf.Extensions.Search.CVE = nil 936 937 ctlr := api.NewController(conf) 938 ctlrManager := NewControllerManager(ctlr) 939 ctlrManager.StartAndWait(port) 940 defer ctlrManager.StopServer() 941 942 // ======================= 943 944 multiarch := CreateRandomMultiarch() 945 946 repo := "artifact-ref" 947 948 err := UploadMultiarchImage(multiarch, baseURL, repo, "multiarch") 949 So(err, ShouldBeNil) 950 951 indexBlob, err := json.Marshal(multiarch.Index) 952 So(err, ShouldBeNil) 953 indexDigest := godigest.FromBytes(indexBlob) 954 indexSize := int64(len(indexBlob)) 955 956 subjectDescriptor := &ispec.Descriptor{ 957 MediaType: ispec.MediaTypeImageIndex, 958 Size: indexSize, 959 Digest: indexDigest, 960 } 961 962 artifactContentBlob := []byte("test artifact") 963 artifactContentBlobSize := int64(len(artifactContentBlob)) 964 artifactContentType := "application/octet-stream" 965 artifactContentBlobDigest := godigest.FromBytes(artifactContentBlob) 966 artifactType := "com.artifact.test/type2" 967 968 configBlob, err := json.Marshal(ispec.Image{}) 969 So(err, ShouldBeNil) 970 971 artifactManifest := ispec.Manifest{ 972 Layers: []ispec.Descriptor{ 973 { 974 MediaType: artifactContentType, 975 Digest: artifactContentBlobDigest, 976 Size: artifactContentBlobSize, 977 }, 978 }, 979 Subject: subjectDescriptor, 980 Config: ispec.Descriptor{ 981 MediaType: artifactType, 982 Digest: godigest.FromBytes(configBlob), 983 }, 984 MediaType: ispec.MediaTypeImageManifest, 985 Annotations: map[string]string{ 986 "com.artifact.format": "test", 987 }, 988 } 989 990 artifactManifest.SchemaVersion = 2 991 992 artifactManifestBlob, err := json.Marshal(artifactManifest) 993 So(err, ShouldBeNil) 994 artifactManifestDigest := godigest.FromBytes(artifactManifestBlob) 995 996 err = UploadImage( 997 Image{ 998 Manifest: artifactManifest, 999 Config: ispec.Image{}, 1000 Layers: [][]byte{artifactContentBlob}, 1001 }, baseURL, repo, artifactManifestDigest.String()) 1002 So(err, ShouldBeNil) 1003 1004 gqlQuery := ` 1005 { 1006 Referrers( repo: "%s", digest: "%s", type: "" ){ 1007 ArtifactType, 1008 Digest, 1009 MediaType, 1010 Size, 1011 Annotations{ 1012 Key 1013 Value 1014 } 1015 } 1016 }` 1017 1018 strQuery := fmt.Sprintf(gqlQuery, repo, indexDigest.String()) 1019 1020 targetURL := fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) 1021 1022 resp, err := resty.R().Get(targetURL) 1023 So(resp, ShouldNotBeNil) 1024 So(err, ShouldBeNil) 1025 So(resp.StatusCode(), ShouldEqual, 200) 1026 So(resp.Body(), ShouldNotBeNil) 1027 1028 referrersResp := &zcommon.ReferrersResp{} 1029 1030 err = json.Unmarshal(resp.Body(), referrersResp) 1031 So(err, ShouldBeNil) 1032 So(referrersResp.Errors, ShouldBeNil) 1033 So(len(referrersResp.Referrers), ShouldEqual, 1) 1034 So(referrersResp.Referrers[0].ArtifactType, ShouldEqual, artifactType) 1035 So(referrersResp.Referrers[0].MediaType, ShouldEqual, ispec.MediaTypeImageManifest) 1036 1037 So(referrersResp.Referrers[0].Annotations[0].Key, ShouldEqual, "com.artifact.format") 1038 So(referrersResp.Referrers[0].Annotations[0].Value, ShouldEqual, "test") 1039 1040 So(referrersResp.Referrers[0].Digest, ShouldEqual, artifactManifestDigest.String()) 1041 }) 1042 1043 Convey("Get referrers with index as referrer", t, func() { 1044 port := GetFreePort() 1045 baseURL := GetBaseURL(port) 1046 conf := config.New() 1047 conf.HTTP.Port = port 1048 conf.Storage.RootDirectory = t.TempDir() 1049 conf.Storage.GC = false 1050 1051 defaultVal := true 1052 conf.Extensions = &extconf.ExtensionConfig{ 1053 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 1054 Lint: &extconf.LintConfig{ 1055 BaseConfig: extconf.BaseConfig{ 1056 Enable: &defaultVal, 1057 }, 1058 }, 1059 } 1060 1061 conf.Extensions.Search.CVE = nil 1062 1063 ctlr := api.NewController(conf) 1064 ctlrManager := NewControllerManager(ctlr) 1065 ctlrManager.StartAndWait(port) 1066 defer ctlrManager.StopServer() 1067 1068 // Upload the index referrer 1069 targetImg := CreateRandomImage() 1070 targetDigest := targetImg.Digest() 1071 1072 err := UploadImage(targetImg, baseURL, "repo", targetImg.DigestStr()) 1073 So(err, ShouldBeNil) 1074 1075 artifactType := "com.artifact.art/type" 1076 indexReferrer := CreateMultiarchWith().RandomImages(2). 1077 ArtifactType(artifactType). 1078 Subject(targetImg.DescriptorRef()). 1079 Build() 1080 indexReferrerDigest := indexReferrer.Digest() 1081 1082 err = UploadMultiarchImage(indexReferrer, baseURL, "repo", "ref") 1083 So(err, ShouldBeNil) 1084 1085 // Call Referrers GQL 1086 1087 referrersQuery := ` 1088 { 1089 Referrers( repo: "%s", digest: "%s"){ 1090 ArtifactType, 1091 Digest, 1092 MediaType, 1093 Size, 1094 Annotations{ 1095 Key 1096 Value 1097 } 1098 } 1099 }` 1100 1101 referrersQuery = fmt.Sprintf(referrersQuery, "repo", targetDigest.String()) 1102 1103 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(referrersQuery)) 1104 So(resp, ShouldNotBeNil) 1105 So(err, ShouldBeNil) 1106 1107 So(resp.StatusCode(), ShouldEqual, 200) 1108 So(resp.Body(), ShouldNotBeNil) 1109 So(err, ShouldBeNil) 1110 1111 referrersResp := &zcommon.ReferrersResp{} 1112 1113 err = json.Unmarshal(resp.Body(), referrersResp) 1114 So(err, ShouldBeNil) 1115 So(len(referrersResp.Referrers), ShouldEqual, 1) 1116 So(referrersResp.Referrers[0].ArtifactType, ShouldResemble, artifactType) 1117 So(referrersResp.Referrers[0].Digest, ShouldResemble, indexReferrerDigest.String()) 1118 So(referrersResp.Referrers[0].MediaType, ShouldResemble, ispec.MediaTypeImageIndex) 1119 1120 // Make REST call 1121 1122 resp, err = resty.R().Get(baseURL + "/v2/repo/referrers/" + targetDigest.String()) 1123 So(err, ShouldBeNil) 1124 1125 var index ispec.Index 1126 1127 err = json.Unmarshal(resp.Body(), &index) 1128 So(err, ShouldBeNil) 1129 So(len(index.Manifests), ShouldEqual, 1) 1130 So(index.Manifests[0].ArtifactType, ShouldEqual, artifactType) 1131 So(index.Manifests[0].Digest.String(), ShouldResemble, indexReferrerDigest.String()) 1132 So(index.Manifests[0].MediaType, ShouldResemble, ispec.MediaTypeImageIndex) 1133 }) 1134 } 1135 1136 func TestExpandedRepoInfo(t *testing.T) { 1137 Convey("Filter out manifests with no tag", t, func() { 1138 tagToBeRemoved := "3.0" 1139 repo1 := "test1" 1140 tempDir := t.TempDir() 1141 port := GetFreePort() 1142 baseURL := GetBaseURL(port) 1143 1144 conf := config.New() 1145 conf.HTTP.Port = port 1146 conf.Storage.RootDirectory = tempDir 1147 defaultVal := true 1148 conf.Extensions = &extconf.ExtensionConfig{ 1149 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 1150 } 1151 1152 conf.Extensions.Search.CVE = nil 1153 1154 ctlr := api.NewController(conf) 1155 1156 imageStore := local.NewImageStore(tempDir, false, false, 1157 log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil) 1158 1159 storeController := storage.StoreController{ 1160 DefaultStore: imageStore, 1161 } 1162 1163 // init storage layout with 3 images 1164 for i := 1; i <= 3; i++ { 1165 image := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Build() 1166 1167 err := WriteImageToFileSystem(image, repo1, fmt.Sprintf("%d.0", i), storeController) 1168 So(err, ShouldBeNil) 1169 } 1170 1171 // remote a tag from index.json 1172 indexPath := path.Join(tempDir, repo1, "index.json") 1173 indexFile, err := os.Open(indexPath) 1174 So(err, ShouldBeNil) 1175 buf, err := io.ReadAll(indexFile) 1176 So(err, ShouldBeNil) 1177 1178 var index ispec.Index 1179 if err = json.Unmarshal(buf, &index); err == nil { 1180 for _, manifest := range index.Manifests { 1181 if val, ok := manifest.Annotations[ispec.AnnotationRefName]; ok && val == tagToBeRemoved { 1182 delete(manifest.Annotations, ispec.AnnotationRefName) 1183 1184 break 1185 } 1186 } 1187 } 1188 buf, err = json.Marshal(index) 1189 So(err, ShouldBeNil) 1190 1191 err = os.WriteFile(indexPath, buf, 0o600) 1192 So(err, ShouldBeNil) 1193 1194 ctlrManager := NewControllerManager(ctlr) 1195 ctlrManager.StartAndWait(port) 1196 defer ctlrManager.StopServer() 1197 1198 query := `{ 1199 ExpandedRepoInfo(repo:"test1"){ 1200 Summary { 1201 Name LastUpdated Size 1202 Platforms {Os Arch} 1203 Vendors 1204 } 1205 Images { 1206 Tag 1207 Manifests { 1208 Digest 1209 Layers {Size Digest} 1210 } 1211 } 1212 } 1213 }` 1214 1215 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1216 So(resp, ShouldNotBeNil) 1217 So(err, ShouldBeNil) 1218 responseStruct := &zcommon.ExpandedRepoInfoResp{} 1219 1220 err = json.Unmarshal(resp.Body(), responseStruct) 1221 So(err, ShouldBeNil) 1222 1223 So(resp.StatusCode(), ShouldEqual, 200) 1224 1225 responseStruct = &zcommon.ExpandedRepoInfoResp{} 1226 1227 err = json.Unmarshal(resp.Body(), responseStruct) 1228 So(err, ShouldBeNil) 1229 So(responseStruct.Summary, ShouldNotBeEmpty) 1230 So(responseStruct.Summary.Name, ShouldEqual, "test1") 1231 So(len(responseStruct.ImageSummaries), ShouldEqual, 2) 1232 }) 1233 1234 Convey("Test expanded repo info", t, func() { 1235 subpath := "/a" 1236 rootDir := t.TempDir() 1237 subRootDir := t.TempDir() 1238 port := GetFreePort() 1239 baseURL := GetBaseURL(port) 1240 conf := config.New() 1241 conf.HTTP.Port = port 1242 conf.Storage.RootDirectory = rootDir 1243 conf.Storage.GC = false 1244 conf.Storage.SubPaths = make(map[string]config.StorageConfig) 1245 conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir} 1246 defaultVal := true 1247 conf.Extensions = &extconf.ExtensionConfig{ 1248 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 1249 } 1250 1251 conf.Extensions.Search.CVE = nil 1252 1253 ctlr := api.NewController(conf) 1254 ctlrManager := NewControllerManager(ctlr) 1255 ctlrManager.StartAndWait(port) 1256 defer ctlrManager.StopServer() 1257 1258 annotations := make(map[string]string) 1259 annotations["org.opencontainers.image.vendor"] = "zot" 1260 1261 uploadedImage := CreateImageWith().RandomLayers(1, 100).DefaultConfig(). 1262 Annotations(annotations).Build() 1263 1264 err := UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1") 1265 So(err, ShouldBeNil) 1266 1267 err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1") 1268 So(err, ShouldBeNil) 1269 1270 err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1") 1271 So(err, ShouldBeNil) 1272 1273 err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1") 1274 So(err, ShouldBeNil) 1275 1276 log := log.NewLogger("debug", "") 1277 metrics := monitoring.NewMetricsServer(false, log) 1278 testStorage := local.NewImageStore(rootDir, false, false, log, metrics, nil, nil) 1279 1280 resp, err := resty.R().Get(baseURL + "/v2/") 1281 So(resp, ShouldNotBeNil) 1282 So(err, ShouldBeNil) 1283 So(resp.StatusCode(), ShouldEqual, 200) 1284 1285 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix) 1286 So(resp, ShouldNotBeNil) 1287 So(err, ShouldBeNil) 1288 So(resp.StatusCode(), ShouldEqual, 422) 1289 1290 query := `{ 1291 ExpandedRepoInfo(repo:"zot-cve-test"){ 1292 Summary { 1293 Name LastUpdated Size 1294 } 1295 } 1296 }` 1297 1298 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1299 So(resp, ShouldNotBeNil) 1300 So(err, ShouldBeNil) 1301 So(resp.StatusCode(), ShouldEqual, 200) 1302 1303 responseStruct := &zcommon.ExpandedRepoInfoResp{} 1304 1305 err = json.Unmarshal(resp.Body(), responseStruct) 1306 So(err, ShouldBeNil) 1307 So(responseStruct.Summary, ShouldNotBeEmpty) 1308 So(responseStruct.Summary.Name, ShouldEqual, "zot-cve-test") 1309 1310 query = `{ 1311 ExpandedRepoInfo(repo:"zot-cve-test"){ 1312 Images { 1313 Tag 1314 Manifests { 1315 Digest 1316 Layers {Size Digest} 1317 } 1318 IsSigned 1319 } 1320 } 1321 }` 1322 1323 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1324 So(resp, ShouldNotBeNil) 1325 So(err, ShouldBeNil) 1326 So(resp.StatusCode(), ShouldEqual, 200) 1327 1328 responseStruct = &zcommon.ExpandedRepoInfoResp{} 1329 1330 err = json.Unmarshal(resp.Body(), responseStruct) 1331 So(err, ShouldBeNil) 1332 So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) 1333 So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) 1334 1335 _, testManifestDigest, _, err := testStorage.GetImageManifest("zot-cve-test", "0.0.1") 1336 So(err, ShouldBeNil) 1337 1338 found := false 1339 for _, m := range responseStruct.ImageSummaries { 1340 if m.Manifests[0].Digest == testManifestDigest.String() { 1341 found = true 1342 So(m.IsSigned, ShouldEqual, false) 1343 } 1344 } 1345 So(found, ShouldEqual, true) 1346 1347 err = signature.SignImageUsingCosign("zot-cve-test:0.0.1", port, false) 1348 So(err, ShouldBeNil) 1349 1350 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1351 So(resp, ShouldNotBeNil) 1352 So(err, ShouldBeNil) 1353 So(resp.StatusCode(), ShouldEqual, 200) 1354 1355 err = json.Unmarshal(resp.Body(), responseStruct) 1356 So(err, ShouldBeNil) 1357 So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) 1358 So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) 1359 1360 _, testManifestDigest, _, err = testStorage.GetImageManifest("zot-cve-test", "0.0.1") 1361 So(err, ShouldBeNil) 1362 1363 found = false 1364 for _, m := range responseStruct.ImageSummaries { 1365 if m.Manifests[0].Digest == testManifestDigest.String() { 1366 found = true 1367 So(m.IsSigned, ShouldEqual, true) 1368 } 1369 } 1370 So(found, ShouldEqual, true) 1371 1372 query = `{ 1373 ExpandedRepoInfo(repo:""){ 1374 Images { 1375 Tag 1376 } 1377 } 1378 }` 1379 1380 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1381 So(resp, ShouldNotBeNil) 1382 So(err, ShouldBeNil) 1383 So(resp.StatusCode(), ShouldEqual, 200) 1384 1385 query = `{ 1386 ExpandedRepoInfo(repo:"zot-test"){ 1387 Images { 1388 RepoName 1389 Tag IsSigned 1390 Manifests{ 1391 Digest 1392 Layers {Size Digest} 1393 } 1394 } 1395 } 1396 }` 1397 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1398 So(resp, ShouldNotBeNil) 1399 So(err, ShouldBeNil) 1400 So(resp.StatusCode(), ShouldEqual, 200) 1401 1402 err = json.Unmarshal(resp.Body(), responseStruct) 1403 So(err, ShouldBeNil) 1404 So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) 1405 So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) 1406 1407 _, testManifestDigest, _, err = testStorage.GetImageManifest("zot-test", "0.0.1") 1408 So(err, ShouldBeNil) 1409 1410 found = false 1411 for _, m := range responseStruct.ImageSummaries { 1412 if m.Manifests[0].Digest == testManifestDigest.String() { 1413 found = true 1414 So(m.IsSigned, ShouldEqual, false) 1415 } 1416 } 1417 So(found, ShouldEqual, true) 1418 1419 err = signature.SignImageUsingCosign("zot-test@"+testManifestDigest.String(), port, false) 1420 So(err, ShouldBeNil) 1421 1422 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "/query?query=" + url.QueryEscape(query)) 1423 So(resp, ShouldNotBeNil) 1424 So(err, ShouldBeNil) 1425 So(resp.StatusCode(), ShouldEqual, 200) 1426 1427 err = json.Unmarshal(resp.Body(), responseStruct) 1428 So(err, ShouldBeNil) 1429 So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) 1430 So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) 1431 1432 _, testManifestDigest, _, err = testStorage.GetImageManifest("zot-test", "0.0.1") 1433 So(err, ShouldBeNil) 1434 1435 found = false 1436 for _, m := range responseStruct.ImageSummaries { 1437 if m.Manifests[0].Digest == testManifestDigest.String() { 1438 found = true 1439 So(m.IsSigned, ShouldEqual, true) 1440 } 1441 } 1442 So(found, ShouldEqual, true) 1443 1444 manifestDigest := uploadedImage.ManifestDescriptor.Digest 1445 1446 err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", manifestDigest.Encoded())) 1447 So(err, ShouldBeNil) 1448 1449 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1450 So(resp, ShouldNotBeNil) 1451 So(err, ShouldBeNil) 1452 So(resp.StatusCode(), ShouldEqual, 200) 1453 1454 err = json.Unmarshal(resp.Body(), responseStruct) 1455 So(err, ShouldBeNil) 1456 }) 1457 1458 Convey("Test expanded repo info with tagged referrers", t, func() { 1459 const testTag = "test" 1460 rootDir := t.TempDir() 1461 port := GetFreePort() 1462 baseURL := GetBaseURL(port) 1463 conf := config.New() 1464 conf.HTTP.Port = port 1465 conf.Storage.RootDirectory = rootDir 1466 conf.Storage.GC = false 1467 defaultVal := true 1468 conf.Extensions = &extconf.ExtensionConfig{ 1469 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 1470 } 1471 1472 conf.Extensions.Search.CVE = nil 1473 1474 ctlr := api.NewController(conf) 1475 ctlrManager := NewControllerManager(ctlr) 1476 ctlrManager.StartAndWait(port) 1477 defer ctlrManager.StopServer() 1478 1479 image := CreateImageWith().RandomLayers(1, 20).DefaultConfig().Build() 1480 1481 err := UploadImage(image, baseURL, "repo", testTag) 1482 So(err, ShouldBeNil) 1483 1484 referrer := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Subject(&ispec.Descriptor{ 1485 Digest: image.ManifestDescriptor.Digest, 1486 MediaType: ispec.MediaTypeImageManifest, 1487 }).Build() 1488 1489 tag := "test-ref-tag" 1490 err = UploadImage(referrer, baseURL, "repo", tag) 1491 So(err, ShouldBeNil) 1492 1493 // ------- Make the call to GQL and see that it doesn't crash 1494 responseStruct := &zcommon.ExpandedRepoInfoResp{} 1495 query := ` 1496 { 1497 ExpandedRepoInfo(repo:"repo"){ 1498 Images { 1499 RepoName 1500 Tag 1501 Manifests { 1502 Digest 1503 Layers {Size Digest} 1504 } 1505 } 1506 } 1507 }` 1508 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1509 So(resp, ShouldNotBeNil) 1510 So(err, ShouldBeNil) 1511 So(resp.StatusCode(), ShouldEqual, 200) 1512 1513 err = json.Unmarshal(resp.Body(), responseStruct) 1514 So(err, ShouldBeNil) 1515 So(len(responseStruct.ImageSummaries), ShouldEqual, 2) 1516 1517 repoInfo := responseStruct.RepoInfo 1518 1519 foundTagTest := false 1520 foundTagRefTag := false 1521 1522 for _, imgSum := range repoInfo.ImageSummaries { 1523 switch imgSum.Tag { 1524 case testTag: 1525 foundTagTest = true 1526 case "test-ref-tag": 1527 foundTagRefTag = true 1528 } 1529 } 1530 1531 So(foundTagTest || foundTagRefTag, ShouldEqual, true) 1532 }) 1533 1534 Convey("Test image tags order", t, func() { 1535 port := GetFreePort() 1536 baseURL := GetBaseURL(port) 1537 conf := config.New() 1538 conf.HTTP.Port = port 1539 conf.Storage.RootDirectory = t.TempDir() 1540 1541 defaultVal := true 1542 conf.Extensions = &extconf.ExtensionConfig{ 1543 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 1544 } 1545 1546 conf.Extensions.Search.CVE = nil 1547 1548 ctlr := api.NewController(conf) 1549 1550 ctlrManager := NewControllerManager(ctlr) 1551 ctlrManager.StartAndWait(port) 1552 defer ctlrManager.StopServer() 1553 1554 resp, err := resty.R().Get(baseURL + "/v2/") 1555 So(resp, ShouldNotBeNil) 1556 So(err, ShouldBeNil) 1557 So(resp.StatusCode(), ShouldEqual, 200) 1558 1559 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix) 1560 So(resp, ShouldNotBeNil) 1561 So(err, ShouldBeNil) 1562 So(resp.StatusCode(), ShouldEqual, 422) 1563 1564 // create test images 1565 repoName := "test-repo" //nolint:goconst 1566 layers := [][]byte{ 1567 {10, 11, 10, 11}, 1568 } 1569 1570 err = uploadNewRepoTag("1.0", repoName, baseURL, layers) 1571 So(err, ShouldBeNil) 1572 1573 err = uploadNewRepoTag("2.0", repoName, baseURL, layers) 1574 So(err, ShouldBeNil) 1575 1576 err = uploadNewRepoTag("3.0", repoName, baseURL, layers) 1577 So(err, ShouldBeNil) 1578 1579 responseStruct := &zcommon.ExpandedRepoInfoResp{} 1580 query := ` 1581 { 1582 ExpandedRepoInfo(repo:"test-repo"){ 1583 Images { 1584 RepoName 1585 Tag 1586 Manifests { 1587 Digest 1588 Layers {Size Digest} 1589 } 1590 } 1591 } 1592 }` 1593 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1594 So(resp, ShouldNotBeNil) 1595 So(err, ShouldBeNil) 1596 So(resp.StatusCode(), ShouldEqual, 200) 1597 1598 err = json.Unmarshal(resp.Body(), responseStruct) 1599 So(err, ShouldBeNil) 1600 So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) 1601 So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) 1602 1603 So(responseStruct.ImageSummaries[0].Tag, ShouldEqual, "3.0") 1604 So(responseStruct.ImageSummaries[1].Tag, ShouldEqual, "2.0") 1605 So(responseStruct.ImageSummaries[2].Tag, ShouldEqual, "1.0") 1606 }) 1607 1608 Convey("With Multiarch Images", t, func() { 1609 conf := config.New() 1610 conf.HTTP.Port = GetFreePort() 1611 baseURL := GetBaseURL(conf.HTTP.Port) 1612 conf.Storage.RootDirectory = t.TempDir() 1613 1614 defaultVal := true 1615 conf.Extensions = &extconf.ExtensionConfig{ 1616 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 1617 } 1618 1619 conf.Extensions.Search.CVE = nil 1620 ctlr := api.NewController(conf) 1621 1622 imageStore := local.NewImageStore(conf.Storage.RootDirectory, false, false, 1623 log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil) 1624 1625 storeController := storage.StoreController{ 1626 DefaultStore: imageStore, 1627 } 1628 1629 // ------- Create test images 1630 indexSubImage11 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{ 1631 Platform: ispec.Platform{ 1632 OS: "os11", 1633 Architecture: "arch11", 1634 }, 1635 }).Build() 1636 1637 indexSubImage12 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{ 1638 Platform: ispec.Platform{ 1639 OS: "os12", 1640 Architecture: "arch12", 1641 }, 1642 }).Build() 1643 1644 multiImage1 := CreateMultiarchWith().Images([]Image{ 1645 indexSubImage11, indexSubImage12, 1646 }).Build() 1647 1648 indexSubImage21 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{ 1649 Platform: ispec.Platform{ 1650 OS: "os21", 1651 Architecture: "arch21", 1652 }, 1653 }).Build() 1654 1655 indexSubImage22 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{ 1656 Platform: ispec.Platform{ 1657 OS: "os22", 1658 Architecture: "arch22", 1659 }, 1660 }).Build() 1661 1662 indexSubImage23 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{ 1663 Platform: ispec.Platform{ 1664 OS: "os23", 1665 Architecture: "arch23", 1666 }, 1667 }).Build() 1668 1669 multiImage2 := CreateMultiarchWith().Images([]Image{ 1670 indexSubImage21, indexSubImage22, indexSubImage23, 1671 }).Build() 1672 1673 // ------- Write test Images 1674 err := WriteMultiArchImageToFileSystem(multiImage1, "repo", "1.0.0", storeController) 1675 So(err, ShouldBeNil) 1676 1677 err = WriteMultiArchImageToFileSystem(multiImage2, "repo", "2.0.0", storeController) 1678 So(err, ShouldBeNil) 1679 // ------- Start Server /tmp/TestExpandedRepoInfo4021254039/005 1680 1681 ctlrManager := NewControllerManager(ctlr) 1682 ctlrManager.StartAndWait(conf.HTTP.Port) 1683 defer ctlrManager.StopServer() 1684 1685 // ------- Test ExpandedRepoInfo 1686 responseStruct := &zcommon.ExpandedRepoInfoResp{} 1687 1688 query := ` 1689 { 1690 ExpandedRepoInfo(repo:"repo"){ 1691 Images { 1692 RepoName 1693 Tag 1694 Manifests { 1695 Digest 1696 Layers {Size Digest} 1697 } 1698 } 1699 } 1700 }` 1701 1702 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 1703 So(resp, ShouldNotBeNil) 1704 So(err, ShouldBeNil) 1705 So(resp.StatusCode(), ShouldEqual, 200) 1706 1707 err = json.Unmarshal(resp.Body(), responseStruct) 1708 So(err, ShouldBeNil) 1709 So(len(responseStruct.Summary.Platforms), ShouldNotEqual, 5) 1710 1711 found := false 1712 for _, is := range responseStruct.ImageSummaries { 1713 if is.Tag == "1.0.0" { 1714 found = true 1715 1716 So(len(is.Manifests), ShouldEqual, 2) 1717 } 1718 } 1719 So(found, ShouldBeTrue) 1720 1721 found = false 1722 for _, is := range responseStruct.ImageSummaries { 1723 if is.Tag == "2.0.0" { 1724 found = true 1725 1726 So(len(is.Manifests), ShouldEqual, 3) 1727 } 1728 } 1729 So(found, ShouldBeTrue) 1730 }) 1731 } 1732 1733 func TestDerivedImageList(t *testing.T) { 1734 rootDir := t.TempDir() 1735 1736 port := GetFreePort() 1737 baseURL := GetBaseURL(port) 1738 conf := config.New() 1739 conf.HTTP.Port = port 1740 conf.Storage.RootDirectory = rootDir 1741 defaultVal := true 1742 conf.Extensions = &extconf.ExtensionConfig{ 1743 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 1744 } 1745 1746 conf.Extensions.Search.CVE = nil 1747 1748 ctlr := api.NewController(conf) 1749 ctlrManager := NewControllerManager(ctlr) 1750 1751 ctlrManager.StartAndWait(port) 1752 defer ctlrManager.StopServer() 1753 1754 Convey("Test dependency list for image working", t, func() { 1755 // create test images 1756 config := ispec.Image{ 1757 Platform: ispec.Platform{ 1758 Architecture: "amd64", 1759 OS: "linux", 1760 }, 1761 RootFS: ispec.RootFS{ 1762 Type: "layers", 1763 DiffIDs: []godigest.Digest{}, 1764 }, 1765 Author: "ZotUser", 1766 } 1767 1768 configBlob, err := json.Marshal(config) 1769 So(err, ShouldBeNil) 1770 1771 configDigest := godigest.FromBytes(configBlob) 1772 1773 layers := [][]byte{ 1774 {10, 11, 10, 11}, 1775 {11, 11, 11, 11}, 1776 {10, 10, 10, 11}, 1777 } 1778 1779 manifest := ispec.Manifest{ 1780 Versioned: specs.Versioned{ 1781 SchemaVersion: 2, 1782 }, 1783 Config: ispec.Descriptor{ 1784 MediaType: "application/vnd.oci.image.config.v1+json", 1785 Digest: configDigest, 1786 Size: int64(len(configBlob)), 1787 }, 1788 Layers: []ispec.Descriptor{ 1789 { 1790 MediaType: "application/vnd.oci.image.layer.v1.tar", 1791 Digest: godigest.FromBytes(layers[0]), 1792 Size: int64(len(layers[0])), 1793 }, 1794 { 1795 MediaType: "application/vnd.oci.image.layer.v1.tar", 1796 Digest: godigest.FromBytes(layers[1]), 1797 Size: int64(len(layers[1])), 1798 }, 1799 { 1800 MediaType: "application/vnd.oci.image.layer.v1.tar", 1801 Digest: godigest.FromBytes(layers[2]), 1802 Size: int64(len(layers[2])), 1803 }, 1804 }, 1805 } 1806 1807 repoName := "test-repo" //nolint:goconst 1808 1809 err = UploadImage( 1810 Image{ 1811 Manifest: manifest, 1812 Config: config, 1813 Layers: layers, 1814 }, baseURL, repoName, "latest", 1815 ) 1816 So(err, ShouldBeNil) 1817 1818 // create image with the same layers 1819 manifest = ispec.Manifest{ 1820 Versioned: specs.Versioned{ 1821 SchemaVersion: 2, 1822 }, 1823 Config: ispec.Descriptor{ 1824 MediaType: "application/vnd.oci.image.config.v1+json", 1825 Digest: configDigest, 1826 Size: int64(len(configBlob)), 1827 }, 1828 Layers: []ispec.Descriptor{ 1829 { 1830 MediaType: "application/vnd.oci.image.layer.v1.tar", 1831 Digest: godigest.FromBytes(layers[0]), 1832 Size: int64(len(layers[0])), 1833 }, 1834 { 1835 MediaType: "application/vnd.oci.image.layer.v1.tar", 1836 Digest: godigest.FromBytes(layers[1]), 1837 Size: int64(len(layers[1])), 1838 }, 1839 { 1840 MediaType: "application/vnd.oci.image.layer.v1.tar", 1841 Digest: godigest.FromBytes(layers[2]), 1842 Size: int64(len(layers[2])), 1843 }, 1844 }, 1845 } 1846 1847 repoName = "same-layers" //nolint:goconst 1848 1849 err = UploadImage( 1850 Image{ 1851 Manifest: manifest, 1852 Config: config, 1853 Layers: layers, 1854 }, baseURL, repoName, "latest", 1855 ) 1856 So(err, ShouldBeNil) 1857 1858 // create image with missing layer 1859 layers = [][]byte{ 1860 {10, 11, 10, 11}, 1861 {10, 10, 10, 11}, 1862 } 1863 1864 manifest = ispec.Manifest{ 1865 Versioned: specs.Versioned{ 1866 SchemaVersion: 2, 1867 }, 1868 Config: ispec.Descriptor{ 1869 MediaType: "application/vnd.oci.image.config.v1+json", 1870 Digest: configDigest, 1871 Size: int64(len(configBlob)), 1872 }, 1873 Layers: []ispec.Descriptor{ 1874 { 1875 MediaType: "application/vnd.oci.image.layer.v1.tar", 1876 Digest: godigest.FromBytes(layers[0]), 1877 Size: int64(len(layers[0])), 1878 }, 1879 { 1880 MediaType: "application/vnd.oci.image.layer.v1.tar", 1881 Digest: godigest.FromBytes(layers[1]), 1882 Size: int64(len(layers[1])), 1883 }, 1884 }, 1885 } 1886 1887 repoName = "missing-layer" 1888 1889 err = UploadImage( 1890 Image{ 1891 Manifest: manifest, 1892 Config: config, 1893 Layers: layers, 1894 }, baseURL, repoName, "latest", 1895 ) 1896 So(err, ShouldBeNil) 1897 1898 // create image with more layers than the original 1899 layers = [][]byte{ 1900 {10, 11, 10, 11}, 1901 {11, 11, 11, 11}, 1902 {10, 10, 10, 10}, 1903 {10, 10, 10, 11}, 1904 {11, 11, 10, 10}, 1905 {11, 10, 10, 10}, 1906 } 1907 1908 manifest = ispec.Manifest{ 1909 Versioned: specs.Versioned{ 1910 SchemaVersion: 2, 1911 }, 1912 Config: ispec.Descriptor{ 1913 MediaType: "application/vnd.oci.image.config.v1+json", 1914 Digest: configDigest, 1915 Size: int64(len(configBlob)), 1916 }, 1917 Layers: []ispec.Descriptor{ 1918 { 1919 MediaType: "application/vnd.oci.image.layer.v1.tar", 1920 Digest: godigest.FromBytes(layers[0]), 1921 Size: int64(len(layers[0])), 1922 }, 1923 { 1924 MediaType: "application/vnd.oci.image.layer.v1.tar", 1925 Digest: godigest.FromBytes(layers[1]), 1926 Size: int64(len(layers[1])), 1927 }, 1928 { 1929 MediaType: "application/vnd.oci.image.layer.v1.tar", 1930 Digest: godigest.FromBytes(layers[2]), 1931 Size: int64(len(layers[2])), 1932 }, 1933 { 1934 MediaType: "application/vnd.oci.image.layer.v1.tar", 1935 Digest: godigest.FromBytes(layers[3]), 1936 Size: int64(len(layers[3])), 1937 }, 1938 { 1939 MediaType: "application/vnd.oci.image.layer.v1.tar", 1940 Digest: godigest.FromBytes(layers[4]), 1941 Size: int64(len(layers[4])), 1942 }, 1943 }, 1944 } 1945 1946 repoName = "more-layers" 1947 1948 err = UploadImage( 1949 Image{ 1950 Manifest: manifest, 1951 Config: config, 1952 Layers: layers, 1953 }, baseURL, repoName, "latest", 1954 ) 1955 So(err, ShouldBeNil) 1956 1957 manifest = ispec.Manifest{ 1958 Versioned: specs.Versioned{ 1959 SchemaVersion: 2, 1960 }, 1961 Config: ispec.Descriptor{ 1962 MediaType: "application/vnd.oci.image.config.v1+json", 1963 Digest: configDigest, 1964 Size: int64(len(configBlob)), 1965 }, 1966 Layers: []ispec.Descriptor{ 1967 { 1968 MediaType: "application/vnd.oci.image.layer.v1.tar", 1969 Digest: godigest.FromBytes(layers[0]), 1970 Size: int64(len(layers[0])), 1971 }, 1972 { 1973 MediaType: "application/vnd.oci.image.layer.v1.tar", 1974 Digest: godigest.FromBytes(layers[1]), 1975 Size: int64(len(layers[1])), 1976 }, 1977 { 1978 MediaType: "application/vnd.oci.image.layer.v1.tar", 1979 Digest: godigest.FromBytes(layers[2]), 1980 Size: int64(len(layers[2])), 1981 }, 1982 { 1983 MediaType: "application/vnd.oci.image.layer.v1.tar", 1984 Digest: godigest.FromBytes(layers[3]), 1985 Size: int64(len(layers[3])), 1986 }, 1987 { 1988 MediaType: "application/vnd.oci.image.layer.v1.tar", 1989 Digest: godigest.FromBytes(layers[4]), 1990 Size: int64(len(layers[4])), 1991 }, 1992 { 1993 MediaType: "application/vnd.oci.image.layer.v1.tar", 1994 Digest: godigest.FromBytes(layers[5]), 1995 Size: int64(len(layers[5])), 1996 }, 1997 }, 1998 } 1999 2000 repoName = "all-layers" 2001 2002 err = UploadImage( 2003 Image{ 2004 Manifest: manifest, 2005 Config: config, 2006 Layers: layers, 2007 }, baseURL, repoName, "latest", 2008 ) 2009 So(err, ShouldBeNil) 2010 2011 Convey("non paginated query", func() { 2012 query := ` 2013 { 2014 DerivedImageList(image:"test-repo:latest"){ 2015 Results{ 2016 RepoName 2017 Tag 2018 Manifests { 2019 Digest 2020 ConfigDigest 2021 LastUpdated 2022 Size 2023 } 2024 Size 2025 } 2026 } 2027 }` 2028 2029 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2030 So(resp, ShouldNotBeNil) 2031 So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst 2032 So(strings.Contains(string(resp.Body()), "missing-layers"), ShouldBeFalse) 2033 So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeTrue) 2034 So(strings.Contains(string(resp.Body()), "all-layers"), ShouldBeTrue) 2035 So(err, ShouldBeNil) 2036 So(resp.StatusCode(), ShouldEqual, 200) 2037 }) 2038 2039 Convey("paginated query", func() { 2040 query := ` 2041 { 2042 DerivedImageList(image:"test-repo:latest", requestedPage:{limit: 1, offset: 0, sortBy:ALPHABETIC_ASC}){ 2043 Results{ 2044 RepoName 2045 Tag 2046 Manifests { 2047 Digest 2048 ConfigDigest 2049 LastUpdated 2050 Size 2051 } 2052 Size 2053 } 2054 } 2055 }` 2056 2057 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2058 So(resp, ShouldNotBeNil) 2059 So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst 2060 So(strings.Contains(string(resp.Body()), "missing-layers"), ShouldBeFalse) 2061 So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse) 2062 So(strings.Contains(string(resp.Body()), "all-layers"), ShouldBeTrue) 2063 So(err, ShouldBeNil) 2064 So(resp.StatusCode(), ShouldEqual, 200) 2065 }) 2066 }) 2067 2068 Convey("Inexistent repository", t, func() { 2069 query := ` 2070 { 2071 DerivedImageList(image:"inexistent-image:latest"){ 2072 Results{ 2073 RepoName 2074 Tag 2075 Manifests { 2076 Digest 2077 ConfigDigest 2078 LastUpdated 2079 Size 2080 } 2081 Size 2082 } 2083 } 2084 }` 2085 2086 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2087 So(string(resp.Body()), ShouldContainSubstring, "repository not found") 2088 So(err, ShouldBeNil) 2089 }) 2090 2091 Convey("Invalid query, no reference provided", t, func() { 2092 query := ` 2093 { 2094 DerivedImageList(image:"inexistent-image"){ 2095 Results{ 2096 RepoName 2097 Tag 2098 Manifests { 2099 Digest 2100 ConfigDigest 2101 LastUpdated 2102 Size 2103 } 2104 Size 2105 } 2106 } 2107 }` 2108 2109 responseStruct := &zcommon.DerivedImageListResponse{} 2110 contains := false 2111 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2112 So(err, ShouldBeNil) 2113 2114 err = json.Unmarshal(resp.Body(), responseStruct) 2115 So(err, ShouldBeNil) 2116 for _, err := range responseStruct.Errors { 2117 result := strings.Contains(err.Message, "no reference provided") 2118 if result { 2119 contains = result 2120 } 2121 } 2122 So(contains, ShouldBeTrue) 2123 }) 2124 } 2125 2126 //nolint:dupl 2127 func TestDerivedImageListNoRepos(t *testing.T) { 2128 Convey("No repositories found", t, func() { 2129 port := GetFreePort() 2130 baseURL := GetBaseURL(port) 2131 2132 conf := config.New() 2133 conf.HTTP.Port = port 2134 conf.Storage.RootDirectory = t.TempDir() 2135 defaultVal := true 2136 conf.Extensions = &extconf.ExtensionConfig{ 2137 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 2138 } 2139 2140 conf.Extensions.Search.CVE = nil 2141 2142 ctlr := api.NewController(conf) 2143 2144 ctlrManager := NewControllerManager(ctlr) 2145 ctlrManager.StartAndWait(port) 2146 defer ctlrManager.StopServer() 2147 2148 query := ` 2149 { 2150 DerivedImageList(image:"test-image:latest"){ 2151 Results{ 2152 RepoName 2153 Tag 2154 Manifests { 2155 Digest 2156 ConfigDigest 2157 LastUpdated 2158 Size 2159 } 2160 Size 2161 } 2162 } 2163 }` 2164 2165 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2166 So(resp, ShouldNotBeNil) 2167 So(err, ShouldBeNil) 2168 So(resp.StatusCode(), ShouldEqual, 200) 2169 2170 So(string(resp.Body()), ShouldContainSubstring, "repository not found") 2171 So(err, ShouldBeNil) 2172 }) 2173 } 2174 2175 func TestGetImageManifest(t *testing.T) { 2176 Convey("Test nonexistent image", t, func() { 2177 mockImageStore := mocks.MockedImageStore{} 2178 2179 storeController := storage.StoreController{ 2180 DefaultStore: mockImageStore, 2181 } 2182 olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) 2183 2184 _, _, err := olu.GetImageManifest("nonexistent-repo", "latest") 2185 So(err, ShouldNotBeNil) 2186 }) 2187 2188 Convey("Test nonexistent image", t, func() { 2189 mockImageStore := mocks.MockedImageStore{ 2190 GetImageManifestFn: func(repo string, reference string) ([]byte, godigest.Digest, string, error) { 2191 return []byte{}, "", "", ErrTestError 2192 }, 2193 } 2194 2195 storeController := storage.StoreController{ 2196 DefaultStore: mockImageStore, 2197 } 2198 olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) 2199 2200 _, _, err := olu.GetImageManifest("test-repo", "latest") //nolint:goconst 2201 So(err, ShouldNotBeNil) 2202 }) 2203 } 2204 2205 func TestBaseImageList(t *testing.T) { 2206 rootDir := t.TempDir() 2207 2208 port := GetFreePort() 2209 baseURL := GetBaseURL(port) 2210 conf := config.New() 2211 conf.HTTP.Port = port 2212 conf.Storage.RootDirectory = rootDir 2213 defaultVal := true 2214 conf.Extensions = &extconf.ExtensionConfig{ 2215 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 2216 } 2217 2218 conf.Extensions.Search.CVE = nil 2219 2220 ctlr := api.NewController(conf) 2221 ctlrManager := NewControllerManager(ctlr) 2222 2223 ctlrManager.StartAndWait(port) 2224 defer ctlrManager.StopServer() 2225 2226 Convey("Test base image list for image working", t, func() { 2227 // create test images 2228 config := ispec.Image{ 2229 Platform: ispec.Platform{ 2230 Architecture: "amd64", 2231 OS: "linux", 2232 }, 2233 RootFS: ispec.RootFS{ 2234 Type: "layers", 2235 DiffIDs: []godigest.Digest{}, 2236 }, 2237 Author: "ZotUser", 2238 } 2239 2240 configBlob, err := json.Marshal(config) 2241 So(err, ShouldBeNil) 2242 2243 configDigest := godigest.FromBytes(configBlob) 2244 2245 layers := [][]byte{ 2246 {10, 11, 10, 11}, 2247 {11, 11, 11, 11}, 2248 {10, 10, 10, 11}, 2249 {10, 10, 10, 10}, 2250 } 2251 2252 manifest := ispec.Manifest{ 2253 Versioned: specs.Versioned{ 2254 SchemaVersion: 2, 2255 }, 2256 Config: ispec.Descriptor{ 2257 MediaType: "application/vnd.oci.image.config.v1+json", 2258 Digest: configDigest, 2259 Size: int64(len(configBlob)), 2260 }, 2261 Layers: []ispec.Descriptor{ 2262 { 2263 MediaType: "application/vnd.oci.image.layer.v1.tar", 2264 Digest: godigest.FromBytes(layers[0]), 2265 Size: int64(len(layers[0])), 2266 }, 2267 { 2268 MediaType: "application/vnd.oci.image.layer.v1.tar", 2269 Digest: godigest.FromBytes(layers[1]), 2270 Size: int64(len(layers[1])), 2271 }, 2272 { 2273 MediaType: "application/vnd.oci.image.layer.v1.tar", 2274 Digest: godigest.FromBytes(layers[2]), 2275 Size: int64(len(layers[2])), 2276 }, 2277 { 2278 MediaType: "application/vnd.oci.image.layer.v1.tar", 2279 Digest: godigest.FromBytes(layers[3]), 2280 Size: int64(len(layers[3])), 2281 }, 2282 }, 2283 } 2284 2285 repoName := "test-repo" //nolint:goconst 2286 2287 err = UploadImage( 2288 Image{ 2289 Manifest: manifest, 2290 Config: config, 2291 Layers: layers, 2292 }, baseURL, repoName, "latest", 2293 ) 2294 So(err, ShouldBeNil) 2295 2296 // create image with the same layers 2297 manifest = ispec.Manifest{ 2298 Versioned: specs.Versioned{ 2299 SchemaVersion: 2, 2300 }, 2301 Config: ispec.Descriptor{ 2302 MediaType: "application/vnd.oci.image.config.v1+json", 2303 Digest: configDigest, 2304 Size: int64(len(configBlob)), 2305 }, 2306 Layers: []ispec.Descriptor{ 2307 { 2308 MediaType: "application/vnd.oci.image.layer.v1.tar", 2309 Digest: godigest.FromBytes(layers[0]), 2310 Size: int64(len(layers[0])), 2311 }, 2312 { 2313 MediaType: "application/vnd.oci.image.layer.v1.tar", 2314 Digest: godigest.FromBytes(layers[1]), 2315 Size: int64(len(layers[1])), 2316 }, 2317 { 2318 MediaType: "application/vnd.oci.image.layer.v1.tar", 2319 Digest: godigest.FromBytes(layers[2]), 2320 Size: int64(len(layers[2])), 2321 }, 2322 { 2323 MediaType: "application/vnd.oci.image.layer.v1.tar", 2324 Digest: godigest.FromBytes(layers[3]), 2325 Size: int64(len(layers[3])), 2326 }, 2327 }, 2328 } 2329 2330 repoName = "same-layers" //nolint:goconst 2331 2332 err = UploadImage( 2333 Image{ 2334 Manifest: manifest, 2335 Config: config, 2336 Layers: layers, 2337 }, baseURL, repoName, "latest", 2338 ) 2339 So(err, ShouldBeNil) 2340 2341 // create image with less layers than the given image, but which are in the given image 2342 layers = [][]byte{ 2343 {10, 11, 10, 11}, 2344 {10, 10, 10, 11}, 2345 } 2346 2347 manifest = ispec.Manifest{ 2348 Versioned: specs.Versioned{ 2349 SchemaVersion: 2, 2350 }, 2351 Config: ispec.Descriptor{ 2352 MediaType: "application/vnd.oci.image.config.v1+json", 2353 Digest: configDigest, 2354 Size: int64(len(configBlob)), 2355 }, 2356 Layers: []ispec.Descriptor{ 2357 { 2358 MediaType: "application/vnd.oci.image.layer.v1.tar", 2359 Digest: godigest.FromBytes(layers[0]), 2360 Size: int64(len(layers[0])), 2361 }, 2362 { 2363 MediaType: "application/vnd.oci.image.layer.v1.tar", 2364 Digest: godigest.FromBytes(layers[1]), 2365 Size: int64(len(layers[1])), 2366 }, 2367 }, 2368 } 2369 2370 repoName = "less-layers" 2371 2372 err = UploadImage( 2373 Image{ 2374 Manifest: manifest, 2375 Config: config, 2376 Layers: layers, 2377 }, baseURL, repoName, "latest", 2378 ) 2379 So(err, ShouldBeNil) 2380 2381 // create image with one layer, which is also present in the given image 2382 layers = [][]byte{ 2383 {10, 11, 10, 11}, 2384 } 2385 2386 manifest = ispec.Manifest{ 2387 Versioned: specs.Versioned{ 2388 SchemaVersion: 2, 2389 }, 2390 Config: ispec.Descriptor{ 2391 MediaType: "application/vnd.oci.image.config.v1+json", 2392 Digest: configDigest, 2393 Size: int64(len(configBlob)), 2394 }, 2395 Layers: []ispec.Descriptor{ 2396 { 2397 MediaType: "application/vnd.oci.image.layer.v1.tar", 2398 Digest: godigest.FromBytes(layers[0]), 2399 Size: int64(len(layers[0])), 2400 }, 2401 }, 2402 } 2403 2404 err = UploadImage( 2405 Image{ 2406 Manifest: manifest, 2407 Config: config, 2408 Layers: layers, 2409 }, baseURL, "one-layer", "latest", 2410 ) 2411 So(err, ShouldBeNil) 2412 2413 // create image with one layer, which is also present in the given image 2414 layers = [][]byte{ 2415 {10, 11, 10, 11}, 2416 } 2417 2418 manifest = ispec.Manifest{ 2419 Versioned: specs.Versioned{ 2420 SchemaVersion: 2, 2421 }, 2422 Config: ispec.Descriptor{ 2423 MediaType: "application/vnd.oci.image.config.v1+json", 2424 Digest: configDigest, 2425 Size: int64(len(configBlob)), 2426 }, 2427 Layers: []ispec.Descriptor{ 2428 { 2429 MediaType: "application/vnd.oci.image.layer.v1.tar", 2430 Digest: godigest.FromBytes(layers[0]), 2431 Size: int64(len(layers[0])), 2432 }, 2433 }, 2434 } 2435 2436 err = UploadImage( 2437 Image{ 2438 Manifest: manifest, 2439 Config: config, 2440 Layers: layers, 2441 }, baseURL, "one-layer", "latest", 2442 ) 2443 So(err, ShouldBeNil) 2444 2445 // create image with one layer, which is also present in the given image 2446 layers = [][]byte{ 2447 {10, 11, 10, 11}, 2448 } 2449 2450 manifest = ispec.Manifest{ 2451 Versioned: specs.Versioned{ 2452 SchemaVersion: 2, 2453 }, 2454 Config: ispec.Descriptor{ 2455 MediaType: "application/vnd.oci.image.config.v1+json", 2456 Digest: configDigest, 2457 Size: int64(len(configBlob)), 2458 }, 2459 Layers: []ispec.Descriptor{ 2460 { 2461 MediaType: "application/vnd.oci.image.layer.v1.tar", 2462 Digest: godigest.FromBytes(layers[0]), 2463 Size: int64(len(layers[0])), 2464 }, 2465 }, 2466 } 2467 2468 repoName = "one-layer" 2469 2470 err = UploadImage( 2471 Image{ 2472 Manifest: manifest, 2473 Config: config, 2474 Layers: layers, 2475 }, baseURL, repoName, "latest", 2476 ) 2477 So(err, ShouldBeNil) 2478 2479 // create image with one layer, which is also present in the given image 2480 layers = [][]byte{ 2481 {10, 11, 10, 11}, 2482 } 2483 2484 manifest = ispec.Manifest{ 2485 Versioned: specs.Versioned{ 2486 SchemaVersion: 2, 2487 }, 2488 Config: ispec.Descriptor{ 2489 MediaType: "application/vnd.oci.image.config.v1+json", 2490 Digest: configDigest, 2491 Size: int64(len(configBlob)), 2492 }, 2493 Layers: []ispec.Descriptor{ 2494 { 2495 MediaType: "application/vnd.oci.image.layer.v1.tar", 2496 Digest: godigest.FromBytes(layers[0]), 2497 Size: int64(len(layers[0])), 2498 }, 2499 }, 2500 } 2501 2502 repoName = "one-layer" 2503 2504 err = UploadImage( 2505 Image{ 2506 Manifest: manifest, 2507 Config: config, 2508 Layers: layers, 2509 }, baseURL, repoName, "latest", 2510 ) 2511 So(err, ShouldBeNil) 2512 2513 // create image with less layers than the given image, but one layer isn't in the given image 2514 layers = [][]byte{ 2515 {10, 11, 10, 11}, 2516 {11, 10, 10, 11}, 2517 } 2518 2519 manifest = ispec.Manifest{ 2520 Versioned: specs.Versioned{ 2521 SchemaVersion: 2, 2522 }, 2523 Config: ispec.Descriptor{ 2524 MediaType: "application/vnd.oci.image.config.v1+json", 2525 Digest: configDigest, 2526 Size: int64(len(configBlob)), 2527 }, 2528 Layers: []ispec.Descriptor{ 2529 { 2530 MediaType: "application/vnd.oci.image.layer.v1.tar", 2531 Digest: godigest.FromBytes(layers[0]), 2532 Size: int64(len(layers[0])), 2533 }, 2534 { 2535 MediaType: "application/vnd.oci.image.layer.v1.tar", 2536 Digest: godigest.FromBytes(layers[1]), 2537 Size: int64(len(layers[1])), 2538 }, 2539 }, 2540 } 2541 2542 repoName = "less-layers-false" 2543 2544 err = UploadImage( 2545 Image{ 2546 Manifest: manifest, 2547 Config: config, 2548 Layers: layers, 2549 }, baseURL, repoName, "latest", 2550 ) 2551 So(err, ShouldBeNil) 2552 2553 // create image with more layers than the original 2554 layers = [][]byte{ 2555 {10, 11, 10, 11}, 2556 {11, 11, 11, 11}, 2557 {10, 10, 10, 10}, 2558 {10, 10, 10, 11}, 2559 {11, 11, 10, 10}, 2560 } 2561 2562 manifest = ispec.Manifest{ 2563 Versioned: specs.Versioned{ 2564 SchemaVersion: 2, 2565 }, 2566 Config: ispec.Descriptor{ 2567 MediaType: "application/vnd.oci.image.config.v1+json", 2568 Digest: configDigest, 2569 Size: int64(len(configBlob)), 2570 }, 2571 Layers: []ispec.Descriptor{ 2572 { 2573 MediaType: "application/vnd.oci.image.layer.v1.tar", 2574 Digest: godigest.FromBytes(layers[0]), 2575 Size: int64(len(layers[0])), 2576 }, 2577 { 2578 MediaType: "application/vnd.oci.image.layer.v1.tar", 2579 Digest: godigest.FromBytes(layers[1]), 2580 Size: int64(len(layers[1])), 2581 }, 2582 { 2583 MediaType: "application/vnd.oci.image.layer.v1.tar", 2584 Digest: godigest.FromBytes(layers[2]), 2585 Size: int64(len(layers[2])), 2586 }, 2587 { 2588 MediaType: "application/vnd.oci.image.layer.v1.tar", 2589 Digest: godigest.FromBytes(layers[3]), 2590 Size: int64(len(layers[3])), 2591 }, 2592 { 2593 MediaType: "application/vnd.oci.image.layer.v1.tar", 2594 Digest: godigest.FromBytes(layers[4]), 2595 Size: int64(len(layers[4])), 2596 }, 2597 }, 2598 } 2599 2600 repoName = "more-layers" 2601 2602 err = UploadImage( 2603 Image{ 2604 Manifest: manifest, 2605 Config: config, 2606 Layers: layers, 2607 }, baseURL, repoName, "latest", 2608 ) 2609 So(err, ShouldBeNil) 2610 2611 // create image with no shared layers with the given image 2612 layers = [][]byte{ 2613 {12, 12, 12, 12}, 2614 {12, 10, 10, 12}, 2615 } 2616 2617 manifest = ispec.Manifest{ 2618 Versioned: specs.Versioned{ 2619 SchemaVersion: 2, 2620 }, 2621 Config: ispec.Descriptor{ 2622 MediaType: "application/vnd.oci.image.config.v1+json", 2623 Digest: configDigest, 2624 Size: int64(len(configBlob)), 2625 }, 2626 Layers: []ispec.Descriptor{ 2627 { 2628 MediaType: "application/vnd.oci.image.layer.v1.tar", 2629 Digest: godigest.FromBytes(layers[0]), 2630 Size: int64(len(layers[0])), 2631 }, 2632 { 2633 MediaType: "application/vnd.oci.image.layer.v1.tar", 2634 Digest: godigest.FromBytes(layers[1]), 2635 Size: int64(len(layers[1])), 2636 }, 2637 }, 2638 } 2639 2640 repoName = "diff-layers" 2641 2642 err = UploadImage( 2643 Image{ 2644 Manifest: manifest, 2645 Config: config, 2646 Layers: layers, 2647 }, baseURL, repoName, "latest", 2648 ) 2649 So(err, ShouldBeNil) 2650 2651 Convey("non paginated query", func() { 2652 query := ` 2653 { 2654 BaseImageList(image:"test-repo:latest"){ 2655 Results{ 2656 RepoName 2657 Tag IsSigned 2658 Manifests { 2659 Digest 2660 ConfigDigest 2661 LastUpdated 2662 Size 2663 } 2664 Size 2665 } 2666 } 2667 }` 2668 2669 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2670 So(resp, ShouldNotBeNil) 2671 So(strings.Contains(string(resp.Body()), "less-layers"), ShouldBeTrue) 2672 So(strings.Contains(string(resp.Body()), "one-layer"), ShouldBeTrue) 2673 So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst 2674 So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse) 2675 So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse) 2676 So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse) 2677 So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image 2678 So(err, ShouldBeNil) 2679 So(resp.StatusCode(), ShouldEqual, 200) 2680 }) 2681 2682 Convey("paginated query", func() { 2683 query := ` 2684 { 2685 BaseImageList(image:"test-repo:latest", requestedPage:{limit: 1, offset: 0, sortBy:RELEVANCE}){ 2686 Results{ 2687 RepoName 2688 Tag 2689 Manifests { 2690 Digest 2691 ConfigDigest 2692 LastUpdated 2693 Size 2694 } 2695 Size 2696 } 2697 } 2698 }` 2699 2700 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2701 So(resp, ShouldNotBeNil) 2702 So(strings.Contains(string(resp.Body()), "less-layers"), ShouldBeTrue) 2703 So(strings.Contains(string(resp.Body()), "one-layer"), ShouldBeFalse) 2704 So(strings.Contains(string(resp.Body()), "same-layers"), ShouldBeFalse) //nolint:goconst 2705 So(strings.Contains(string(resp.Body()), "less-layers-false"), ShouldBeFalse) 2706 So(strings.Contains(string(resp.Body()), "more-layers"), ShouldBeFalse) 2707 So(strings.Contains(string(resp.Body()), "diff-layers"), ShouldBeFalse) 2708 So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) //nolint:goconst // should not list given image 2709 So(err, ShouldBeNil) 2710 So(resp.StatusCode(), ShouldEqual, 200) 2711 }) 2712 }) 2713 2714 Convey("Nonexistent repository", t, func() { 2715 query := ` 2716 { 2717 BaseImageList(image:"nonexistent-image:latest"){ 2718 Results{ 2719 RepoName 2720 Tag 2721 Manifests { 2722 Digest 2723 ConfigDigest 2724 LastUpdated 2725 Size 2726 } 2727 Size 2728 } 2729 } 2730 }` 2731 2732 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2733 So(string(resp.Body()), ShouldContainSubstring, "repository not found") 2734 So(err, ShouldBeNil) 2735 }) 2736 2737 Convey("Invalid query, no reference provided", t, func() { 2738 query := ` 2739 { 2740 BaseImageList(image:"nonexistent-image"){ 2741 Results{ 2742 RepoName 2743 Tag 2744 Manifests { 2745 Digest 2746 ConfigDigest 2747 LastUpdated 2748 Size 2749 } 2750 Size 2751 } 2752 } 2753 }` 2754 2755 responseStruct := &zcommon.BaseImageListResponse{} 2756 contains := false 2757 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2758 So(err, ShouldBeNil) 2759 2760 err = json.Unmarshal(resp.Body(), responseStruct) 2761 So(err, ShouldBeNil) 2762 for _, err := range responseStruct.Errors { 2763 result := strings.Contains(err.Message, "no reference provided") 2764 if result { 2765 contains = result 2766 } 2767 } 2768 So(contains, ShouldBeTrue) 2769 }) 2770 } 2771 2772 //nolint:dupl 2773 func TestBaseImageListNoRepos(t *testing.T) { 2774 Convey("No repositories found", t, func() { 2775 port := GetFreePort() 2776 baseURL := GetBaseURL(port) 2777 2778 conf := config.New() 2779 conf.HTTP.Port = port 2780 conf.Storage.RootDirectory = t.TempDir() 2781 defaultVal := true 2782 conf.Extensions = &extconf.ExtensionConfig{ 2783 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 2784 } 2785 2786 conf.Extensions.Search.CVE = nil 2787 2788 ctlr := api.NewController(conf) 2789 2790 ctlrManager := NewControllerManager(ctlr) 2791 ctlrManager.StartAndWait(port) 2792 defer ctlrManager.StopServer() 2793 2794 query := ` 2795 { 2796 BaseImageList(image:"test-image"){ 2797 Results{ 2798 RepoName 2799 Tag 2800 Manifests { 2801 Digest 2802 ConfigDigest 2803 LastUpdated 2804 Size 2805 } 2806 IsSigned 2807 Size 2808 } 2809 } 2810 }` 2811 2812 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2813 So(strings.Contains(string(resp.Body()), "no reference provided"), ShouldBeTrue) 2814 So(err, ShouldBeNil) 2815 }) 2816 } 2817 2818 func TestGetRepositories(t *testing.T) { 2819 Convey("Test getting the repositories list", t, func() { 2820 mockImageStore := mocks.MockedImageStore{ 2821 GetRepositoriesFn: func() ([]string, error) { 2822 return []string{}, ErrTestError 2823 }, 2824 } 2825 2826 storeController := storage.StoreController{ 2827 DefaultStore: mockImageStore, 2828 SubStore: map[string]storageTypes.ImageStore{"test": mockImageStore}, 2829 } 2830 olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) 2831 2832 repoList, err := olu.GetRepositories() 2833 So(repoList, ShouldBeEmpty) 2834 So(err, ShouldNotBeNil) 2835 2836 storeController = storage.StoreController{ 2837 DefaultStore: mocks.MockedImageStore{}, 2838 SubStore: map[string]storageTypes.ImageStore{"test": mockImageStore}, 2839 } 2840 olu = ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", "")) 2841 2842 repoList, err = olu.GetRepositories() 2843 So(repoList, ShouldBeEmpty) 2844 So(err, ShouldNotBeNil) 2845 }) 2846 } 2847 2848 func TestGlobalSearchImageAuthor(t *testing.T) { 2849 port := GetFreePort() 2850 baseURL := GetBaseURL(port) 2851 conf := config.New() 2852 conf.HTTP.Port = port 2853 tempDir := t.TempDir() 2854 conf.Storage.RootDirectory = tempDir 2855 2856 defaultVal := true 2857 conf.Extensions = &extconf.ExtensionConfig{ 2858 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 2859 } 2860 2861 conf.Extensions.Search.CVE = nil 2862 2863 ctlr := api.NewController(conf) 2864 ctlrManager := NewControllerManager(ctlr) 2865 2866 ctlrManager.StartAndWait(port) 2867 defer ctlrManager.StopServer() 2868 2869 Convey("Test global search with author in manifest's annotations", t, func() { 2870 annotations := make(map[string]string) 2871 annotations["org.opencontainers.image.authors"] = "author name" 2872 2873 image := CreateImageWith().RandomLayers(1, 10000).DefaultConfig(). 2874 Annotations(annotations).Build() 2875 2876 err := UploadImage(image, baseURL, "repowithauthor", "latest") 2877 So(err, ShouldBeNil) 2878 2879 query := ` 2880 { 2881 GlobalSearch(query:"repowithauthor:latest"){ 2882 Images { 2883 RepoName Tag LastUpdated Size IsSigned 2884 Authors 2885 } 2886 } 2887 }` 2888 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2889 So(resp, ShouldNotBeNil) 2890 So(err, ShouldBeNil) 2891 So(resp.StatusCode(), ShouldEqual, 200) 2892 2893 responseStructImages := &zcommon.GlobalSearchResultResp{} 2894 2895 err = json.Unmarshal(resp.Body(), responseStructImages) 2896 So(err, ShouldBeNil) 2897 2898 So(responseStructImages.Images[0].Authors, ShouldEqual, "author name") 2899 2900 query = ` 2901 { 2902 GlobalSearch(query:"repowithauthor"){ 2903 Repos { 2904 Name LastUpdated Size 2905 Platforms { Os Arch } 2906 Vendors 2907 NewestImage { 2908 RepoName Tag LastUpdated Size IsSigned 2909 Authors 2910 } 2911 } 2912 } 2913 }` 2914 2915 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2916 So(resp, ShouldNotBeNil) 2917 So(err, ShouldBeNil) 2918 So(resp.StatusCode(), ShouldEqual, 200) 2919 2920 responseStructRepos := &zcommon.GlobalSearchResultResp{} 2921 2922 err = json.Unmarshal(resp.Body(), responseStructRepos) 2923 So(err, ShouldBeNil) 2924 2925 So(responseStructRepos.Repos[0].NewestImage.Authors, ShouldEqual, "author name") 2926 }) 2927 2928 Convey("Test global search with author in manifest's config", t, func() { 2929 image := CreateImageWith().RandomLayers(1, 10000).DefaultConfig().Build() 2930 2931 err := UploadImage(image, baseURL, "repowithauthorconfig", "latest") 2932 So(err, ShouldBeNil) 2933 2934 query := ` 2935 { 2936 GlobalSearch(query:"repowithauthorconfig:latest"){ 2937 Images { 2938 RepoName Tag LastUpdated Size IsSigned 2939 Authors 2940 } 2941 } 2942 }` 2943 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2944 So(resp, ShouldNotBeNil) 2945 So(err, ShouldBeNil) 2946 So(resp.StatusCode(), ShouldEqual, 200) 2947 2948 responseStructImages := &zcommon.GlobalSearchResultResp{} 2949 2950 err = json.Unmarshal(resp.Body(), responseStructImages) 2951 So(err, ShouldBeNil) 2952 2953 So(responseStructImages.Images[0].Authors, ShouldEqual, "ZotUser") 2954 2955 query = ` 2956 { 2957 GlobalSearch(query:"repowithauthorconfig"){ 2958 Repos { 2959 Name LastUpdated Size 2960 Platforms { Os Arch } 2961 Vendors 2962 NewestImage { 2963 RepoName Tag LastUpdated Size IsSigned 2964 Authors 2965 } 2966 } 2967 } 2968 }` 2969 2970 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 2971 So(resp, ShouldNotBeNil) 2972 So(err, ShouldBeNil) 2973 So(resp.StatusCode(), ShouldEqual, 200) 2974 2975 responseStructRepos := &zcommon.GlobalSearchResultResp{} 2976 2977 err = json.Unmarshal(resp.Body(), responseStructRepos) 2978 So(err, ShouldBeNil) 2979 2980 So(responseStructRepos.Repos[0].NewestImage.Authors, ShouldEqual, "ZotUser") 2981 }) 2982 } 2983 2984 func TestGlobalSearch(t *testing.T) { 2985 Convey("Test searching for repos with vulnerabitity scanning disabled", t, func() { 2986 subpath := "/a" 2987 2988 dir := t.TempDir() 2989 subDir := t.TempDir() 2990 2991 subRootDir := path.Join(subDir, subpath) 2992 2993 port := GetFreePort() 2994 baseURL := GetBaseURL(port) 2995 conf := config.New() 2996 conf.HTTP.Port = port 2997 conf.Storage.RootDirectory = dir 2998 conf.Storage.SubPaths = make(map[string]config.StorageConfig) 2999 conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir} 3000 defaultVal := true 3001 conf.Extensions = &extconf.ExtensionConfig{ 3002 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 3003 } 3004 3005 conf.Extensions.Search.CVE = nil 3006 3007 ctlr := api.NewController(conf) 3008 3009 ctlrManager := NewControllerManager(ctlr) 3010 ctlrManager.StartAndWait(port) 3011 defer ctlrManager.StopServer() 3012 3013 // push test images to repo 1 image 1 3014 createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC) 3015 createdTimeL2 := time.Date(2010, 2, 1, 12, 0, 0, 0, time.UTC) 3016 config1 := ispec.Image{ 3017 Created: &createdTimeL2, 3018 Platform: ispec.Platform{ 3019 Architecture: "amd64", 3020 OS: "linux", 3021 }, 3022 RootFS: ispec.RootFS{ 3023 Type: "layers", 3024 DiffIDs: []godigest.Digest{}, 3025 }, 3026 Author: "ZotUser", 3027 History: []ispec.History{ 3028 { 3029 Created: &createdTime, 3030 CreatedBy: "go test data", 3031 Author: "ZotUser", 3032 Comment: "Test history comment", 3033 EmptyLayer: true, 3034 }, 3035 { 3036 Created: &createdTimeL2, 3037 CreatedBy: "go test data 2", 3038 Author: "ZotUser", 3039 Comment: "Test history comment2", 3040 EmptyLayer: false, 3041 }, 3042 }, 3043 } 3044 3045 image1 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 100)}). 3046 ImageConfig(config1).Build() 3047 3048 layersSize1 := 0 3049 for _, l := range image1.Layers { 3050 layersSize1 += len(l) 3051 } 3052 3053 err := UploadImage(image1, baseURL, "repo1", "1.0.1") 3054 So(err, ShouldBeNil) 3055 3056 // push test images to repo 1 image 2 3057 createdTime2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC) 3058 createdTimeL2 = time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC) 3059 3060 config2 := ispec.Image{ 3061 Created: &createdTimeL2, 3062 Platform: ispec.Platform{ 3063 Architecture: "amd64", 3064 OS: "linux", 3065 }, 3066 RootFS: ispec.RootFS{ 3067 Type: "layers", 3068 DiffIDs: []godigest.Digest{}, 3069 }, 3070 Author: "ZotUser", 3071 History: []ispec.History{ 3072 { 3073 Created: &createdTime2, 3074 CreatedBy: "go test data", 3075 Author: "ZotUser", 3076 Comment: "Test history comment", 3077 EmptyLayer: true, 3078 }, 3079 { 3080 Created: &createdTimeL2, 3081 CreatedBy: "go test data 2", 3082 Author: "ZotUser", 3083 Comment: "Test history comment2", 3084 EmptyLayer: false, 3085 }, 3086 }, 3087 } 3088 3089 image2 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 200)}). 3090 ImageConfig(config2).Build() 3091 3092 layersSize2 := 0 3093 for _, l := range image2.Layers { 3094 layersSize2 += len(l) 3095 } 3096 3097 err = UploadImage(image2, baseURL, "repo1", "1.0.2") 3098 So(err, ShouldBeNil) 3099 3100 // push test images to repo 2 image 1 3101 createdTime3 := time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC) 3102 config3 := ispec.Image{ 3103 Created: &createdTime3, 3104 Platform: ispec.Platform{ 3105 Architecture: "amd64", 3106 OS: "linux", 3107 }, 3108 RootFS: ispec.RootFS{ 3109 Type: "layers", 3110 DiffIDs: []godigest.Digest{}, 3111 }, 3112 Author: "ZotUser", 3113 History: []ispec.History{{Created: &createdTime3}}, 3114 } 3115 3116 image3 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 300)}). 3117 ImageConfig(config3).Build() 3118 3119 layersSize3 := 0 3120 for _, l := range image3.Layers { 3121 layersSize3 += len(l) 3122 } 3123 3124 err = UploadImage(image3, baseURL, "repo2", "1.0.0") 3125 So(err, ShouldBeNil) 3126 3127 olu := ociutils.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) 3128 3129 // Initialize the objects containing the expected data 3130 repos, err := olu.GetRepositories() 3131 So(err, ShouldBeNil) 3132 3133 allExpectedRepoInfoMap := make(map[string]zcommon.RepoInfo) 3134 allExpectedImageSummaryMap := make(map[string]zcommon.ImageSummary) 3135 for _, repo := range repos { 3136 repoInfo, err := olu.GetExpandedRepoInfo(repo) 3137 So(err, ShouldBeNil) 3138 allExpectedRepoInfoMap[repo] = repoInfo 3139 for _, image := range repoInfo.ImageSummaries { 3140 imageName := fmt.Sprintf("%s:%s", repo, image.Tag) 3141 allExpectedImageSummaryMap[imageName] = image 3142 } 3143 } 3144 3145 query := ` 3146 { 3147 GlobalSearch(query:"repo"){ 3148 Images { 3149 RepoName Tag LastUpdated Size IsSigned 3150 Manifests { 3151 LastUpdated 3152 Size 3153 Platform { Os Arch } 3154 History { 3155 Layer { Size Digest } 3156 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3157 } 3158 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3159 } 3160 Vendor 3161 Vulnerabilities { Count MaxSeverity } 3162 } 3163 Repos { 3164 Name LastUpdated Size 3165 Platforms { Os Arch } 3166 Vendors 3167 NewestImage { 3168 RepoName Tag LastUpdated Size 3169 Digest 3170 Manifests{ 3171 Digest ConfigDigest 3172 LastUpdated Size 3173 Platform { Os Arch } 3174 History { 3175 Layer { Size Digest } 3176 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3177 } 3178 } 3179 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3180 } 3181 } 3182 Layers { Digest Size } 3183 } 3184 }` 3185 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 3186 So(resp, ShouldNotBeNil) 3187 So(err, ShouldBeNil) 3188 So(resp.StatusCode(), ShouldEqual, 200) 3189 3190 responseStruct := &zcommon.GlobalSearchResultResp{} 3191 3192 err = json.Unmarshal(resp.Body(), responseStruct) 3193 So(err, ShouldBeNil) 3194 3195 // Make sure the repo/image counts match before comparing actual content 3196 So(responseStruct.Images, ShouldNotBeNil) 3197 t.Logf("returned images: %v", responseStruct.Images) 3198 So(responseStruct.Images, ShouldBeEmpty) 3199 t.Logf("returned repos: %v", responseStruct.Repos) 3200 So(len(responseStruct.Repos), ShouldEqual, 2) 3201 t.Logf("returned layers: %v", responseStruct.GlobalSearch.Layers) 3202 So(responseStruct.Layers, ShouldBeEmpty) 3203 3204 newestImageMap := make(map[string]zcommon.ImageSummary) 3205 actualRepoMap := make(map[string]zcommon.RepoSummary) 3206 for _, repo := range responseStruct.Repos { 3207 newestImageMap[repo.Name] = repo.NewestImage 3208 actualRepoMap[repo.Name] = repo 3209 } 3210 3211 // Tag 1.0.2 has a history entry which is older compare to 1.0.1 3212 So(newestImageMap["repo1"].Tag, ShouldEqual, "1.0.1") 3213 So(newestImageMap["repo1"].LastUpdated, ShouldEqual, time.Date(2010, 2, 1, 12, 0, 0, 0, time.UTC)) 3214 3215 So(newestImageMap["repo2"].Tag, ShouldEqual, "1.0.0") 3216 So(newestImageMap["repo2"].LastUpdated, ShouldEqual, time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC)) 3217 3218 for repoName, repoSummary := range actualRepoMap { 3219 repoSummary := repoSummary 3220 3221 // Check if data in NewestImage is consistent with the data in RepoSummary 3222 So(repoName, ShouldEqual, repoSummary.NewestImage.RepoName) 3223 So(repoSummary.Name, ShouldEqual, repoSummary.NewestImage.RepoName) 3224 So(repoSummary.LastUpdated, ShouldEqual, repoSummary.NewestImage.LastUpdated) 3225 3226 // The data in the RepoSummary returned from the request matches the data returned from the disk 3227 repoInfo := allExpectedRepoInfoMap[repoName] 3228 3229 t.Logf("Validate repo summary returned by global search with vulnerability scanning disabled") 3230 verifyRepoSummaryFields(t, &repoSummary, &repoInfo.Summary) 3231 3232 // RepoInfo object does not provide vulnerability information so we need to check differently 3233 // No vulnerabilities should be detected since trivy is disabled 3234 t.Logf("Found vulnerability summary %v", repoSummary.NewestImage.Vulnerabilities) 3235 So(repoSummary.NewestImage.Vulnerabilities.Count, ShouldEqual, 0) 3236 So(repoSummary.NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0) 3237 So(repoSummary.NewestImage.Vulnerabilities.LowCount, ShouldEqual, 0) 3238 So(repoSummary.NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 0) 3239 So(repoSummary.NewestImage.Vulnerabilities.HighCount, ShouldEqual, 0) 3240 So(repoSummary.NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 0) 3241 So(repoSummary.NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "") 3242 } 3243 3244 query = ` 3245 { 3246 GlobalSearch(query:"repo1:1.0.1"){ 3247 Images { 3248 RepoName Tag LastUpdated Size 3249 Manifests { 3250 LastUpdated Size 3251 Platform { Os Arch } 3252 History { 3253 Layer { Size Digest } 3254 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3255 } 3256 } 3257 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3258 } 3259 Repos { 3260 Name LastUpdated Size 3261 Platforms { Os Arch } 3262 Vendors 3263 NewestImage { 3264 RepoName Tag LastUpdated Size 3265 Manifests { 3266 LastUpdated Size 3267 Platform { Os Arch } 3268 History { 3269 Layer { Size Digest } 3270 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3271 } 3272 } 3273 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3274 } 3275 } 3276 Layers { Digest Size } 3277 } 3278 }` 3279 3280 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 3281 So(resp, ShouldNotBeNil) 3282 So(err, ShouldBeNil) 3283 So(resp.StatusCode(), ShouldEqual, 200) 3284 3285 responseStruct = &zcommon.GlobalSearchResultResp{} 3286 3287 err = json.Unmarshal(resp.Body(), responseStruct) 3288 So(err, ShouldBeNil) 3289 3290 So(responseStruct.Images, ShouldNotBeEmpty) 3291 So(responseStruct.Repos, ShouldBeEmpty) 3292 So(responseStruct.Layers, ShouldBeEmpty) 3293 3294 So(len(responseStruct.Images), ShouldEqual, 1) 3295 actualImageSummary := responseStruct.Images[0] 3296 So(actualImageSummary.Tag, ShouldEqual, "1.0.1") 3297 3298 expectedImageSummary, ok := allExpectedImageSummaryMap["repo1:1.0.1"] 3299 So(ok, ShouldEqual, true) 3300 3301 t.Logf("Validate image summary returned by global search with vulnerability scanning disabled") 3302 verifyImageSummaryFields(t, &actualImageSummary, &expectedImageSummary) 3303 3304 // RepoInfo object does not provide vulnerability information so we need to check differently 3305 // 0 vulnerabilities should be detected since trivy is disabled 3306 t.Logf("Found vulnerability summary %v", actualImageSummary.Vulnerabilities) 3307 So(actualImageSummary.Vulnerabilities.Count, ShouldEqual, 0) 3308 So(actualImageSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0) 3309 So(actualImageSummary.Vulnerabilities.LowCount, ShouldEqual, 0) 3310 So(actualImageSummary.Vulnerabilities.MediumCount, ShouldEqual, 0) 3311 So(actualImageSummary.Vulnerabilities.HighCount, ShouldEqual, 0) 3312 So(actualImageSummary.Vulnerabilities.CriticalCount, ShouldEqual, 0) 3313 So(actualImageSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "") 3314 }) 3315 3316 Convey("Test global search with real images and vulnerabitity scanning enabled", t, func() { 3317 subpath := "/a" 3318 3319 dir := t.TempDir() 3320 subDir := t.TempDir() 3321 3322 subRootDir := path.Join(subDir, subpath) 3323 3324 port := GetFreePort() 3325 baseURL := GetBaseURL(port) 3326 conf := config.New() 3327 conf.HTTP.Port = port 3328 conf.Storage.RootDirectory = dir 3329 conf.Storage.SubPaths = make(map[string]config.StorageConfig) 3330 conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir} 3331 defaultVal := true 3332 3333 updateDuration, _ := time.ParseDuration("1h") 3334 trivyConfig := &extconf.TrivyConfig{ 3335 DBRepository: "ghcr.io/project-zot/trivy-db", 3336 } 3337 cveConfig := &extconf.CVEConfig{ 3338 UpdateInterval: updateDuration, 3339 Trivy: trivyConfig, 3340 } 3341 searchConfig := &extconf.SearchConfig{ 3342 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 3343 CVE: cveConfig, 3344 } 3345 conf.Extensions = &extconf.ExtensionConfig{ 3346 Search: searchConfig, 3347 } 3348 3349 // we won't use the logging config feature as we want logs in both 3350 // stdout and a file 3351 logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") 3352 So(err, ShouldBeNil) 3353 logPath := logFile.Name() 3354 defer os.Remove(logPath) 3355 3356 writers := io.MultiWriter(os.Stdout, logFile) 3357 3358 ctlr := api.NewController(conf) 3359 ctlr.Log.Logger = ctlr.Log.Output(writers) 3360 3361 if err := ctlr.Init(); err != nil { 3362 panic(err) 3363 } 3364 3365 ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) 3366 3367 go func() { 3368 if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) { 3369 panic(err) 3370 } 3371 }() 3372 3373 defer ctlr.Shutdown() 3374 3375 // Wait for trivy db to download 3376 substring := "{\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000," + 3377 "\"Trivy\":{\"DBRepository\":\"ghcr.io/project-zot/trivy-db\",\"JavaDBRepository\":\"\"}}}" 3378 found, err := readFileAndSearchString(logPath, substring, 2*time.Minute) 3379 So(found, ShouldBeTrue) 3380 So(err, ShouldBeNil) 3381 3382 found, err = readFileAndSearchString(logPath, "updating cve-db", 2*time.Minute) 3383 So(found, ShouldBeTrue) 3384 So(err, ShouldBeNil) 3385 3386 found, err = readFileAndSearchString(logPath, "cve-db update completed, next update scheduled after interval", 3387 4*time.Minute) 3388 So(found, ShouldBeTrue) 3389 So(err, ShouldBeNil) 3390 3391 WaitTillServerReady(baseURL) 3392 3393 // push test images to repo 1 image 1 3394 createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC) 3395 config1 := ispec.Image{ 3396 Created: &createdTime, 3397 Platform: ispec.Platform{ 3398 Architecture: "amd64", 3399 OS: "linux", 3400 }, 3401 RootFS: ispec.RootFS{ 3402 Type: "layers", 3403 DiffIDs: []godigest.Digest{}, 3404 }, 3405 Author: "ZotUser", 3406 History: []ispec.History{{Created: &createdTime}}, 3407 } 3408 3409 image1 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 100)}). 3410 ImageConfig(config1).Build() 3411 3412 layersSize1 := 0 3413 for _, l := range image1.Layers { 3414 layersSize1 += len(l) 3415 } 3416 3417 err = UploadImage(image1, baseURL, "repo1", "1.0.1") 3418 So(err, ShouldBeNil) 3419 3420 // push test images to repo 1 image 2 3421 createdTime2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC) 3422 config2 := ispec.Image{ 3423 Platform: ispec.Platform{ 3424 Architecture: "amd64", 3425 OS: "linux", 3426 }, 3427 RootFS: ispec.RootFS{ 3428 Type: "layers", 3429 DiffIDs: []godigest.Digest{}, 3430 }, 3431 Author: "ZotUser", 3432 History: []ispec.History{{Created: &createdTime2}}, 3433 } 3434 3435 image2 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 200)}). 3436 ImageConfig(config2).Build() 3437 3438 layersSize2 := 0 3439 for _, l := range image2.Layers { 3440 layersSize2 += len(l) 3441 } 3442 3443 err = UploadImage(image2, baseURL, "repo1", "1.0.2") 3444 So(err, ShouldBeNil) 3445 3446 // push test images to repo 2 image 1 3447 createdTime3 := time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC) 3448 config3 := ispec.Image{ 3449 Platform: ispec.Platform{ 3450 Architecture: "amd64", 3451 OS: "linux", 3452 }, 3453 RootFS: ispec.RootFS{ 3454 Type: "layers", 3455 DiffIDs: []godigest.Digest{}, 3456 }, 3457 Author: "ZotUser", 3458 History: []ispec.History{{Created: &createdTime3}}, 3459 } 3460 3461 image3 := CreateImageWith().LayerBlobs([][]byte{make([]byte, 300)}). 3462 ImageConfig(config3).Build() 3463 3464 layersSize3 := 0 3465 for _, l := range image3.Layers { 3466 layersSize3 += len(l) 3467 } 3468 3469 err = UploadImage(image3, baseURL, "repo2", "1.0.0") 3470 So(err, ShouldBeNil) 3471 3472 olu := ociutils.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", "")) 3473 3474 // Initialize the objects containing the expected data 3475 repos, err := olu.GetRepositories() 3476 So(err, ShouldBeNil) 3477 3478 allExpectedRepoInfoMap := make(map[string]zcommon.RepoInfo) 3479 allExpectedImageSummaryMap := make(map[string]zcommon.ImageSummary) 3480 for _, repo := range repos { 3481 repoInfo, err := olu.GetExpandedRepoInfo(repo) 3482 So(err, ShouldBeNil) 3483 allExpectedRepoInfoMap[repo] = repoInfo 3484 for _, image := range repoInfo.ImageSummaries { 3485 imageName := fmt.Sprintf("%s:%s", repo, image.Tag) 3486 allExpectedImageSummaryMap[imageName] = image 3487 } 3488 } 3489 3490 query := ` 3491 { 3492 GlobalSearch(query:"repo"){ 3493 Images { 3494 RepoName Tag LastUpdated Size 3495 Manifests { 3496 LastUpdated Size 3497 Platform { Os Arch } 3498 History { 3499 Layer { Size Digest } 3500 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3501 } 3502 } 3503 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3504 } 3505 Repos { 3506 Name LastUpdated Size 3507 Platforms { Os Arch } 3508 Vendors 3509 NewestImage { 3510 RepoName Tag LastUpdated Size 3511 Manifests { 3512 LastUpdated Size 3513 Platform { Os Arch } 3514 History { 3515 Layer { Size Digest } 3516 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3517 } 3518 } 3519 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3520 } 3521 } 3522 Layers { Digest Size } 3523 } 3524 }` 3525 3526 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 3527 So(resp, ShouldNotBeNil) 3528 So(err, ShouldBeNil) 3529 So(resp.StatusCode(), ShouldEqual, 200) 3530 3531 responseStruct := &zcommon.GlobalSearchResultResp{} 3532 3533 err = json.Unmarshal(resp.Body(), responseStruct) 3534 So(err, ShouldBeNil) 3535 3536 // Make sure the repo/image counts match before comparing actual content 3537 So(responseStruct.Images, ShouldNotBeNil) 3538 t.Logf("returned images: %v", responseStruct.Images) 3539 So(responseStruct.Images, ShouldBeEmpty) 3540 t.Logf("returned repos: %v", responseStruct.Repos) 3541 So(len(responseStruct.Repos), ShouldEqual, 2) 3542 t.Logf("returned layers: %v", responseStruct.Layers) 3543 So(responseStruct.Layers, ShouldBeEmpty) 3544 3545 newestImageMap := make(map[string]zcommon.ImageSummary) 3546 actualRepoMap := make(map[string]zcommon.RepoSummary) 3547 for _, repo := range responseStruct.Repos { 3548 newestImageMap[repo.Name] = repo.NewestImage 3549 actualRepoMap[repo.Name] = repo 3550 } 3551 3552 // Tag 1.0.2 has a history entry which is older compare to 1.0.1 3553 So(newestImageMap["repo1"].Tag, ShouldEqual, "1.0.1") 3554 So(newestImageMap["repo1"].LastUpdated, ShouldEqual, time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)) 3555 3556 So(newestImageMap["repo2"].Tag, ShouldEqual, "1.0.0") 3557 So(newestImageMap["repo2"].LastUpdated, ShouldEqual, time.Date(2009, 2, 1, 12, 0, 0, 0, time.UTC)) 3558 3559 for repoName, repoSummary := range actualRepoMap { 3560 repoSummary := repoSummary 3561 3562 // Check if data in NewestImage is consistent with the data in RepoSummary 3563 So(repoName, ShouldEqual, repoSummary.NewestImage.RepoName) 3564 So(repoSummary.Name, ShouldEqual, repoSummary.NewestImage.RepoName) 3565 So(repoSummary.LastUpdated, ShouldEqual, repoSummary.NewestImage.LastUpdated) 3566 3567 // The data in the RepoSummary returned from the request matches the data returned from the disk 3568 repoInfo := allExpectedRepoInfoMap[repoName] 3569 3570 t.Logf("Validate repo summary returned by global search with vulnerability scanning enabled") 3571 verifyRepoSummaryFields(t, &repoSummary, &repoInfo.Summary) 3572 3573 // RepoInfo object does not provide vulnerability information so we need to check differently 3574 t.Logf("Found vulnerability summary %v", repoSummary.NewestImage.Vulnerabilities) 3575 if repoName == "repo1" { //nolint:goconst 3576 So(repoSummary.NewestImage.Vulnerabilities.Count, ShouldEqual, 4) 3577 // There are 4 vulnerabilities in the data used in tests 3578 So(repoSummary.NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0) 3579 So(repoSummary.NewestImage.Vulnerabilities.LowCount, ShouldEqual, 1) 3580 So(repoSummary.NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 1) 3581 So(repoSummary.NewestImage.Vulnerabilities.HighCount, ShouldEqual, 1) 3582 So(repoSummary.NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 1) 3583 So(repoSummary.NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL") 3584 } else { 3585 So(repoSummary.NewestImage.Vulnerabilities.Count, ShouldEqual, 0) 3586 // There are 0 vulnerabilities this data used in tests 3587 So(repoSummary.NewestImage.Vulnerabilities.UnknownCount, ShouldEqual, 0) 3588 So(repoSummary.NewestImage.Vulnerabilities.LowCount, ShouldEqual, 0) 3589 So(repoSummary.NewestImage.Vulnerabilities.MediumCount, ShouldEqual, 0) 3590 So(repoSummary.NewestImage.Vulnerabilities.HighCount, ShouldEqual, 0) 3591 So(repoSummary.NewestImage.Vulnerabilities.CriticalCount, ShouldEqual, 0) 3592 So(repoSummary.NewestImage.Vulnerabilities.MaxSeverity, ShouldEqual, "NONE") 3593 } 3594 } 3595 3596 query = ` 3597 { 3598 GlobalSearch(query:"repo1:1.0.1"){ 3599 Images { 3600 RepoName Tag LastUpdated Size 3601 Manifests { 3602 LastUpdated Size 3603 Platform { Os Arch } 3604 History { 3605 Layer { Size Digest } 3606 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3607 } 3608 } 3609 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3610 } 3611 Repos { 3612 Name LastUpdated Size 3613 Platforms { Os Arch } 3614 Vendors 3615 NewestImage { 3616 RepoName Tag LastUpdated Size 3617 Manifests { 3618 LastUpdated Size 3619 Platform { Os Arch } 3620 History { 3621 Layer { Size Digest } 3622 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 3623 } 3624 } 3625 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 3626 } 3627 } 3628 Layers { Digest Size } 3629 } 3630 }` 3631 3632 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 3633 So(resp, ShouldNotBeNil) 3634 So(err, ShouldBeNil) 3635 So(resp.StatusCode(), ShouldEqual, 200) 3636 3637 responseStruct = &zcommon.GlobalSearchResultResp{} 3638 3639 err = json.Unmarshal(resp.Body(), responseStruct) 3640 So(err, ShouldBeNil) 3641 3642 So(responseStruct.Images, ShouldNotBeEmpty) 3643 So(responseStruct.Repos, ShouldBeEmpty) 3644 So(responseStruct.Layers, ShouldBeEmpty) 3645 3646 So(len(responseStruct.Images), ShouldEqual, 1) 3647 actualImageSummary := responseStruct.Images[0] 3648 So(actualImageSummary.Tag, ShouldEqual, "1.0.1") 3649 3650 expectedImageSummary, ok := allExpectedImageSummaryMap["repo1:1.0.1"] 3651 So(ok, ShouldEqual, true) 3652 3653 t.Logf("Validate image summary returned by global search with vulnerability scanning enable") 3654 verifyImageSummaryFields(t, &actualImageSummary, &expectedImageSummary) 3655 3656 // RepoInfo object does not provide vulnerability information so we need to check differently 3657 t.Logf("Found vulnerability summary %v", actualImageSummary.Vulnerabilities) 3658 // There are 4 vulnerabilities in the data used in tests 3659 So(actualImageSummary.Vulnerabilities.Count, ShouldEqual, 4) 3660 So(actualImageSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0) 3661 So(actualImageSummary.Vulnerabilities.LowCount, ShouldEqual, 1) 3662 So(actualImageSummary.Vulnerabilities.MediumCount, ShouldEqual, 1) 3663 So(actualImageSummary.Vulnerabilities.HighCount, ShouldEqual, 1) 3664 So(actualImageSummary.Vulnerabilities.CriticalCount, ShouldEqual, 1) 3665 So(actualImageSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL") 3666 }) 3667 3668 Convey("global searching by digest", t, func() { 3669 log := log.NewLogger("debug", "") 3670 rootDir := t.TempDir() 3671 port := GetFreePort() 3672 baseURL := GetBaseURL(port) 3673 conf := config.New() 3674 conf.HTTP.Port = port 3675 conf.Storage.RootDirectory = rootDir 3676 defaultVal := true 3677 conf.Extensions = &extconf.ExtensionConfig{ 3678 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 3679 } 3680 conf.Extensions.Search.CVE = nil 3681 3682 ctlr := api.NewController(conf) 3683 ctlrManager := NewControllerManager(ctlr) 3684 3685 storeCtlr := ociutils.GetDefaultStoreController(rootDir, log) 3686 3687 image1 := CreateRandomImage() 3688 image2 := CreateRandomImage() 3689 multiArch := CreateRandomMultiarch() 3690 3691 err := WriteImageToFileSystem(image1, "repo1", "tag1", storeCtlr) 3692 So(err, ShouldBeNil) 3693 err = WriteImageToFileSystem(image2, "repo1", "tag2", storeCtlr) 3694 So(err, ShouldBeNil) 3695 err = WriteMultiArchImageToFileSystem(multiArch, "repo1", "tag-multi", storeCtlr) 3696 So(err, ShouldBeNil) 3697 3698 err = WriteImageToFileSystem(image2, "repo2", "tag2", storeCtlr) 3699 So(err, ShouldBeNil) 3700 3701 ctlrManager.StartAndWait(port) 3702 defer ctlrManager.StopServer() 3703 3704 // simple image 3705 results := GlobalSearchGQL(image1.DigestStr(), baseURL).GlobalSearch 3706 So(len(results.Images), ShouldEqual, 1) 3707 So(results.Images[0].Digest, ShouldResemble, image1.DigestStr()) 3708 So(results.Images[0].RepoName, ShouldResemble, "repo1") 3709 3710 results = GlobalSearchGQL(image2.DigestStr(), baseURL).GlobalSearch 3711 So(len(results.Images), ShouldEqual, 2) 3712 3713 repos := AccumulateField(results.Images, 3714 func(is zcommon.ImageSummary) string { return is.RepoName }) 3715 So(repos, ShouldContain, "repo1") 3716 So(repos, ShouldContain, "repo2") 3717 3718 // multiarch 3719 results = GlobalSearchGQL(multiArch.DigestStr(), baseURL).GlobalSearch 3720 So(len(results.Images), ShouldEqual, 1) 3721 So(results.Images[0].Digest, ShouldResemble, multiArch.DigestStr()) 3722 So(len(results.Images[0].Manifests), ShouldEqual, len(multiArch.Images)) 3723 So(results.Images[0].RepoName, ShouldResemble, "repo1") 3724 3725 results = GlobalSearchGQL(multiArch.Images[0].DigestStr(), baseURL).GlobalSearch 3726 So(len(results.Images), ShouldEqual, 1) 3727 So(results.Images[0].Digest, ShouldResemble, multiArch.DigestStr()) 3728 So(len(results.Images[0].Manifests), ShouldEqual, 1) 3729 So(results.Images[0].Manifests[0].Digest, ShouldResemble, multiArch.Images[0].DigestStr()) 3730 So(results.Images[0].RepoName, ShouldResemble, "repo1") 3731 }) 3732 3733 Convey("global searching by tag cross repo", t, func() { 3734 log := log.NewLogger("debug", "") 3735 rootDir := t.TempDir() 3736 port := GetFreePort() 3737 baseURL := GetBaseURL(port) 3738 conf := config.New() 3739 conf.HTTP.Port = port 3740 conf.Storage.RootDirectory = rootDir 3741 defaultVal := true 3742 conf.Extensions = &extconf.ExtensionConfig{ 3743 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 3744 } 3745 conf.Extensions.Search.CVE = nil 3746 3747 ctlr := api.NewController(conf) 3748 ctlrManager := NewControllerManager(ctlr) 3749 3750 storeCtlr := ociutils.GetDefaultStoreController(rootDir, log) 3751 3752 image11 := CreateRandomImage() 3753 image12 := CreateRandomImage() 3754 err := WriteImageToFileSystem(image11, "repo1", "tag1", storeCtlr) 3755 So(err, ShouldBeNil) 3756 err = WriteImageToFileSystem(image12, "repo1", "tag2", storeCtlr) 3757 So(err, ShouldBeNil) 3758 3759 image21 := CreateRandomImage() 3760 image22 := CreateRandomImage() 3761 multiArch2 := CreateRandomMultiarch() 3762 err = WriteImageToFileSystem(image21, "repo2", "tag1", storeCtlr) 3763 So(err, ShouldBeNil) 3764 err = WriteImageToFileSystem(image22, "repo2", "tag2", storeCtlr) 3765 So(err, ShouldBeNil) 3766 err = WriteMultiArchImageToFileSystem(multiArch2, "repo2", "tag-multi", storeCtlr) 3767 So(err, ShouldBeNil) 3768 3769 image31 := CreateRandomImage() 3770 image32 := CreateRandomImage() 3771 err = WriteImageToFileSystem(image31, "repo3", "tag1", storeCtlr) 3772 So(err, ShouldBeNil) 3773 err = WriteImageToFileSystem(image32, "repo3", "tag2", storeCtlr) 3774 So(err, ShouldBeNil) 3775 3776 image41 := CreateRandomImage() 3777 image42 := CreateRandomImage() 3778 multiArch4 := CreateRandomMultiarch() 3779 err = WriteImageToFileSystem(image41, "repo4", "tag1", storeCtlr) 3780 So(err, ShouldBeNil) 3781 err = WriteImageToFileSystem(image42, "repo4", "tag2", storeCtlr) 3782 So(err, ShouldBeNil) 3783 err = WriteMultiArchImageToFileSystem(multiArch4, "repo4", "tag-multi", storeCtlr) 3784 So(err, ShouldBeNil) 3785 3786 image51 := CreateRandomImage() 3787 err = WriteImageToFileSystem(image51, "repo5", "tag1", storeCtlr) 3788 So(err, ShouldBeNil) 3789 3790 multiArch62 := CreateRandomMultiarch() 3791 err = WriteMultiArchImageToFileSystem(multiArch62, "repo6", "tag2", storeCtlr) 3792 So(err, ShouldBeNil) 3793 3794 ctlrManager.StartAndWait(port) 3795 defer ctlrManager.StopServer() 3796 3797 // Search for a specific tag cross-repo and return single arch images 3798 results := GlobalSearchGQL(":tag1", baseURL).GlobalSearch 3799 So(len(results.Images), ShouldEqual, 5) 3800 So(len(results.Repos), ShouldEqual, 0) 3801 3802 expectedRepos := []string{"repo1", "repo2", "repo3", "repo4", "repo5"} 3803 for _, image := range results.Images { 3804 So(image.Tag, ShouldEqual, "tag1") 3805 So(image.RepoName, ShouldBeIn, expectedRepos) 3806 So(len(image.Manifests), ShouldEqual, 1) 3807 } 3808 3809 // Search for a specific tag cross-repo and return multi arch images 3810 results = GlobalSearchGQL(":tag-multi", baseURL).GlobalSearch 3811 So(len(results.Images), ShouldEqual, 2) 3812 So(len(results.Repos), ShouldEqual, 0) 3813 3814 expectedRepos = []string{"repo2", "repo4"} 3815 for _, image := range results.Images { 3816 So(image.Tag, ShouldEqual, "tag-multi") 3817 So(image.RepoName, ShouldBeIn, expectedRepos) 3818 So(len(image.Manifests), ShouldEqual, 3) 3819 } 3820 3821 // Search for a specific tag cross-repo and return mixed single and multiarch images 3822 results = GlobalSearchGQL(":tag2", baseURL).GlobalSearch 3823 So(len(results.Images), ShouldEqual, 5) 3824 So(len(results.Repos), ShouldEqual, 0) 3825 3826 expectedRepos = []string{"repo1", "repo2", "repo3", "repo4", "repo6"} 3827 for _, image := range results.Images { 3828 So(image.Tag, ShouldEqual, "tag2") 3829 So(image.RepoName, ShouldBeIn, expectedRepos) 3830 if image.RepoName == "repo6" { 3831 So(len(image.Manifests), ShouldEqual, 3) 3832 } else { 3833 So(len(image.Manifests), ShouldEqual, 1) 3834 } 3835 } 3836 3837 // Search for multiple tags using a partial match cross-repo and return multiarch images 3838 results = GlobalSearchGQL(":multi", baseURL).GlobalSearch 3839 So(len(results.Images), ShouldEqual, 2) 3840 So(len(results.Repos), ShouldEqual, 0) 3841 3842 expectedRepos = []string{"repo2", "repo4"} 3843 for _, image := range results.Images { 3844 So(image.Tag, ShouldContainSubstring, "multi") 3845 So(image.RepoName, ShouldBeIn, expectedRepos) 3846 So(len(image.Manifests), ShouldEqual, 3) 3847 } 3848 3849 // Search for multiple tags using a partial match cross-repo and return mixed single and multiarch images 3850 results = GlobalSearchGQL(":tag", baseURL).GlobalSearch 3851 So(len(results.Images), ShouldEqual, 12) 3852 So(len(results.Repos), ShouldEqual, 0) 3853 3854 expectedRepos = []string{"repo1", "repo2", "repo3", "repo4", "repo5", "repo6"} 3855 for _, image := range results.Images { 3856 So(image.Tag, ShouldContainSubstring, "tag") 3857 So(image.RepoName, ShouldBeIn, expectedRepos) 3858 } 3859 3860 // Search for a specific tag cross-repo and return mixt single and multiarch images 3861 result := GlobalSearchGQL(":", baseURL) 3862 errors := result.Errors 3863 So(len(errors), ShouldEqual, 1) 3864 3865 results = result.GlobalSearch 3866 So(len(results.Images), ShouldEqual, 0) 3867 So(len(results.Repos), ShouldEqual, 0) 3868 }) 3869 } 3870 3871 func TestCleaningFilteringParamsGlobalSearch(t *testing.T) { 3872 Convey("Test cleaning filtering parameters for global search", t, func() { 3873 dir := t.TempDir() 3874 3875 port := GetFreePort() 3876 baseURL := GetBaseURL(port) 3877 conf := config.New() 3878 conf.HTTP.Port = port 3879 conf.Storage.RootDirectory = dir 3880 defaultVal := true 3881 conf.Extensions = &extconf.ExtensionConfig{ 3882 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 3883 } 3884 3885 ctlr := api.NewController(conf) 3886 3887 ctlrManager := NewControllerManager(ctlr) 3888 ctlrManager.StartAndWait(port) 3889 defer ctlrManager.StopServer() 3890 3891 image := CreateImageWith().RandomLayers(1, 100). 3892 ImageConfig(ispec.Image{Platform: ispec.Platform{ 3893 OS: "windows", 3894 Architecture: "amd64", 3895 }}).Build() 3896 3897 err := UploadImage(image, baseURL, "repo1", image.DigestStr()) 3898 So(err, ShouldBeNil) 3899 3900 image = CreateImageWith().RandomLayers(1, 100). 3901 ImageConfig(ispec.Image{Platform: ispec.Platform{ 3902 OS: "linux", 3903 Architecture: "amd64", 3904 }}).Build() 3905 3906 err = UploadImage(image, baseURL, "repo2", image.DigestStr()) 3907 So(err, ShouldBeNil) 3908 3909 query := ` 3910 { 3911 GlobalSearch(query:"repo", requestedPage:{limit: 3, offset: 0, sortBy:RELEVANCE}, 3912 filter:{Os:[" linux", "Windows ", " "], Arch:["","aMd64 "]}) { 3913 Repos { 3914 Name 3915 } 3916 } 3917 }` 3918 3919 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 3920 So(resp, ShouldNotBeNil) 3921 So(err, ShouldBeNil) 3922 So(resp.StatusCode(), ShouldEqual, 200) 3923 3924 responseStruct := &zcommon.GlobalSearchResultResp{} 3925 3926 err = json.Unmarshal(resp.Body(), responseStruct) 3927 So(err, ShouldBeNil) 3928 }) 3929 } 3930 3931 func TestGlobalSearchFiltering(t *testing.T) { 3932 Convey("Global search HasToBeSigned filtering", t, func() { 3933 dir := t.TempDir() 3934 port := GetFreePort() 3935 baseURL := GetBaseURL(port) 3936 conf := config.New() 3937 conf.HTTP.Port = port 3938 conf.Storage.RootDirectory = dir 3939 3940 defaultVal := true 3941 conf.Extensions = &extconf.ExtensionConfig{ 3942 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 3943 } 3944 3945 ctlr := api.NewController(conf) 3946 3947 ctlrManager := NewControllerManager(ctlr) 3948 ctlrManager.StartAndWait(port) 3949 defer ctlrManager.StopServer() 3950 3951 image := CreateRandomImage() 3952 3953 err := UploadImage(image, baseURL, "unsigned-repo", "test") 3954 So(err, ShouldBeNil) 3955 3956 image = CreateRandomImage() 3957 3958 err = UploadImage(image, baseURL, "signed-repo", "test") 3959 So(err, ShouldBeNil) 3960 3961 err = signature.SignImageUsingCosign("signed-repo:test", port, false) 3962 So(err, ShouldBeNil) 3963 3964 query := `{ 3965 GlobalSearch(query:"repo", 3966 filter:{HasToBeSigned:true}) { 3967 Repos { 3968 Name 3969 } 3970 } 3971 }` 3972 3973 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 3974 So(resp, ShouldNotBeNil) 3975 So(err, ShouldBeNil) 3976 So(resp.StatusCode(), ShouldEqual, 200) 3977 3978 responseStruct := &zcommon.GlobalSearchResultResp{} 3979 3980 err = json.Unmarshal(resp.Body(), responseStruct) 3981 So(err, ShouldBeNil) 3982 3983 So(responseStruct.Repos, ShouldNotBeEmpty) 3984 So(responseStruct.Repos[0].Name, ShouldResemble, "signed-repo") 3985 }) 3986 } 3987 3988 func TestGlobalSearchWithInvalidInput(t *testing.T) { 3989 Convey("Global search with invalid input", t, func() { 3990 dir := t.TempDir() 3991 3992 port := GetFreePort() 3993 baseURL := GetBaseURL(port) 3994 conf := config.New() 3995 conf.HTTP.Port = port 3996 conf.Storage.RootDirectory = dir 3997 defaultVal := true 3998 conf.Extensions = &extconf.ExtensionConfig{ 3999 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 4000 } 4001 4002 ctlr := api.NewController(conf) 4003 4004 ctlrManager := NewControllerManager(ctlr) 4005 ctlrManager.StartAndWait(port) 4006 defer ctlrManager.StopServer() 4007 4008 longString := RandomString(1000) 4009 4010 query := fmt.Sprintf(` 4011 { 4012 GlobalSearch(query:"%s", requestedPage:{limit: 3, offset: 4, sortBy:RELEVANCE}, 4013 filter:{Os:["linux", "Windows", ""], Arch:["","amd64"]}) { 4014 Repos { 4015 Name 4016 } 4017 } 4018 }`, longString) 4019 4020 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4021 So(resp, ShouldNotBeNil) 4022 So(err, ShouldBeNil) 4023 So(resp.StatusCode(), ShouldEqual, 200) 4024 4025 responseStruct := &zcommon.GlobalSearchResultResp{} 4026 4027 err = json.Unmarshal(resp.Body(), responseStruct) 4028 So(err, ShouldBeNil) 4029 4030 So(responseStruct.Errors, ShouldNotBeEmpty) 4031 4032 query = fmt.Sprintf(` 4033 { 4034 GlobalSearch(query:"repo", requestedPage:{limit: 3, offset: 4, sortBy:RELEVANCE}, 4035 filter:{Os:["%s", "Windows", ""], Arch:["","amd64"]}) { 4036 Repos { 4037 Name 4038 } 4039 } 4040 }`, longString) 4041 4042 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4043 So(resp, ShouldNotBeNil) 4044 So(err, ShouldBeNil) 4045 So(resp.StatusCode(), ShouldEqual, 200) 4046 4047 responseStruct = &zcommon.GlobalSearchResultResp{} 4048 4049 err = json.Unmarshal(resp.Body(), responseStruct) 4050 So(err, ShouldBeNil) 4051 4052 So(responseStruct.Errors, ShouldNotBeEmpty) 4053 4054 query = fmt.Sprintf(` 4055 { 4056 GlobalSearch(query:"repo", requestedPage:{limit: 3, offset: 4, sortBy:RELEVANCE}, 4057 filter:{Os:["", "Windows", ""], Arch:["","%s"]}) { 4058 Repos { 4059 Name 4060 } 4061 } 4062 }`, longString) 4063 4064 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4065 So(resp, ShouldNotBeNil) 4066 So(err, ShouldBeNil) 4067 So(resp.StatusCode(), ShouldEqual, 200) 4068 4069 responseStruct = &zcommon.GlobalSearchResultResp{} 4070 4071 err = json.Unmarshal(resp.Body(), responseStruct) 4072 So(err, ShouldBeNil) 4073 4074 So(responseStruct.Errors, ShouldNotBeEmpty) 4075 }) 4076 } 4077 4078 func TestImageList(t *testing.T) { 4079 Convey("Test ImageList", t, func() { 4080 rootDir := t.TempDir() 4081 4082 port := GetFreePort() 4083 baseURL := GetBaseURL(port) 4084 4085 conf := config.New() 4086 conf.HTTP.Port = port 4087 conf.Storage.RootDirectory = rootDir 4088 defaultVal := true 4089 conf.Extensions = &extconf.ExtensionConfig{ 4090 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 4091 } 4092 4093 conf.Extensions.Search.CVE = nil 4094 4095 ctlr := api.NewController(conf) 4096 4097 ctlrManager := NewControllerManager(ctlr) 4098 ctlrManager.StartAndWait(port) 4099 defer ctlrManager.StopServer() 4100 4101 createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC) 4102 createdTimeL2 := time.Date(2010, 2, 1, 12, 0, 0, 0, time.UTC) 4103 config := ispec.Image{ 4104 Platform: ispec.Platform{ 4105 Architecture: "amd64", 4106 OS: "linux", 4107 }, 4108 RootFS: ispec.RootFS{ 4109 Type: "layers", 4110 DiffIDs: []godigest.Digest{}, 4111 }, 4112 Author: "ZotUser", 4113 History: []ispec.History{ 4114 { 4115 Created: &createdTime, 4116 CreatedBy: "go test data", 4117 Author: "ZotUser", 4118 Comment: "Test history comment", 4119 EmptyLayer: true, 4120 }, 4121 { 4122 Created: &createdTimeL2, 4123 CreatedBy: "go test data 2", 4124 Author: "ZotUser", 4125 Comment: "Test history comment2", 4126 EmptyLayer: false, 4127 }, 4128 }, 4129 } 4130 4131 image := CreateImageWith().RandomLayers(1, 100).ImageConfig(config).Build() 4132 4133 err := UploadImage(image, baseURL, "zot-cve-test", "0.0.1") 4134 So(err, ShouldBeNil) 4135 4136 err = UploadImage(image, baseURL, "a/zot-cve-test", "0.0.1") 4137 So(err, ShouldBeNil) 4138 4139 err = UploadImage(image, baseURL, "zot-test", "0.0.1") 4140 So(err, ShouldBeNil) 4141 4142 err = UploadImage(image, baseURL, "a/zot-test", "0.0.1") 4143 So(err, ShouldBeNil) 4144 4145 imageStore := ctlr.StoreController.DefaultStore 4146 4147 repos, err := imageStore.GetRepositories() 4148 So(err, ShouldBeNil) 4149 4150 tags, err := imageStore.GetImageTags(repos[0]) 4151 So(err, ShouldBeNil) 4152 4153 buf, _, _, err := imageStore.GetImageManifest(repos[0], tags[0]) 4154 So(err, ShouldBeNil) 4155 var imageManifest ispec.Manifest 4156 err = json.Unmarshal(buf, &imageManifest) 4157 So(err, ShouldBeNil) 4158 4159 var imageConfigInfo ispec.Image 4160 imageConfigBuf, err := imageStore.GetBlobContent(repos[0], imageManifest.Config.Digest) 4161 So(err, ShouldBeNil) 4162 err = json.Unmarshal(imageConfigBuf, &imageConfigInfo) 4163 So(err, ShouldBeNil) 4164 4165 Convey("without pagination, valid response", func() { 4166 query := fmt.Sprintf(`{ 4167 ImageList(repo:"%s"){ 4168 Results { 4169 Manifests { 4170 History{ 4171 HistoryDescription{ 4172 Author 4173 Comment 4174 Created 4175 CreatedBy 4176 EmptyLayer 4177 }, 4178 Layer{ 4179 Digest 4180 Size 4181 } 4182 } 4183 } 4184 } 4185 } 4186 }`, repos[0]) 4187 4188 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4189 So(err, ShouldBeNil) 4190 So(resp.StatusCode(), ShouldEqual, 200) 4191 So(resp, ShouldNotBeNil) 4192 4193 var responseStruct zcommon.ImageListResponse 4194 err = json.Unmarshal(resp.Body(), &responseStruct) 4195 So(err, ShouldBeNil) 4196 4197 So(len(responseStruct.Results), ShouldEqual, len(tags)) 4198 So(len(responseStruct.Results[0].Manifests[0].History), ShouldEqual, len(imageConfigInfo.History)) 4199 }) 4200 4201 Convey("Pagination with valid params", func() { 4202 limit := 1 4203 query := fmt.Sprintf(`{ 4204 ImageList(repo:"%s", requestedPage:{limit: %d, offset: 0, sortBy:RELEVANCE}){ 4205 Results{ 4206 Manifests { 4207 History{ 4208 HistoryDescription{ 4209 Author 4210 Comment 4211 Created 4212 CreatedBy 4213 EmptyLayer 4214 }, 4215 Layer{ 4216 Digest 4217 Size 4218 } 4219 } 4220 } 4221 } 4222 } 4223 }`, repos[0], limit) 4224 4225 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4226 So(err, ShouldBeNil) 4227 So(resp.StatusCode(), ShouldEqual, 200) 4228 So(resp, ShouldNotBeNil) 4229 4230 var responseStruct zcommon.ImageListResponse 4231 err = json.Unmarshal(resp.Body(), &responseStruct) 4232 So(err, ShouldBeNil) 4233 4234 So(len(responseStruct.Results), ShouldEqual, limit) 4235 }) 4236 }) 4237 } 4238 4239 func TestGlobalSearchPagination(t *testing.T) { 4240 Convey("Test global search pagination", t, func() { 4241 dir := t.TempDir() 4242 4243 port := GetFreePort() 4244 baseURL := GetBaseURL(port) 4245 conf := config.New() 4246 conf.HTTP.Port = port 4247 conf.Storage.RootDirectory = dir 4248 defaultVal := true 4249 conf.Extensions = &extconf.ExtensionConfig{ 4250 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 4251 } 4252 4253 ctlr := api.NewController(conf) 4254 4255 ctlrManager := NewControllerManager(ctlr) 4256 ctlrManager.StartAndWait(port) 4257 defer ctlrManager.StopServer() 4258 4259 for i := 0; i < 3; i++ { 4260 image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() 4261 4262 err := UploadImage(image, baseURL, fmt.Sprintf("repo%d", i), "0.0.1") 4263 So(err, ShouldBeNil) 4264 } 4265 4266 Convey("Limit is bigger than the repo count", func() { 4267 query := ` 4268 { 4269 GlobalSearch(query:"repo", requestedPage:{limit: 9, offset: 0, sortBy:RELEVANCE}){ 4270 Repos { 4271 Name 4272 } 4273 } 4274 }` 4275 4276 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4277 So(resp, ShouldNotBeNil) 4278 So(err, ShouldBeNil) 4279 So(resp.StatusCode(), ShouldEqual, 200) 4280 4281 responseStruct := &zcommon.GlobalSearchResultResp{} 4282 4283 err = json.Unmarshal(resp.Body(), responseStruct) 4284 So(err, ShouldBeNil) 4285 4286 So(responseStruct.Images, ShouldBeEmpty) 4287 So(responseStruct.Repos, ShouldNotBeEmpty) 4288 So(responseStruct.Layers, ShouldBeEmpty) 4289 4290 So(len(responseStruct.Repos), ShouldEqual, 3) 4291 }) 4292 4293 Convey("Limit is lower than the repo count", func() { 4294 query := ` 4295 { 4296 GlobalSearch(query:"repo", requestedPage:{limit: 2, offset: 0, sortBy:RELEVANCE}){ 4297 Repos { 4298 Name 4299 } 4300 } 4301 }` 4302 4303 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4304 So(resp, ShouldNotBeNil) 4305 So(err, ShouldBeNil) 4306 So(resp.StatusCode(), ShouldEqual, 200) 4307 4308 responseStruct := &zcommon.GlobalSearchResultResp{} 4309 4310 err = json.Unmarshal(resp.Body(), responseStruct) 4311 So(err, ShouldBeNil) 4312 4313 So(responseStruct.Images, ShouldBeEmpty) 4314 So(responseStruct.Repos, ShouldNotBeEmpty) 4315 So(responseStruct.Layers, ShouldBeEmpty) 4316 4317 So(len(responseStruct.Repos), ShouldEqual, 2) 4318 }) 4319 4320 Convey("PageInfo returned proper response", func() { 4321 query := ` 4322 { 4323 GlobalSearch(query:"repo", requestedPage:{limit: 2, offset: 0, sortBy:RELEVANCE}){ 4324 Repos { 4325 Name 4326 } 4327 Page{ 4328 ItemCount 4329 TotalCount 4330 } 4331 } 4332 }` 4333 4334 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4335 So(resp, ShouldNotBeNil) 4336 So(err, ShouldBeNil) 4337 So(resp.StatusCode(), ShouldEqual, 200) 4338 4339 responseStruct := &zcommon.GlobalSearchResultResp{} 4340 4341 err = json.Unmarshal(resp.Body(), responseStruct) 4342 So(err, ShouldBeNil) 4343 4344 So(responseStruct.Images, ShouldBeEmpty) 4345 So(responseStruct.Repos, ShouldNotBeEmpty) 4346 So(responseStruct.Layers, ShouldBeEmpty) 4347 4348 So(len(responseStruct.Repos), ShouldEqual, 2) 4349 So(responseStruct.Page.TotalCount, ShouldEqual, 3) 4350 So(responseStruct.Page.ItemCount, ShouldEqual, 2) 4351 }) 4352 4353 Convey("PageInfo when limit is bigger than the repo count", func() { 4354 query := ` 4355 { 4356 GlobalSearch(query:"repo", requestedPage:{limit: 9, offset: 0, sortBy:RELEVANCE}){ 4357 Repos { 4358 Name 4359 } 4360 Page{ 4361 ItemCount 4362 TotalCount 4363 } 4364 } 4365 }` 4366 4367 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4368 So(resp, ShouldNotBeNil) 4369 So(err, ShouldBeNil) 4370 So(resp.StatusCode(), ShouldEqual, 200) 4371 4372 responseStruct := &zcommon.GlobalSearchResultResp{} 4373 4374 err = json.Unmarshal(resp.Body(), responseStruct) 4375 So(err, ShouldBeNil) 4376 4377 So(responseStruct.Images, ShouldBeEmpty) 4378 So(responseStruct.Repos, ShouldNotBeEmpty) 4379 So(responseStruct.Layers, ShouldBeEmpty) 4380 4381 So(len(responseStruct.Repos), ShouldEqual, 3) 4382 So(responseStruct.Page.TotalCount, ShouldEqual, 3) 4383 So(responseStruct.Page.ItemCount, ShouldEqual, 3) 4384 }) 4385 4386 Convey("PageInfo when limit and offset have 0 value", func() { 4387 query := ` 4388 { 4389 GlobalSearch(query:"repo", requestedPage:{limit: 0, offset: 0, sortBy:RELEVANCE}){ 4390 Repos { 4391 Name 4392 } 4393 Page{ 4394 ItemCount 4395 TotalCount 4396 } 4397 } 4398 }` 4399 4400 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4401 So(resp, ShouldNotBeNil) 4402 So(err, ShouldBeNil) 4403 So(resp.StatusCode(), ShouldEqual, 200) 4404 4405 responseStruct := &zcommon.GlobalSearchResultResp{} 4406 4407 err = json.Unmarshal(resp.Body(), responseStruct) 4408 So(err, ShouldBeNil) 4409 4410 So(responseStruct.Images, ShouldBeEmpty) 4411 So(responseStruct.Repos, ShouldNotBeEmpty) 4412 So(responseStruct.Layers, ShouldBeEmpty) 4413 4414 So(len(responseStruct.Repos), ShouldEqual, 3) 4415 So(responseStruct.Page.TotalCount, ShouldEqual, 3) 4416 So(responseStruct.Page.ItemCount, ShouldEqual, 3) 4417 }) 4418 }) 4419 } 4420 4421 func TestMetaDBWhenSigningImages(t *testing.T) { 4422 Convey("SigningImages", t, func() { 4423 subpath := "/a" 4424 4425 dir := t.TempDir() 4426 subDir := t.TempDir() 4427 4428 subRootDir := path.Join(subDir, subpath) 4429 4430 port := GetFreePort() 4431 baseURL := GetBaseURL(port) 4432 conf := config.New() 4433 conf.HTTP.Port = port 4434 conf.Storage.RootDirectory = dir 4435 conf.Storage.SubPaths = make(map[string]config.StorageConfig) 4436 conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir} 4437 defaultVal := true 4438 conf.Extensions = &extconf.ExtensionConfig{ 4439 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 4440 } 4441 4442 conf.Extensions.Search.CVE = nil 4443 4444 ctlr := api.NewController(conf) 4445 4446 ctlrManager := NewControllerManager(ctlr) 4447 ctlrManager.StartAndWait(port) 4448 defer ctlrManager.StopServer() 4449 4450 // push test images to repo 1 image 1 4451 image1 := CreateDefaultImage() 4452 4453 err := UploadImage(image1, baseURL, "repo1", "1.0.1") 4454 So(err, ShouldBeNil) 4455 4456 err = UploadImage(image1, baseURL, "repo1", "2.0.2") 4457 So(err, ShouldBeNil) 4458 4459 manifestBlob, err := json.Marshal(image1.Manifest) 4460 So(err, ShouldBeNil) 4461 4462 manifestDigest := godigest.FromBytes(manifestBlob) 4463 4464 multiArch := CreateRandomMultiarch() 4465 4466 err = UploadMultiarchImage(multiArch, baseURL, "repo1", "index") 4467 So(err, ShouldBeNil) 4468 4469 queryImage1 := ` 4470 { 4471 GlobalSearch(query:"repo1:1.0"){ 4472 Images { 4473 RepoName Tag LastUpdated Size IsSigned 4474 Manifests{ 4475 LastUpdated Size 4476 } 4477 } 4478 } 4479 }` 4480 4481 queryImage2 := ` 4482 { 4483 GlobalSearch(query:"repo1:2.0"){ 4484 Images { 4485 RepoName Tag LastUpdated Size IsSigned 4486 Manifests { LastUpdated Size Platform { Os Arch } } 4487 } 4488 } 4489 }` 4490 4491 queryIndex := ` 4492 { 4493 GlobalSearch(query:"repo1:index"){ 4494 Images { 4495 RepoName Tag LastUpdated Size IsSigned 4496 Manifests { LastUpdated Size Platform { Os Arch } } 4497 } 4498 } 4499 } 4500 ` 4501 4502 Convey("Sign with cosign", func() { 4503 err = signature.SignImageUsingCosign("repo1:1.0.1", port, false) 4504 So(err, ShouldBeNil) 4505 4506 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage1)) 4507 So(resp, ShouldNotBeNil) 4508 So(err, ShouldBeNil) 4509 So(resp.StatusCode(), ShouldEqual, 200) 4510 4511 responseStruct := &zcommon.GlobalSearchResultResp{} 4512 4513 err = json.Unmarshal(resp.Body(), responseStruct) 4514 So(err, ShouldBeNil) 4515 4516 So(responseStruct.Images[0].IsSigned, ShouldBeTrue) 4517 4518 // check image 2 is signed also because it has the same manifest 4519 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage2)) 4520 So(resp, ShouldNotBeNil) 4521 So(err, ShouldBeNil) 4522 So(resp.StatusCode(), ShouldEqual, 200) 4523 4524 responseStruct = &zcommon.GlobalSearchResultResp{} 4525 4526 err = json.Unmarshal(resp.Body(), responseStruct) 4527 So(err, ShouldBeNil) 4528 4529 So(responseStruct.Images[0].IsSigned, ShouldBeTrue) 4530 4531 // delete the signature 4532 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + 4533 fmt.Sprintf("sha256-%s.sig", manifestDigest.Encoded())) 4534 So(err, ShouldBeNil) 4535 So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) 4536 4537 // check image 2 is not signed anymore 4538 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage2)) 4539 So(resp, ShouldNotBeNil) 4540 So(err, ShouldBeNil) 4541 So(resp.StatusCode(), ShouldEqual, 200) 4542 4543 responseStruct = &zcommon.GlobalSearchResultResp{} 4544 4545 err = json.Unmarshal(resp.Body(), responseStruct) 4546 So(err, ShouldBeNil) 4547 4548 So(responseStruct.Images[0].IsSigned, ShouldBeFalse) 4549 }) 4550 4551 Convey("Cover errors when signing with cosign", func() { 4552 Convey("imageIsSignature fails", func() { 4553 // make image store ignore the wrong format of the input 4554 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 4555 PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest, 4556 godigest.Digest, error, 4557 ) { 4558 return "", "", nil 4559 }, 4560 DeleteImageManifestFn: func(repo, reference string, dc bool) error { 4561 return ErrTestError 4562 }, 4563 } 4564 4565 // push bad manifest blob 4566 resp, err := resty.R(). 4567 SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json"). 4568 SetBody([]byte("unmashable manifest blob")). 4569 Put(baseURL + "/v2/" + "repo" + "/manifests/" + "tag") 4570 So(err, ShouldBeNil) 4571 So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError) 4572 }) 4573 4574 Convey("image is a signature, AddManifestSignature fails", func() { 4575 ctlr.MetaDB = mocks.MetaDBMock{ 4576 AddManifestSignatureFn: func(repo string, signedManifestDigest godigest.Digest, 4577 sm mTypes.SignatureMetadata, 4578 ) error { 4579 return ErrTestError 4580 }, 4581 } 4582 4583 err := signature.SignImageUsingCosign("repo1:1.0.1", port, false) 4584 So(err, ShouldNotBeNil) 4585 }) 4586 }) 4587 4588 Convey("Sign with notation", func() { 4589 err = signature.SignImageUsingNotary("repo1:1.0.1", port, true) 4590 So(err, ShouldBeNil) 4591 4592 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImage1)) 4593 So(resp, ShouldNotBeNil) 4594 So(err, ShouldBeNil) 4595 So(resp.StatusCode(), ShouldEqual, 200) 4596 4597 responseStruct := &zcommon.GlobalSearchResultResp{} 4598 4599 err = json.Unmarshal(resp.Body(), responseStruct) 4600 So(err, ShouldBeNil) 4601 4602 So(responseStruct.Images[0].IsSigned, ShouldBeTrue) 4603 }) 4604 4605 Convey("Sign with notation index", func() { 4606 err = signature.SignImageUsingNotary("repo1:index", port, false) 4607 So(err, ShouldBeNil) 4608 4609 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryIndex)) 4610 So(resp, ShouldNotBeNil) 4611 So(err, ShouldBeNil) 4612 So(resp.StatusCode(), ShouldEqual, 200) 4613 4614 responseStruct := &zcommon.GlobalSearchResultResp{} 4615 4616 err = json.Unmarshal(resp.Body(), responseStruct) 4617 So(err, ShouldBeNil) 4618 4619 So(responseStruct.Images[0].IsSigned, ShouldBeTrue) 4620 }) 4621 4622 Convey("Sign with cosign index", func() { 4623 err = signature.SignImageUsingCosign("repo1:index", port, false) 4624 So(err, ShouldBeNil) 4625 4626 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryIndex)) 4627 So(resp, ShouldNotBeNil) 4628 So(err, ShouldBeNil) 4629 4630 So(resp.StatusCode(), ShouldEqual, 200) 4631 4632 responseStruct := &zcommon.GlobalSearchResultResp{} 4633 4634 err = json.Unmarshal(resp.Body(), responseStruct) 4635 So(err, ShouldBeNil) 4636 4637 So(responseStruct.Images[0].IsSigned, ShouldBeTrue) 4638 }) 4639 }) 4640 } 4641 4642 func TestMetaDBWhenPushingImages(t *testing.T) { 4643 Convey("Cover errors when pushing", t, func() { 4644 dir := t.TempDir() 4645 4646 port := GetFreePort() 4647 baseURL := GetBaseURL(port) 4648 conf := config.New() 4649 conf.HTTP.Port = port 4650 conf.Storage.RootDirectory = dir 4651 defaultVal := true 4652 conf.Extensions = &extconf.ExtensionConfig{ 4653 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 4654 } 4655 4656 ctlr := api.NewController(conf) 4657 4658 ctlrManager := NewControllerManager(ctlr) 4659 ctlrManager.StartAndWait(port) 4660 defer ctlrManager.StopServer() 4661 4662 Convey("SetManifestMeta succeeds but SetRepoReference fails", func() { 4663 ctlr.MetaDB = mocks.MetaDBMock{ 4664 SetRepoReferenceFn: func(ctx context.Context, repo, reference string, imageMeta mTypes.ImageMeta) error { 4665 return ErrTestError 4666 }, 4667 } 4668 4669 image := CreateRandomImage() 4670 4671 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 4672 NewBlobUploadFn: ctlr.StoreController.DefaultStore.NewBlobUpload, 4673 PutBlobChunkFn: ctlr.StoreController.DefaultStore.PutBlobChunk, 4674 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 4675 return image.ConfigDescriptor.Data, nil 4676 }, 4677 } 4678 4679 err := UploadImage(image, baseURL, "repo1", "1.0.1") 4680 So(err, ShouldNotBeNil) 4681 }) 4682 }) 4683 } 4684 4685 func TestMetaDBIndexOperations(t *testing.T) { 4686 Convey("Idex Operations BoltDB", t, func() { 4687 dir := t.TempDir() 4688 4689 port := GetFreePort() 4690 baseURL := GetBaseURL(port) 4691 conf := config.New() 4692 conf.HTTP.Port = port 4693 conf.Storage.RootDirectory = dir 4694 conf.Storage.GC = false 4695 defaultVal := true 4696 conf.Extensions = &extconf.ExtensionConfig{ 4697 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 4698 } 4699 4700 ctlr := api.NewController(conf) 4701 4702 ctlrManager := NewControllerManager(ctlr) 4703 ctlrManager.StartAndWait(port) 4704 defer ctlrManager.StopServer() 4705 4706 RunMetaDBIndexTests(baseURL, port) 4707 }) 4708 } 4709 4710 func RunMetaDBIndexTests(baseURL, port string) { 4711 Convey("Push test index", func() { 4712 const repo = "repo" 4713 4714 multiarchImage := CreateRandomMultiarch() 4715 4716 err := UploadMultiarchImage(multiarchImage, baseURL, repo, "tag1") 4717 So(err, ShouldBeNil) 4718 4719 query := ` 4720 { 4721 GlobalSearch(query:"repo:tag1"){ 4722 Images { 4723 RepoName Tag DownloadCount 4724 IsSigned 4725 Manifests { 4726 Digest 4727 ConfigDigest 4728 Platform {Os Arch} 4729 Layers {Size Digest} 4730 LastUpdated 4731 Size 4732 } 4733 } 4734 } 4735 }` 4736 4737 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4738 So(resp, ShouldNotBeNil) 4739 So(err, ShouldBeNil) 4740 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 4741 4742 responseStruct := &zcommon.GlobalSearchResultResp{} 4743 4744 err = json.Unmarshal(resp.Body(), responseStruct) 4745 So(err, ShouldBeNil) 4746 4747 responseImages := responseStruct.GlobalSearchResult.GlobalSearch.Images 4748 So(responseImages, ShouldNotBeEmpty) 4749 responseImage := responseImages[0] 4750 So(len(responseImage.Manifests), ShouldEqual, 3) 4751 4752 err = signature.SignImageUsingCosign(fmt.Sprintf("repo@%s", multiarchImage.DigestStr()), port, false) 4753 So(err, ShouldBeNil) 4754 4755 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4756 So(resp, ShouldNotBeNil) 4757 So(err, ShouldBeNil) 4758 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 4759 4760 responseStruct = &zcommon.GlobalSearchResultResp{} 4761 4762 err = json.Unmarshal(resp.Body(), responseStruct) 4763 So(err, ShouldBeNil) 4764 4765 responseImages = responseStruct.GlobalSearchResult.GlobalSearch.Images 4766 So(responseImages, ShouldNotBeEmpty) 4767 responseImage = responseImages[0] 4768 4769 So(responseImage.IsSigned, ShouldBeTrue) 4770 4771 // remove signature 4772 cosignTag := "sha256-" + multiarchImage.Digest().Encoded() + ".sig" 4773 _, err = resty.R().Delete(baseURL + "/v2/" + "repo" + "/manifests/" + cosignTag) 4774 So(err, ShouldBeNil) 4775 4776 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4777 So(resp, ShouldNotBeNil) 4778 So(err, ShouldBeNil) 4779 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 4780 4781 responseStruct = &zcommon.GlobalSearchResultResp{} 4782 4783 err = json.Unmarshal(resp.Body(), responseStruct) 4784 So(err, ShouldBeNil) 4785 4786 responseImages = responseStruct.GlobalSearchResult.GlobalSearch.Images 4787 So(responseImages, ShouldNotBeEmpty) 4788 responseImage = responseImages[0] 4789 4790 So(responseImage.IsSigned, ShouldBeFalse) 4791 }) 4792 Convey("Index base images", func() { 4793 // ---------------- BASE IMAGE ------------------- 4794 imageAMD64 := CreateImageWith().LayerBlobs([][]byte{ 4795 {10, 20, 30}, 4796 {11, 21, 31}, 4797 }).ImageConfig(ispec.Image{ 4798 Platform: ispec.Platform{ 4799 OS: "linux", 4800 Architecture: "amd64", 4801 }, 4802 }).Build() 4803 4804 imageSomeArch := CreateImageWith().LayerBlobs([][]byte{ 4805 {18, 28, 38}, 4806 {12, 22, 32}, 4807 }).ImageConfig(ispec.Image{ 4808 Platform: ispec.Platform{ 4809 OS: "linux", 4810 Architecture: "someArch", 4811 }, 4812 }).Build() 4813 4814 multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build() 4815 4816 err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "latest") 4817 So(err, ShouldBeNil) 4818 // ---------------- BASE IMAGE ------------------- 4819 4820 // ---------------- SAME LAYERS ------------------- 4821 image1 := CreateImageWith().LayerBlobs([][]byte{{0, 0, 2}}).ImageConfig(imageSomeArch.Config).Build() 4822 4823 image2 := CreateImageWith().LayerBlobs(imageAMD64.Layers).ImageConfig(imageAMD64.Config).Build() 4824 4825 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 4826 4827 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-same-layers", "index-one-arch-same-layers") 4828 So(err, ShouldBeNil) 4829 // ---------------- SAME LAYERS ------------------- 4830 4831 // ---------------- LESS LAYERS ------------------- 4832 image1 = CreateImageWith().LayerBlobs([][]byte{ 4833 {3, 2, 2}, 4834 {5, 2, 5}, 4835 }).ImageConfig(imageSomeArch.Config).Build() 4836 4837 image2 = CreateImageWith().LayerBlobs([][]byte{ 4838 imageAMD64.Layers[0], 4839 }).ImageConfig(imageAMD64.Config).Build() 4840 4841 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 4842 4843 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers", "index-one-arch-less-layers") 4844 So(err, ShouldBeNil) 4845 // ---------------- LESS LAYERS ------------------- 4846 4847 // ---------------- LESS LAYERS FALSE ------------------- 4848 image1 = CreateImageWith().LayerBlobs([][]byte{ 4849 {3, 2, 2}, 4850 {5, 2, 5}, 4851 }).ImageConfig(imageSomeArch.Config).Build() 4852 4853 auxLayer := imageAMD64.Layers[0] 4854 auxLayer[0] = 20 4855 4856 image2 = CreateImageWith().LayerBlobs([][]byte{ 4857 auxLayer, 4858 }).ImageConfig(imageAMD64.Config).Build() 4859 4860 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 4861 4862 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers-false", 4863 "index-one-arch-less-layers-false") 4864 So(err, ShouldBeNil) 4865 // ---------------- LESS LAYERS FALSE ------------------- 4866 4867 // ---------------- MORE LAYERS ------------------- 4868 image1 = CreateImageWith().LayerBlobs([][]byte{ 4869 {0, 0, 2}, 4870 {3, 0, 2}, 4871 }).ImageConfig(imageSomeArch.Config).Build() 4872 4873 image2 = CreateImageWith().LayerBlobs( 4874 append(imageAMD64.Layers, []byte{1, 3, 55}), 4875 ).ImageConfig(imageAMD64.Config).Build() 4876 4877 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 4878 4879 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-more-layers", "index-one-arch-more-layers") 4880 So(err, ShouldBeNil) 4881 // ---------------- MORE LAYERS ------------------- 4882 4883 query := ` 4884 { 4885 BaseImageList(image:"test-repo:latest"){ 4886 Results{ 4887 RepoName 4888 Tag 4889 Manifests { 4890 Digest 4891 ConfigDigest 4892 LastUpdated 4893 Size 4894 } 4895 Size 4896 } 4897 } 4898 }` 4899 4900 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 4901 So(resp, ShouldNotBeNil) 4902 So(err, ShouldBeNil) 4903 4904 So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers"), ShouldBeTrue) 4905 So(strings.Contains(string(resp.Body()), "index-one-arch-same-layers"), ShouldBeFalse) 4906 So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers-false"), ShouldBeFalse) 4907 So(strings.Contains(string(resp.Body()), "index-one-arch-more-layers"), ShouldBeFalse) 4908 So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) 4909 }) 4910 4911 Convey("Index base images for digest", func() { 4912 // ---------------- BASE IMAGE ------------------- 4913 imageAMD64 := CreateImageWith().LayerBlobs([][]byte{ 4914 {10, 20, 30}, 4915 {11, 21, 31}, 4916 }).ImageConfig(ispec.Image{ 4917 Platform: ispec.Platform{ 4918 OS: "linux", 4919 Architecture: "amd64", 4920 }, 4921 }).Build() 4922 4923 baseLinuxAMD64Digest := imageAMD64.ManifestDescriptor.Digest 4924 4925 imageSomeArch := CreateImageWith().LayerBlobs([][]byte{ 4926 {18, 28, 38}, 4927 {12, 22, 32}, 4928 }).ImageConfig(ispec.Image{ 4929 Platform: ispec.Platform{ 4930 OS: "linux", 4931 Architecture: "someArch", 4932 }, 4933 }).Build() 4934 4935 baseLinuxSomeArchDigest := imageSomeArch.ManifestDescriptor.Digest 4936 4937 multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build() 4938 4939 err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "index") 4940 So(err, ShouldBeNil) 4941 // ---------------- BASE IMAGE FOR LINUX AMD64 ------------------- 4942 4943 image := CreateImageWith().LayerBlobs([][]byte{ 4944 imageAMD64.Layers[0], 4945 }).ImageConfig(imageAMD64.Config).Build() 4946 4947 err = UploadImage(image, baseURL, "test-repo", "less-layers-linux-amd64") 4948 So(err, ShouldBeNil) 4949 4950 // ---------------- BASE IMAGE FOR LINUX SOMEARCH ------------------- 4951 4952 image = CreateImageWith().LayerBlobs([][]byte{ 4953 imageSomeArch.Layers[0], 4954 }).ImageConfig(imageAMD64.Config).Build() 4955 4956 err = UploadImage(image, baseURL, "test-repo", "less-layers-linux-somearch") 4957 So(err, ShouldBeNil) 4958 4959 // ------- TEST 4960 4961 query := ` 4962 { 4963 BaseImageList(image:"test-repo:index", digest:"%s"){ 4964 Results{ 4965 RepoName 4966 Tag 4967 Manifests { 4968 Digest 4969 ConfigDigest 4970 LastUpdated 4971 Size 4972 } 4973 Size 4974 } 4975 } 4976 }` 4977 4978 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + 4979 url.QueryEscape( 4980 fmt.Sprintf(query, baseLinuxAMD64Digest.String()), 4981 ), 4982 ) 4983 So(resp, ShouldNotBeNil) 4984 So(err, ShouldBeNil) 4985 4986 So(strings.Contains(string(resp.Body()), "less-layers-linux-amd64"), ShouldEqual, true) 4987 So(strings.Contains(string(resp.Body()), "less-layers-linux-somearch"), ShouldEqual, false) 4988 4989 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + 4990 url.QueryEscape( 4991 fmt.Sprintf(query, baseLinuxSomeArchDigest.String()), 4992 ), 4993 ) 4994 So(resp, ShouldNotBeNil) 4995 So(err, ShouldBeNil) 4996 4997 So(strings.Contains(string(resp.Body()), "less-layers-linux-amd64"), ShouldEqual, false) 4998 So(strings.Contains(string(resp.Body()), "less-layers-linux-somearch"), ShouldEqual, true) 4999 }) 5000 5001 Convey("Index derived images", func() { 5002 // ---------------- BASE IMAGE ------------------- 5003 imageAMD64 := CreateImageWith().LayerBlobs([][]byte{ 5004 {10, 20, 30}, 5005 {11, 21, 31}, 5006 }).ImageConfig(ispec.Image{ 5007 Platform: ispec.Platform{ 5008 OS: "linux", 5009 Architecture: "amd64", 5010 }, 5011 }).Build() 5012 5013 imageSomeArch := CreateImageWith().LayerBlobs([][]byte{ 5014 {18, 28, 38}, 5015 {12, 22, 32}, 5016 }).ImageConfig(ispec.Image{ 5017 Platform: ispec.Platform{ 5018 OS: "linux", 5019 Architecture: "someArch", 5020 }, 5021 }).Build() 5022 5023 multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build() 5024 5025 err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "latest") 5026 So(err, ShouldBeNil) 5027 // ---------------- BASE IMAGE ------------------- 5028 5029 // ---------------- SAME LAYERS ------------------- 5030 image1 := CreateImageWith().LayerBlobs([][]byte{ 5031 {0, 0, 2}, 5032 }).ImageConfig(imageSomeArch.Config).Build() 5033 5034 image2 := CreateImageWith().LayerBlobs( 5035 imageAMD64.Layers, 5036 ).ImageConfig(imageAMD64.Config).Build() 5037 5038 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 5039 5040 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-same-layers", "index-one-arch-same-layers") 5041 So(err, ShouldBeNil) 5042 // ---------------- SAME LAYERS ------------------- 5043 5044 // ---------------- LESS LAYERS ------------------- 5045 image1 = CreateImageWith().LayerBlobs([][]byte{ 5046 {3, 2, 2}, 5047 {5, 2, 5}, 5048 }).ImageConfig(imageSomeArch.Config).Build() 5049 5050 image2 = CreateImageWith().LayerBlobs([][]byte{ 5051 imageAMD64.Layers[0], 5052 }).ImageConfig(imageAMD64.Config).Build() 5053 5054 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 5055 5056 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers", "index-one-arch-less-layers") 5057 So(err, ShouldBeNil) 5058 // ---------------- LESS LAYERS ------------------- 5059 5060 // ---------------- LESS LAYERS FALSE ------------------- 5061 image1 = CreateImageWith().LayerBlobs([][]byte{ 5062 {3, 2, 2}, 5063 {5, 2, 5}, 5064 }).ImageConfig(imageSomeArch.Config).Build() 5065 5066 image2 = CreateImageWith().LayerBlobs([][]byte{ 5067 {99, 100, 102}, 5068 }).ImageConfig(imageAMD64.Config).Build() 5069 5070 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 5071 5072 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-less-layers-false", 5073 "index-one-arch-less-layers-false") 5074 So(err, ShouldBeNil) 5075 // ---------------- LESS LAYERS FALSE ------------------- 5076 5077 // ---------------- MORE LAYERS ------------------- 5078 image1 = CreateImageWith().LayerBlobs([][]byte{ 5079 {0, 0, 2}, 5080 {3, 0, 2}, 5081 }).ImageConfig(imageSomeArch.Config).Build() 5082 5083 image2 = CreateImageWith().LayerBlobs([][]byte{ 5084 imageAMD64.Layers[0], 5085 imageAMD64.Layers[1], 5086 {1, 3, 55}, 5087 }).ImageConfig(imageAMD64.Config).Build() 5088 5089 multiImage = CreateMultiarchWith().Images([]Image{image1, image2}).Build() 5090 5091 err = UploadMultiarchImage(multiImage, baseURL, "index-one-arch-more-layers", "index-one-arch-more-layers") 5092 So(err, ShouldBeNil) 5093 // ---------------- MORE LAYERS ------------------- 5094 5095 query := ` 5096 { 5097 DerivedImageList(image:"test-repo:latest"){ 5098 Results{ 5099 RepoName 5100 Tag 5101 Manifests { 5102 Digest 5103 ConfigDigest 5104 LastUpdated 5105 Size 5106 } 5107 Size 5108 } 5109 } 5110 }` 5111 5112 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5113 So(resp, ShouldNotBeNil) 5114 So(err, ShouldBeNil) 5115 5116 So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers"), ShouldBeFalse) 5117 So(strings.Contains(string(resp.Body()), "index-one-arch-same-layers"), ShouldBeFalse) 5118 So(strings.Contains(string(resp.Body()), "index-one-arch-less-layers-false"), ShouldBeFalse) 5119 So(strings.Contains(string(resp.Body()), "index-one-arch-more-layers"), ShouldBeTrue) 5120 So(strings.Contains(string(resp.Body()), "test-repo"), ShouldBeFalse) 5121 }) 5122 5123 Convey("Index derived images for digest", func() { 5124 // ---------------- BASE IMAGE ------------------- 5125 imageAMD64 := CreateImageWith().LayerBlobs([][]byte{ 5126 {10, 20, 30}, 5127 {11, 21, 31}, 5128 }).ImageConfig(ispec.Image{ 5129 Platform: ispec.Platform{ 5130 OS: "linux", 5131 Architecture: "amd64", 5132 }, 5133 }).Build() 5134 5135 baseLinuxAMD64Digest := imageAMD64.ManifestDescriptor.Digest 5136 5137 imageSomeArch := CreateImageWith().LayerBlobs([][]byte{ 5138 {18, 28, 38}, 5139 {12, 22, 32}, 5140 }).ImageConfig(ispec.Image{ 5141 Platform: ispec.Platform{ 5142 OS: "linux", 5143 Architecture: "someArch", 5144 }, 5145 }).Build() 5146 5147 baseLinuxSomeArchDigest := imageSomeArch.ManifestDescriptor.Digest 5148 5149 multiImage := CreateMultiarchWith().Images([]Image{imageAMD64, imageSomeArch}).Build() 5150 5151 err := UploadMultiarchImage(multiImage, baseURL, "test-repo", "index") 5152 So(err, ShouldBeNil) 5153 // ---------------- BASE IMAGE FOR LINUX AMD64 ------------------- 5154 image := CreateImageWith().LayerBlobs([][]byte{ 5155 imageAMD64.Layers[0], 5156 imageAMD64.Layers[1], 5157 {0, 0, 0, 0}, 5158 {1, 1, 1, 1}, 5159 }).ImageConfig(imageAMD64.Config).Build() 5160 5161 err = UploadImage(image, baseURL, "test-repo", "more-layers-linux-amd64") 5162 So(err, ShouldBeNil) 5163 5164 // ---------------- BASE IMAGE FOR LINUX SOMEARCH ------------------- 5165 image = CreateImageWith().LayerBlobs([][]byte{ 5166 imageSomeArch.Layers[0], 5167 imageSomeArch.Layers[1], 5168 {3, 3, 3, 3}, 5169 {2, 2, 2, 2}, 5170 }).ImageConfig(imageAMD64.Config).Build() 5171 5172 err = UploadImage(image, baseURL, "test-repo", "more-layers-linux-somearch") 5173 So(err, ShouldBeNil) 5174 5175 // ------- TEST 5176 5177 query := ` 5178 { 5179 DerivedImageList(image:"test-repo:index", digest:"%s"){ 5180 Results{ 5181 RepoName 5182 Tag 5183 Manifests { 5184 Digest 5185 ConfigDigest 5186 LastUpdated 5187 Size 5188 } 5189 Size 5190 } 5191 } 5192 }` 5193 5194 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + 5195 url.QueryEscape( 5196 fmt.Sprintf(query, baseLinuxAMD64Digest.String()), 5197 ), 5198 ) 5199 So(resp, ShouldNotBeNil) 5200 So(err, ShouldBeNil) 5201 5202 So(strings.Contains(string(resp.Body()), "more-layers-linux-amd64"), ShouldEqual, true) 5203 So(strings.Contains(string(resp.Body()), "more-layers-linux-somearch"), ShouldEqual, false) 5204 5205 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + 5206 url.QueryEscape( 5207 fmt.Sprintf(query, baseLinuxSomeArchDigest.String()), 5208 ), 5209 ) 5210 So(resp, ShouldNotBeNil) 5211 So(err, ShouldBeNil) 5212 5213 So(strings.Contains(string(resp.Body()), "more-layers-linux-amd64"), ShouldEqual, false) 5214 So(strings.Contains(string(resp.Body()), "more-layers-linux-somearch"), ShouldEqual, true) 5215 }) 5216 } 5217 5218 func TestMetaDBWhenReadingImages(t *testing.T) { 5219 Convey("Push test image", t, func() { 5220 dir := t.TempDir() 5221 5222 port := GetFreePort() 5223 baseURL := GetBaseURL(port) 5224 conf := config.New() 5225 conf.HTTP.Port = port 5226 conf.Storage.RootDirectory = dir 5227 defaultVal := true 5228 conf.Extensions = &extconf.ExtensionConfig{ 5229 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 5230 } 5231 5232 ctlr := api.NewController(conf) 5233 5234 ctlrManager := NewControllerManager(ctlr) 5235 ctlrManager.StartAndWait(port) 5236 defer ctlrManager.StopServer() 5237 5238 image := CreateImageWith().RandomLayers(1, 100).DefaultConfig().Build() 5239 5240 err := UploadImage(image, baseURL, "repo1", "1.0.1") 5241 So(err, ShouldBeNil) 5242 5243 Convey("Download 3 times", func() { 5244 resp, err := resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1") 5245 So(err, ShouldBeNil) 5246 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5247 5248 resp, err = resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1") 5249 So(err, ShouldBeNil) 5250 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5251 5252 resp, err = resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1") 5253 So(err, ShouldBeNil) 5254 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5255 5256 query := ` 5257 { 5258 GlobalSearch(query:"repo1:1.0"){ 5259 Images { 5260 RepoName Tag DownloadCount 5261 } 5262 } 5263 }` 5264 5265 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5266 So(resp, ShouldNotBeNil) 5267 So(err, ShouldBeNil) 5268 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5269 5270 responseStruct := &zcommon.GlobalSearchResultResp{} 5271 5272 err = json.Unmarshal(resp.Body(), responseStruct) 5273 So(err, ShouldBeNil) 5274 So(responseStruct.Images, ShouldNotBeEmpty) 5275 So(responseStruct.Images[0].DownloadCount, ShouldEqual, 3) 5276 }) 5277 5278 Convey("Error when incrementing", func() { 5279 ctlr.MetaDB = mocks.MetaDBMock{ 5280 UpdateStatsOnDownloadFn: func(repo string, tag string) error { 5281 return ErrTestError 5282 }, 5283 } 5284 5285 resp, err := resty.R().Get(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1") 5286 So(err, ShouldBeNil) 5287 So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError) 5288 }) 5289 }) 5290 } 5291 5292 func TestMetaDBWhenDeletingImages(t *testing.T) { 5293 Convey("Setting up zot repo with test images", t, func() { 5294 dir := t.TempDir() 5295 port := GetFreePort() 5296 baseURL := GetBaseURL(port) 5297 5298 conf := config.New() 5299 conf.HTTP.Port = port 5300 conf.Storage.RootDirectory = dir 5301 conf.Storage.GC = false 5302 5303 defaultVal := true 5304 conf.Extensions = &extconf.ExtensionConfig{ 5305 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 5306 } 5307 5308 conf.Extensions.Search.CVE = nil 5309 5310 ctlr := api.NewController(conf) 5311 5312 ctlrManager := NewControllerManager(ctlr) 5313 ctlrManager.StartAndWait(port) 5314 defer ctlrManager.StopServer() 5315 5316 // push test images to repo 1 image 1 5317 image1 := CreateRandomImage() 5318 5319 err := UploadImage(image1, baseURL, "repo1", "1.0.1") 5320 So(err, ShouldBeNil) 5321 5322 // push test images to repo 1 image 2 5323 createdTime2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC) 5324 image2 := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{ 5325 Created: &createdTime2, 5326 History: []ispec.History{ 5327 { 5328 Created: &createdTime2, 5329 }, 5330 }, 5331 }).Build() 5332 5333 err = UploadImage(image2, baseURL, "repo1", "1.0.2") 5334 So(err, ShouldBeNil) 5335 5336 query := ` 5337 { 5338 GlobalSearch(query:"repo1:1.0"){ 5339 Images { 5340 RepoName Tag LastUpdated Size IsSigned 5341 Manifests{ 5342 Platform { Os Arch } 5343 LastUpdated Size 5344 } 5345 } 5346 } 5347 }` 5348 5349 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5350 So(resp, ShouldNotBeNil) 5351 So(err, ShouldBeNil) 5352 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5353 5354 responseStruct := &zcommon.GlobalSearchResultResp{} 5355 5356 err = json.Unmarshal(resp.Body(), responseStruct) 5357 So(err, ShouldBeNil) 5358 5359 So(len(responseStruct.Images), ShouldEqual, 2) 5360 5361 Convey("Delete a normal tag", func() { 5362 resp, err := resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "1.0.1") 5363 So(resp, ShouldNotBeNil) 5364 So(err, ShouldBeNil) 5365 So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) 5366 5367 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5368 So(resp, ShouldNotBeNil) 5369 So(err, ShouldBeNil) 5370 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5371 5372 responseStruct := &zcommon.GlobalSearchResultResp{} 5373 5374 err = json.Unmarshal(resp.Body(), responseStruct) 5375 So(err, ShouldBeNil) 5376 5377 So(len(responseStruct.Images), ShouldEqual, 1) 5378 So(responseStruct.Images[0].Tag, ShouldEqual, "1.0.2") 5379 }) 5380 5381 Convey("Delete a cosign signature", func() { 5382 repo := "repo1" 5383 err := signature.SignImageUsingCosign("repo1:1.0.1", port, false) 5384 So(err, ShouldBeNil) 5385 5386 query := ` 5387 { 5388 GlobalSearch(query:"repo1:1.0.1"){ 5389 Images { 5390 RepoName Tag LastUpdated Size IsSigned 5391 Manifests{ 5392 Platform { Os Arch } 5393 LastUpdated Size 5394 } 5395 } 5396 } 5397 }` 5398 5399 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5400 So(resp, ShouldNotBeNil) 5401 So(err, ShouldBeNil) 5402 So(resp.StatusCode(), ShouldEqual, 200) 5403 5404 responseStruct := &zcommon.GlobalSearchResultResp{} 5405 5406 err = json.Unmarshal(resp.Body(), responseStruct) 5407 So(err, ShouldBeNil) 5408 5409 So(responseStruct.Images[0].IsSigned, ShouldBeTrue) 5410 5411 // get signatur digest 5412 log := log.NewLogger("debug", "") 5413 metrics := monitoring.NewMetricsServer(false, log) 5414 storage := local.NewImageStore(dir, false, false, log, metrics, nil, nil) 5415 5416 indexBlob, err := storage.GetIndexContent(repo) 5417 So(err, ShouldBeNil) 5418 5419 var indexContent ispec.Index 5420 5421 err = json.Unmarshal(indexBlob, &indexContent) 5422 So(err, ShouldBeNil) 5423 5424 signatureTag := "" 5425 5426 for _, manifest := range indexContent.Manifests { 5427 tag := manifest.Annotations[ispec.AnnotationRefName] 5428 5429 if zcommon.IsCosignTag(tag) { 5430 signatureTag = tag 5431 } 5432 } 5433 5434 // delete the signature using the digest 5435 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + signatureTag) 5436 So(resp, ShouldNotBeNil) 5437 So(err, ShouldBeNil) 5438 So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) 5439 5440 // verify isSigned again and it should be false 5441 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5442 So(resp, ShouldNotBeNil) 5443 So(err, ShouldBeNil) 5444 So(resp.StatusCode(), ShouldEqual, 200) 5445 5446 responseStruct = &zcommon.GlobalSearchResultResp{} 5447 5448 err = json.Unmarshal(resp.Body(), responseStruct) 5449 So(err, ShouldBeNil) 5450 5451 So(responseStruct.Images[0].IsSigned, ShouldBeFalse) 5452 }) 5453 5454 Convey("Delete a notary signature", func() { 5455 repo := "repo1" 5456 err := signature.SignImageUsingNotary("repo1:1.0.1", port, true) 5457 So(err, ShouldBeNil) 5458 5459 query := ` 5460 { 5461 GlobalSearch(query:"repo1:1.0.1"){ 5462 Images { 5463 RepoName Tag LastUpdated Size IsSigned 5464 Manifests{ 5465 Platform { Os Arch } 5466 LastUpdated Size 5467 } 5468 } 5469 } 5470 }` 5471 5472 // test if it's signed 5473 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5474 So(resp, ShouldNotBeNil) 5475 So(err, ShouldBeNil) 5476 So(resp.StatusCode(), ShouldEqual, 200) 5477 5478 responseStruct := &zcommon.GlobalSearchResultResp{} 5479 5480 err = json.Unmarshal(resp.Body(), responseStruct) 5481 So(err, ShouldBeNil) 5482 5483 So(responseStruct.Images[0].IsSigned, ShouldBeTrue) 5484 5485 // get signatur digest 5486 log := log.NewLogger("debug", "") 5487 metrics := monitoring.NewMetricsServer(false, log) 5488 storage := local.NewImageStore(dir, false, false, log, metrics, nil, nil) 5489 5490 indexBlob, err := storage.GetIndexContent(repo) 5491 So(err, ShouldBeNil) 5492 5493 var indexContent ispec.Index 5494 5495 err = json.Unmarshal(indexBlob, &indexContent) 5496 So(err, ShouldBeNil) 5497 5498 signatureReference := "" 5499 5500 var sigManifestContent ispec.Manifest 5501 5502 for _, manifest := range indexContent.Manifests { 5503 manifestBlob, _, _, err := storage.GetImageManifest(repo, manifest.Digest.String()) 5504 So(err, ShouldBeNil) 5505 var manifestContent ispec.Manifest 5506 5507 err = json.Unmarshal(manifestBlob, &manifestContent) 5508 So(err, ShouldBeNil) 5509 5510 if zcommon.GetManifestArtifactType(manifestContent) == notreg.ArtifactTypeNotation { 5511 signatureReference = manifest.Digest.String() 5512 manifestBlob, _, _, err := storage.GetImageManifest(repo, signatureReference) 5513 So(err, ShouldBeNil) 5514 err = json.Unmarshal(manifestBlob, &sigManifestContent) 5515 So(err, ShouldBeNil) 5516 } 5517 } 5518 5519 So(sigManifestContent, ShouldNotBeZeroValue) 5520 // check notation signature 5521 manifest1Blob, err := json.Marshal(image1.Manifest) 5522 So(err, ShouldBeNil) 5523 manifest1Digest := godigest.FromBytes(manifest1Blob) 5524 So(sigManifestContent.Subject, ShouldNotBeNil) 5525 So(sigManifestContent.Subject.Digest.String(), ShouldEqual, manifest1Digest.String()) 5526 5527 // delete the signature using the digest 5528 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + signatureReference) 5529 So(resp, ShouldNotBeNil) 5530 So(err, ShouldBeNil) 5531 So(resp.StatusCode(), ShouldEqual, http.StatusAccepted) 5532 5533 // verify isSigned again and it should be false 5534 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5535 So(resp, ShouldNotBeNil) 5536 So(err, ShouldBeNil) 5537 So(resp.StatusCode(), ShouldEqual, 200) 5538 5539 responseStruct = &zcommon.GlobalSearchResultResp{} 5540 5541 err = json.Unmarshal(resp.Body(), responseStruct) 5542 So(err, ShouldBeNil) 5543 5544 So(responseStruct.Images[0].IsSigned, ShouldBeFalse) 5545 }) 5546 5547 Convey("Delete a referrer", func() { 5548 referredImageDescriptor := &ispec.Descriptor{ 5549 Digest: image1.ManifestDescriptor.Digest, 5550 MediaType: ispec.MediaTypeImageManifest, 5551 } 5552 5553 referrerImage := CreateImageWith().RandomLayers(1, 100).DefaultConfig(). 5554 Subject(referredImageDescriptor).Build() 5555 5556 err = UploadImage(referrerImage, baseURL, "repo1", referrerImage.DigestStr()) 5557 So(err, ShouldBeNil) 5558 5559 // ------- check referrers for this image 5560 5561 query := fmt.Sprintf(` 5562 { 5563 Referrers(repo:"repo1", digest:"%s"){ 5564 MediaType 5565 Digest 5566 } 5567 }`, image1.ManifestDescriptor.Digest.String()) 5568 5569 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5570 So(resp, ShouldNotBeNil) 5571 So(err, ShouldBeNil) 5572 So(resp.StatusCode(), ShouldEqual, 200) 5573 5574 responseStruct := &zcommon.ReferrersResp{} 5575 5576 err = json.Unmarshal(resp.Body(), responseStruct) 5577 So(err, ShouldBeNil) 5578 5579 So(len(responseStruct.Referrers), ShouldEqual, 1) 5580 So(responseStruct.Referrers[0].Digest, ShouldResemble, referrerImage.DigestStr()) 5581 5582 statusCode, err := DeleteImage("repo1", referrerImage.DigestStr(), "badURL") 5583 So(err, ShouldNotBeNil) 5584 So(statusCode, ShouldEqual, -1) 5585 5586 // ------- Delete the referrer and see if it disappears from metaDB also 5587 statusCode, err = DeleteImage("repo1", referrerImage.DigestStr(), baseURL) 5588 So(err, ShouldBeNil) 5589 So(statusCode, ShouldEqual, http.StatusAccepted) 5590 5591 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5592 So(resp, ShouldNotBeNil) 5593 So(err, ShouldBeNil) 5594 So(resp.StatusCode(), ShouldEqual, 200) 5595 5596 responseStruct = &zcommon.ReferrersResp{} 5597 5598 err = json.Unmarshal(resp.Body(), responseStruct) 5599 So(err, ShouldBeNil) 5600 5601 So(len(responseStruct.Referrers), ShouldEqual, 0) 5602 }) 5603 5604 Convey("Deleting causes errors", func() { 5605 Convey("error while backing up the manifest", func() { 5606 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 5607 GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) { 5608 return []byte{}, "", "", zerr.ErrRepoNotFound 5609 }, 5610 } 5611 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference") 5612 So(resp, ShouldNotBeNil) 5613 So(err, ShouldBeNil) 5614 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 5615 GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) { 5616 return []byte{}, "", "", zerr.ErrBadManifest 5617 }, 5618 } 5619 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference") 5620 So(resp, ShouldNotBeNil) 5621 So(err, ShouldBeNil) 5622 5623 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 5624 GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) { 5625 return []byte{}, "", "", zerr.ErrRepoNotFound 5626 }, 5627 } 5628 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference") 5629 So(resp, ShouldNotBeNil) 5630 So(err, ShouldBeNil) 5631 }) 5632 5633 Convey("imageIsSignature fails", func() { 5634 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 5635 PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest, 5636 godigest.Digest, error, 5637 ) { 5638 return "", "", nil 5639 }, 5640 DeleteImageManifestFn: func(repo, reference string, dc bool) error { 5641 return nil 5642 }, 5643 } 5644 5645 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + "signatureReference") 5646 So(resp, ShouldNotBeNil) 5647 So(err, ShouldBeNil) 5648 So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError) 5649 }) 5650 5651 Convey("image is a signature, DeleteSignature fails", func() { 5652 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 5653 NewBlobUploadFn: ctlr.StoreController.DefaultStore.NewBlobUpload, 5654 PutBlobChunkFn: ctlr.StoreController.DefaultStore.PutBlobChunk, 5655 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 5656 configBlob, err := json.Marshal(ispec.Image{}) 5657 So(err, ShouldBeNil) 5658 5659 return configBlob, nil 5660 }, 5661 PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest, 5662 godigest.Digest, error, 5663 ) { 5664 return "", "", nil 5665 }, 5666 DeleteImageManifestFn: func(repo, reference string, dc bool) error { 5667 return nil 5668 }, 5669 GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) { 5670 return []byte("{}"), "1", "1", nil 5671 }, 5672 } 5673 5674 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + 5675 "sha256-343ebab94a7674da181c6ea3da013aee4f8cbe357870f8dcaf6268d5343c3474.sig") 5676 So(resp, ShouldNotBeNil) 5677 So(err, ShouldBeNil) 5678 So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError) 5679 }) 5680 5681 Convey("image is a signature, PutImageManifest fails", func() { 5682 ctlr.StoreController.DefaultStore = mocks.MockedImageStore{ 5683 NewBlobUploadFn: ctlr.StoreController.DefaultStore.NewBlobUpload, 5684 PutBlobChunkFn: ctlr.StoreController.DefaultStore.PutBlobChunk, 5685 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 5686 configBlob, err := json.Marshal(ispec.Image{}) 5687 So(err, ShouldBeNil) 5688 5689 return configBlob, nil 5690 }, 5691 PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest, 5692 godigest.Digest, error, 5693 ) { 5694 return "", "", ErrTestError 5695 }, 5696 DeleteImageManifestFn: func(repo, reference string, dc bool) error { 5697 return nil 5698 }, 5699 GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) { 5700 return []byte("{}"), "1", "1", nil 5701 }, 5702 } 5703 5704 ctlr.MetaDB = mocks.MetaDBMock{ 5705 RemoveRepoReferenceFn: func(repo, reference string, manifestDigest godigest.Digest, 5706 ) error { 5707 return ErrTestError 5708 }, 5709 } 5710 5711 resp, err = resty.R().Delete(baseURL + "/v2/" + "repo1" + "/manifests/" + 5712 "343ebab94a7674da181c6ea3da013aee4f8cbe357870f8dcaf6268d5343c3474.sig") 5713 So(resp, ShouldNotBeNil) 5714 So(err, ShouldBeNil) 5715 So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError) 5716 }) 5717 }) 5718 }) 5719 } 5720 5721 func TestSearchSize(t *testing.T) { 5722 Convey("Repo sizes", t, func() { 5723 port := GetFreePort() 5724 baseURL := GetBaseURL(port) 5725 5726 conf := config.New() 5727 conf.HTTP.Port = port 5728 tr := true 5729 conf.Extensions = &extconf.ExtensionConfig{ 5730 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &tr}}, 5731 } 5732 5733 ctlr := api.NewController(conf) 5734 dir := t.TempDir() 5735 ctlr.Config.Storage.RootDirectory = dir 5736 5737 ctlrManager := NewControllerManager(ctlr) 5738 ctlrManager.StartAndWait(port) 5739 defer ctlrManager.StopServer() 5740 5741 repoName := "testrepo" 5742 uploadedImage := CreateImageWith().RandomLayers(1, 10000).DefaultConfig().Build() 5743 5744 configSize := uploadedImage.ConfigDescriptor.Size 5745 manifestSize := uploadedImage.ManifestDescriptor.Size 5746 layersSize := int64(0) 5747 for _, l := range uploadedImage.Layers { 5748 layersSize += int64(len(l)) 5749 } 5750 5751 err := UploadImage(uploadedImage, baseURL, repoName, "latest") 5752 So(err, ShouldBeNil) 5753 5754 query := ` 5755 { 5756 GlobalSearch(query:"testrepo:"){ 5757 Images { 5758 RepoName Tag LastUpdated Size Vendor 5759 Manifests{ 5760 Platform { Os Arch } 5761 LastUpdated Size 5762 } 5763 } 5764 Repos { 5765 Name LastUpdated Size 5766 NewestImage { 5767 Manifests{ 5768 Platform { Os Arch } 5769 LastUpdated Size 5770 } 5771 } 5772 } 5773 } 5774 }` 5775 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5776 So(err, ShouldBeNil) 5777 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5778 So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue) 5779 5780 responseStruct := &zcommon.GlobalSearchResultResp{} 5781 err = json.Unmarshal(resp.Body(), responseStruct) 5782 So(err, ShouldBeNil) 5783 5784 image := responseStruct.GlobalSearchResult.GlobalSearch.Images[0] 5785 So(image.Tag, ShouldResemble, "latest") 5786 5787 size, err := strconv.Atoi(image.Size) 5788 So(err, ShouldBeNil) 5789 So(size, ShouldEqual, configSize+layersSize+manifestSize) 5790 5791 query = ` 5792 { 5793 GlobalSearch(query:"testrepo"){ 5794 Images { 5795 RepoName Tag LastUpdated Size 5796 Manifests{ 5797 Platform { Os Arch } 5798 LastUpdated Size 5799 } 5800 } 5801 Repos { 5802 Name LastUpdated Size 5803 NewestImage { 5804 Manifests{ 5805 Platform { Os Arch } 5806 LastUpdated Size 5807 } 5808 } 5809 } 5810 } 5811 }` 5812 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5813 So(err, ShouldBeNil) 5814 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5815 So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue) 5816 5817 responseStruct = &zcommon.GlobalSearchResultResp{} 5818 err = json.Unmarshal(resp.Body(), responseStruct) 5819 So(err, ShouldBeNil) 5820 5821 repo := responseStruct.GlobalSearchResult.GlobalSearch.Repos[0] 5822 size, err = strconv.Atoi(repo.Size) 5823 So(err, ShouldBeNil) 5824 So(size, ShouldEqual, configSize+layersSize+manifestSize) 5825 5826 // add the same image with different tag 5827 err = UploadImage(uploadedImage, baseURL, repoName, "10.2.14") 5828 So(err, ShouldBeNil) 5829 5830 // query for images 5831 query = ` 5832 { 5833 GlobalSearch(query:"testrepo:"){ 5834 Images { 5835 RepoName Tag LastUpdated Size 5836 Manifests{ 5837 Platform { Os Arch } 5838 LastUpdated Size 5839 } 5840 } 5841 Repos { 5842 Name LastUpdated Size 5843 NewestImage { 5844 Manifests{ 5845 Platform { Os Arch } 5846 LastUpdated Size 5847 } 5848 } 5849 } 5850 Layers { Digest Size } 5851 } 5852 }` 5853 5854 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5855 So(err, ShouldBeNil) 5856 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5857 So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue) 5858 5859 responseStruct = &zcommon.GlobalSearchResultResp{} 5860 err = json.Unmarshal(resp.Body(), responseStruct) 5861 So(err, ShouldBeNil) 5862 5863 So(len(responseStruct.Images), ShouldEqual, 2) 5864 // check that the repo size is the same 5865 // query for repos 5866 query = ` 5867 { 5868 GlobalSearch(query:"testrepo"){ 5869 Images { 5870 RepoName Tag LastUpdated Size 5871 Manifests{ 5872 Platform { Os Arch } 5873 LastUpdated Size 5874 } 5875 } 5876 Repos { 5877 Name LastUpdated Size 5878 NewestImage { 5879 Manifests{ 5880 Platform { Os Arch } 5881 LastUpdated Size 5882 } 5883 } 5884 } 5885 Layers { Digest Size } 5886 } 5887 }` 5888 5889 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 5890 So(err, ShouldBeNil) 5891 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 5892 So(configSize+layersSize+manifestSize, ShouldNotBeZeroValue) 5893 5894 responseStruct = &zcommon.GlobalSearchResultResp{} 5895 err = json.Unmarshal(resp.Body(), responseStruct) 5896 So(err, ShouldBeNil) 5897 5898 repo = responseStruct.GlobalSearchResult.GlobalSearch.Repos[0] 5899 size, err = strconv.Atoi(repo.Size) 5900 So(err, ShouldBeNil) 5901 So(size, ShouldEqual, configSize+layersSize+manifestSize) 5902 }) 5903 } 5904 5905 func TestImageSummary(t *testing.T) { 5906 Convey("GraphQL query ImageSummary", t, func() { 5907 port := GetFreePort() 5908 baseURL := GetBaseURL(port) 5909 conf := config.New() 5910 conf.HTTP.Port = port 5911 conf.Storage.RootDirectory = t.TempDir() 5912 conf.Storage.GC = false 5913 5914 defaultVal := true 5915 conf.Extensions = &extconf.ExtensionConfig{ 5916 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 5917 } 5918 5919 conf.Extensions.Search.CVE = nil 5920 5921 ctlr := api.NewController(conf) 5922 5923 gqlQuery := ` 5924 { 5925 Image(image:"%s:%s"){ 5926 RepoName 5927 Tag 5928 Digest 5929 MediaType 5930 Manifests { 5931 Digest 5932 ConfigDigest 5933 LastUpdated 5934 Size 5935 Platform { Os Arch } 5936 Layers { Digest Size } 5937 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 5938 History { 5939 HistoryDescription { Created } 5940 Layer { Digest Size } 5941 } 5942 } 5943 LastUpdated 5944 Size 5945 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 5946 Referrers {MediaType ArtifactType Digest Annotations {Key Value}} 5947 } 5948 }` 5949 5950 noTagQuery := ` 5951 { 5952 Image(image:"%s"){ 5953 RepoName, 5954 Tag, 5955 Digest, 5956 MediaType, 5957 Manifests { 5958 Digest 5959 ConfigDigest 5960 LastUpdated 5961 Size 5962 Platform { Os Arch } 5963 Layers { Digest Size } 5964 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 5965 History { 5966 HistoryDescription { Created } 5967 Layer { Digest Size } 5968 } 5969 }, 5970 Size 5971 } 5972 }` 5973 5974 gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix) 5975 5976 ctlrManager := NewControllerManager(ctlr) 5977 ctlrManager.StartAndWait(port) 5978 defer ctlrManager.StopServer() 5979 5980 repoName := "test-repo" //nolint:goconst 5981 tagTarget := "latest" 5982 5983 createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC) 5984 5985 image := CreateImageWith().RandomLayers(1, 100).ImageConfig(ispec.Image{ 5986 History: []ispec.History{{Created: &createdTime}}, 5987 Platform: ispec.Platform{ 5988 Architecture: "amd64", 5989 OS: "linux", 5990 }, 5991 }).Build() 5992 5993 manifestDigest := image.ManifestDescriptor.Digest 5994 5995 err := UploadImage(image, baseURL, repoName, tagTarget) 5996 So(err, ShouldBeNil) 5997 5998 // ------ Add a referrer 5999 referrerImage := CreateImageWith().RandomLayers(1, 100). 6000 CustomConfigBlob( 6001 []byte{}, 6002 "application/test.artifact.type", 6003 ). 6004 Subject(&ispec.Descriptor{ 6005 Digest: manifestDigest, 6006 MediaType: ispec.MediaTypeImageManifest, 6007 }).Annotations(map[string]string{"testAnnotationKey": "testAnnotationValue"}). 6008 Build() 6009 6010 referrerManifestDigest := referrerImage.Digest() 6011 6012 err = UploadImage(referrerImage, baseURL, repoName, referrerManifestDigest.String()) 6013 So(err, ShouldBeNil) 6014 6015 var ( 6016 imgSummaryResponse zcommon.ImageSummaryResult 6017 strQuery string 6018 targetURL string 6019 resp *resty.Response 6020 ) 6021 6022 t.Log("starting test to retrieve image without reference") 6023 strQuery = fmt.Sprintf(noTagQuery, repoName) 6024 targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) 6025 contains := false 6026 6027 resp, err = resty.R().Get(targetURL) 6028 So(resp, ShouldNotBeNil) 6029 So(err, ShouldBeNil) 6030 So(resp.StatusCode(), ShouldEqual, 200) 6031 So(resp.Body(), ShouldNotBeNil) 6032 6033 err = json.Unmarshal(resp.Body(), &imgSummaryResponse) 6034 So(err, ShouldBeNil) 6035 for _, err := range imgSummaryResponse.Errors { 6036 result := strings.Contains(err.Message, "no reference provided") 6037 if result { 6038 contains = result 6039 } 6040 } 6041 So(contains, ShouldBeTrue) 6042 6043 t.Log("starting Test retrieve image based on image identifier") 6044 // gql is parametrized with the repo. 6045 strQuery = fmt.Sprintf(gqlQuery, repoName, tagTarget) 6046 targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) 6047 6048 resp, err = resty.R().Get(targetURL) 6049 So(resp, ShouldNotBeNil) 6050 So(err, ShouldBeNil) 6051 So(resp.StatusCode(), ShouldEqual, 200) 6052 So(resp.Body(), ShouldNotBeNil) 6053 6054 err = json.Unmarshal(resp.Body(), &imgSummaryResponse) 6055 So(err, ShouldBeNil) 6056 So(imgSummaryResponse, ShouldNotBeNil) 6057 So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil) 6058 So(imgSummaryResponse.ImageSummary, ShouldNotBeNil) 6059 imgSummary := imgSummaryResponse.SingleImageSummary.ImageSummary 6060 So(imgSummary.RepoName, ShouldContainSubstring, repoName) 6061 So(imgSummary.Tag, ShouldContainSubstring, tagTarget) 6062 So(imgSummary.Digest, ShouldContainSubstring, manifestDigest.Encoded()) 6063 So(imgSummary.MediaType, ShouldContainSubstring, ispec.MediaTypeImageManifest) 6064 So(imgSummary.Manifests[0].ConfigDigest, ShouldContainSubstring, image.Manifest.Config.Digest.Encoded()) 6065 So(imgSummary.Manifests[0].Digest, ShouldContainSubstring, manifestDigest.Encoded()) 6066 So(len(imgSummary.Manifests[0].Layers), ShouldEqual, 1) 6067 So(imgSummary.Manifests[0].Layers[0].Digest, ShouldContainSubstring, 6068 image.Manifest.Layers[0].Digest.Encoded()) 6069 So(imgSummary.LastUpdated, ShouldEqual, createdTime) 6070 So(imgSummary.IsSigned, ShouldEqual, false) 6071 So(imgSummary.Manifests[0].Platform.Os, ShouldEqual, "linux") 6072 So(imgSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64") 6073 So(len(imgSummary.Manifests[0].History), ShouldEqual, 1) 6074 So(imgSummary.Manifests[0].History[0].HistoryDescription.Created, ShouldEqual, createdTime) 6075 // No vulnerabilities should be detected since trivy is disabled 6076 So(imgSummary.Vulnerabilities.Count, ShouldEqual, 0) 6077 So(imgSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0) 6078 So(imgSummary.Vulnerabilities.LowCount, ShouldEqual, 0) 6079 So(imgSummary.Vulnerabilities.MediumCount, ShouldEqual, 0) 6080 So(imgSummary.Vulnerabilities.HighCount, ShouldEqual, 0) 6081 So(imgSummary.Vulnerabilities.CriticalCount, ShouldEqual, 0) 6082 So(imgSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "") 6083 So(len(imgSummary.Referrers), ShouldEqual, 1) 6084 So(imgSummary.Referrers[0], ShouldResemble, zcommon.Referrer{ 6085 MediaType: ispec.MediaTypeImageManifest, 6086 ArtifactType: "application/test.artifact.type", 6087 Digest: referrerManifestDigest.String(), 6088 Annotations: []zcommon.Annotation{{Key: "testAnnotationKey", Value: "testAnnotationValue"}}, 6089 }) 6090 6091 t.Log("starting Test retrieve duplicated image same layers based on image identifier") 6092 // gqlEndpoint 6093 strQuery = fmt.Sprintf(gqlQuery, "wrong-repo-does-not-exist", "latest") 6094 targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) 6095 6096 resp, err = resty.R().Get(targetURL) 6097 So(resp, ShouldNotBeNil) 6098 So(err, ShouldBeNil) 6099 So(resp.StatusCode(), ShouldEqual, 200) 6100 So(resp.Body(), ShouldNotBeNil) 6101 err = json.Unmarshal(resp.Body(), &imgSummaryResponse) 6102 So(err, ShouldBeNil) 6103 So(imgSummaryResponse, ShouldNotBeNil) 6104 So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil) 6105 So(imgSummaryResponse.ImageSummary, ShouldNotBeNil) 6106 6107 So(len(imgSummaryResponse.Errors), ShouldEqual, 1) 6108 So(imgSummaryResponse.Errors[0].Message, 6109 ShouldContainSubstring, "repo metadata not found for given repo name") 6110 6111 t.Log("starting Test retrieve image with bad tag") 6112 // gql is parametrized with the repo. 6113 strQuery = fmt.Sprintf(gqlQuery, repoName, "nonexisttag") 6114 targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) 6115 6116 resp, err = resty.R().Get(targetURL) 6117 So(resp, ShouldNotBeNil) 6118 So(err, ShouldBeNil) 6119 So(resp.StatusCode(), ShouldEqual, 200) 6120 So(resp.Body(), ShouldNotBeNil) 6121 err = json.Unmarshal(resp.Body(), &imgSummaryResponse) 6122 So(err, ShouldBeNil) 6123 So(imgSummaryResponse, ShouldNotBeNil) 6124 So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil) 6125 So(imgSummaryResponse.ImageSummary, ShouldNotBeNil) 6126 6127 So(len(imgSummaryResponse.Errors), ShouldEqual, 1) 6128 So(imgSummaryResponse.Errors[0].Message, 6129 ShouldContainSubstring, "can't find image: test-repo:nonexisttag") 6130 }) 6131 6132 Convey("GraphQL query ImageSummary with Vulnerability scan enabled", t, func() { 6133 port := GetFreePort() 6134 baseURL := GetBaseURL(port) 6135 conf := config.New() 6136 conf.HTTP.Port = port 6137 conf.Storage.RootDirectory = t.TempDir() 6138 6139 defaultVal := true 6140 updateDuration, _ := time.ParseDuration("1h") 6141 trivyConfig := &extconf.TrivyConfig{ 6142 DBRepository: "ghcr.io/project-zot/trivy-db", 6143 } 6144 cveConfig := &extconf.CVEConfig{ 6145 UpdateInterval: updateDuration, 6146 Trivy: trivyConfig, 6147 } 6148 searchConfig := &extconf.SearchConfig{ 6149 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 6150 CVE: cveConfig, 6151 } 6152 conf.Extensions = &extconf.ExtensionConfig{ 6153 Search: searchConfig, 6154 } 6155 6156 ctlr := api.NewController(conf) 6157 6158 gqlQuery := ` 6159 { 6160 Image(image:"%s:%s"){ 6161 RepoName 6162 Tag 6163 Manifests { 6164 Digest 6165 ConfigDigest 6166 LastUpdated 6167 Size 6168 Platform { Os Arch } 6169 Layers { Digest Size } 6170 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 6171 History { 6172 HistoryDescription { Created } 6173 Layer { Digest Size } 6174 } 6175 } 6176 LastUpdated 6177 Size 6178 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 6179 } 6180 }` 6181 6182 gqlEndpoint := fmt.Sprintf("%s%s?query=", baseURL, graphqlQueryPrefix) 6183 6184 createdTime := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC) 6185 6186 image := CreateImageWith().DefaultLayers().ImageConfig(ispec.Image{ 6187 History: []ispec.History{{Created: &createdTime}}, 6188 Platform: ispec.Platform{ 6189 Architecture: "amd64", 6190 OS: "linux", 6191 }, 6192 }).Build() 6193 6194 manifestDigest := image.ManifestDescriptor.Digest 6195 configDigest := image.ConfigDescriptor.Digest 6196 6197 if err := ctlr.Init(); err != nil { 6198 panic(err) 6199 } 6200 6201 ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) 6202 6203 go func() { 6204 if err := ctlr.Run(); !errors.Is(err, http.ErrServerClosed) { 6205 panic(err) 6206 } 6207 }() 6208 6209 defer ctlr.Shutdown() 6210 6211 WaitTillServerReady(baseURL) 6212 6213 repoName := "test-repo" //nolint:goconst 6214 tagTarget := "latest" 6215 err := UploadImage(image, baseURL, repoName, tagTarget) 6216 So(err, ShouldBeNil) 6217 var ( 6218 imgSummaryResponse zcommon.ImageSummaryResult 6219 strQuery string 6220 targetURL string 6221 resp *resty.Response 6222 ) 6223 6224 t.Log("starting Test retrieve image based on image identifier") 6225 // gql is parametrized with the repo. 6226 strQuery = fmt.Sprintf(gqlQuery, repoName, tagTarget) 6227 targetURL = fmt.Sprintf("%s%s", gqlEndpoint, url.QueryEscape(strQuery)) 6228 6229 resp, err = resty.R().Get(targetURL) 6230 So(resp, ShouldNotBeNil) 6231 So(err, ShouldBeNil) 6232 So(resp.StatusCode(), ShouldEqual, 200) 6233 So(resp.Body(), ShouldNotBeNil) 6234 6235 err = json.Unmarshal(resp.Body(), &imgSummaryResponse) 6236 So(err, ShouldBeNil) 6237 So(imgSummaryResponse, ShouldNotBeNil) 6238 So(imgSummaryResponse.SingleImageSummary, ShouldNotBeNil) 6239 So(imgSummaryResponse.ImageSummary, ShouldNotBeNil) 6240 6241 imgSummary := imgSummaryResponse.ImageSummary 6242 So(imgSummary.RepoName, ShouldContainSubstring, repoName) 6243 So(imgSummary.Tag, ShouldContainSubstring, tagTarget) 6244 So(imgSummary.Manifests[0].ConfigDigest, ShouldContainSubstring, configDigest.Encoded()) 6245 So(imgSummary.Manifests[0].Digest, ShouldContainSubstring, manifestDigest.Encoded()) 6246 So(len(imgSummary.Manifests[0].Layers), ShouldEqual, 3) 6247 So(imgSummary.Manifests[0].Layers[0].Digest, ShouldContainSubstring, 6248 godigest.FromBytes(image.Layers[0]).Encoded()) 6249 So(imgSummary.LastUpdated, ShouldEqual, createdTime) 6250 So(imgSummary.IsSigned, ShouldEqual, false) 6251 So(imgSummary.Manifests[0].Platform.Os, ShouldEqual, "linux") 6252 So(imgSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64") 6253 So(len(imgSummary.Manifests[0].History), ShouldEqual, 1) 6254 So(imgSummary.Manifests[0].History[0].HistoryDescription.Created, ShouldEqual, createdTime) 6255 So(imgSummary.Vulnerabilities.Count, ShouldEqual, 4) 6256 So(imgSummary.Vulnerabilities.UnknownCount, ShouldEqual, 0) 6257 So(imgSummary.Vulnerabilities.LowCount, ShouldEqual, 1) 6258 So(imgSummary.Vulnerabilities.MediumCount, ShouldEqual, 1) 6259 So(imgSummary.Vulnerabilities.HighCount, ShouldEqual, 1) 6260 So(imgSummary.Vulnerabilities.CriticalCount, ShouldEqual, 1) 6261 // There are 0 vulnerabilities this data used in tests 6262 So(imgSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL") 6263 }) 6264 6265 Convey("GraphQL query for Artifact Type", t, func() { 6266 port := GetFreePort() 6267 baseURL := GetBaseURL(port) 6268 conf := config.New() 6269 conf.HTTP.Port = port 6270 conf.Storage.RootDirectory = t.TempDir() 6271 conf.Storage.GC = false 6272 6273 defaultVal := true 6274 conf.Extensions = &extconf.ExtensionConfig{ 6275 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, 6276 } 6277 6278 conf.Extensions.Search.CVE = nil 6279 6280 ctlr := api.NewController(conf) 6281 6282 query := ` 6283 { 6284 Image(image:"repo:art%d"){ 6285 RepoName 6286 Tag 6287 Manifests { 6288 Digest 6289 ArtifactType 6290 } 6291 Size 6292 } 6293 }` 6294 6295 queryImg1 := fmt.Sprintf(query, 1) 6296 queryImg2 := fmt.Sprintf(query, 2) 6297 6298 var imgSummaryResponse zcommon.ImageSummaryResult 6299 6300 ctlrManager := NewControllerManager(ctlr) 6301 ctlrManager.StartAndWait(port) 6302 defer ctlrManager.StopServer() 6303 6304 // upload the images 6305 artType1 := "application/test.signature.v1" 6306 artType2 := "application/test.signature.v2" 6307 6308 img1 := CreateImageWith().RandomLayers(1, 20).EmptyConfig().ArtifactType(artType1).Build() 6309 6310 err := UploadImage(img1, baseURL, "repo", "art1") 6311 So(err, ShouldBeNil) 6312 6313 img2 := CreateImageWith().RandomLayers(1, 20).DefaultConfig().ArtifactType(artType2).Build() 6314 6315 err = UploadImage(img2, baseURL, "repo", "art2") 6316 So(err, ShouldBeNil) 6317 6318 // GET image 1 6319 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImg1)) 6320 So(resp, ShouldNotBeNil) 6321 So(err, ShouldBeNil) 6322 So(resp.StatusCode(), ShouldEqual, 200) 6323 So(resp.Body(), ShouldNotBeNil) 6324 6325 err = json.Unmarshal(resp.Body(), &imgSummaryResponse) 6326 So(err, ShouldBeNil) 6327 6328 imgSum := imgSummaryResponse.SingleImageSummary.ImageSummary 6329 So(len(imgSum.Manifests), ShouldEqual, 1) 6330 So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType1) 6331 6332 // GET image 2 6333 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryImg2)) 6334 So(resp, ShouldNotBeNil) 6335 So(err, ShouldBeNil) 6336 So(resp.StatusCode(), ShouldEqual, 200) 6337 So(resp.Body(), ShouldNotBeNil) 6338 6339 err = json.Unmarshal(resp.Body(), &imgSummaryResponse) 6340 So(err, ShouldBeNil) 6341 6342 imgSum = imgSummaryResponse.SingleImageSummary.ImageSummary 6343 So(len(imgSum.Manifests), ShouldEqual, 1) 6344 So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType2) 6345 6346 // Expanded repo info test 6347 6348 queryExpRepoInfo := `{ 6349 ExpandedRepoInfo(repo:"test1"){ 6350 Images { 6351 Tag 6352 Manifests { 6353 Digest 6354 ArtifactType 6355 } 6356 } 6357 } 6358 }` 6359 6360 var expandedRepoInfoResp zcommon.ExpandedRepoInfoResp 6361 6362 resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + 6363 url.QueryEscape(queryExpRepoInfo)) 6364 So(resp, ShouldNotBeNil) 6365 So(err, ShouldBeNil) 6366 So(resp.StatusCode(), ShouldEqual, 200) 6367 So(resp.Body(), ShouldNotBeNil) 6368 6369 err = json.Unmarshal(resp.Body(), &expandedRepoInfoResp) 6370 So(err, ShouldBeNil) 6371 6372 imgSums := expandedRepoInfoResp.ExpandedRepoInfo.RepoInfo.ImageSummaries 6373 6374 for _, imgSum := range imgSums { 6375 switch imgSum.Digest { 6376 case img1.ManifestDescriptor.Digest.String(): 6377 So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType1) 6378 case img2.ManifestDescriptor.Digest.String(): 6379 So(imgSum.Manifests[0].ArtifactType, ShouldResemble, artType2) 6380 } 6381 } 6382 }) 6383 } 6384 6385 func TestUploadingArtifactsWithDifferentMediaType(t *testing.T) { 6386 Convey("", t, func() { 6387 port := GetFreePort() 6388 baseURL := GetBaseURL(port) 6389 conf := config.New() 6390 conf.HTTP.Port = port 6391 conf.Storage.RootDirectory = t.TempDir() 6392 conf.Storage.GC = false 6393 6394 defaultVal := true 6395 conf.Extensions = &extconf.ExtensionConfig{ 6396 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, CVE: nil}, 6397 } 6398 conf.Log = &config.LogConfig{Level: "debug", Output: "/dev/null"} 6399 6400 ctlr := api.NewController(conf) 6401 6402 ctlrManager := NewControllerManager(ctlr) 6403 ctlrManager.StartAndWait(port) 6404 defer ctlrManager.StopServer() 6405 6406 const customMediaType = "application/custom.media.type+json" 6407 6408 imageWithIncompatibleConfig := CreateImageWith().DefaultLayers(). 6409 CustomConfigBlob([]byte(`{"author": {"key": "val"}}`), customMediaType).Build() 6410 6411 defaultImage := CreateDefaultImage() 6412 6413 var configContent ispec.Image 6414 err := json.Unmarshal(imageWithIncompatibleConfig.ConfigDescriptor.Data, &configContent) 6415 So(err, ShouldNotBeNil) 6416 6417 err = UploadImage(imageWithIncompatibleConfig, baseURL, "repo", "incompatible-image") 6418 So(err, ShouldBeNil) 6419 6420 err = UploadImage(defaultImage, baseURL, "repo", "default-image") 6421 So(err, ShouldBeNil) 6422 6423 query := ` 6424 { 6425 GlobalSearch(query:"repo:incompatible-image"){ 6426 Images { 6427 RepoName Tag 6428 Manifests { 6429 Digest ConfigDigest 6430 } 6431 } 6432 } 6433 }` 6434 6435 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) 6436 So(resp, ShouldNotBeNil) 6437 So(err, ShouldBeNil) 6438 So(resp.StatusCode(), ShouldEqual, 200) 6439 6440 responseStruct := &zcommon.GlobalSearchResultResp{} 6441 6442 err = json.Unmarshal(resp.Body(), responseStruct) 6443 So(err, ShouldBeNil) 6444 6445 So(len(responseStruct.Images), ShouldEqual, 1) 6446 So(responseStruct.Images[0].Manifests[0].Digest, ShouldResemble, 6447 imageWithIncompatibleConfig.ManifestDescriptor.Digest.String()) 6448 So(responseStruct.Images[0].Manifests[0].ConfigDigest, ShouldResemble, 6449 imageWithIncompatibleConfig.ConfigDescriptor.Digest.String()) 6450 }) 6451 } 6452 6453 func TestReadUploadDeleteDynamoDB(t *testing.T) { 6454 tskip.SkipDynamo(t) 6455 6456 uuid, err := guuid.NewV4() 6457 if err != nil { 6458 panic(err) 6459 } 6460 6461 cacheTablename := "BlobTable" + uuid.String() 6462 repoMetaTablename := "RepoMetadataTable" + uuid.String() 6463 versionTablename := "Version" + uuid.String() 6464 userDataTablename := "UserDataTable" + uuid.String() 6465 apiKeyTablename := "ApiKeyTable" + uuid.String() 6466 imageMetaTablename := "ImageMeta" + uuid.String() 6467 repoBlobsTablename := "RepoBlobs" + uuid.String() 6468 6469 cacheDriverParams := map[string]interface{}{ 6470 "name": "dynamoDB", 6471 "endpoint": os.Getenv("DYNAMODBMOCK_ENDPOINT"), 6472 "region": "us-east-2", 6473 "cachetablename": cacheTablename, 6474 "repometatablename": repoMetaTablename, 6475 "imagemetatablename": imageMetaTablename, 6476 "repoblobsinfotablename": repoBlobsTablename, 6477 "userdatatablename": userDataTablename, 6478 "apikeytablename": apiKeyTablename, 6479 "versiontablename": versionTablename, 6480 } 6481 6482 port := GetFreePort() 6483 baseURL := GetBaseURL(port) 6484 conf := config.New() 6485 conf.HTTP.Port = port 6486 conf.Storage.RootDirectory = t.TempDir() 6487 conf.Storage.GC = false 6488 conf.Storage.CacheDriver = cacheDriverParams 6489 conf.Storage.RemoteCache = true 6490 conf.Log = &config.LogConfig{Level: "debug", Output: "/dev/null"} 6491 6492 defaultVal := true 6493 conf.Extensions = &extconf.ExtensionConfig{ 6494 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, CVE: nil}, 6495 } 6496 6497 ctlr := api.NewController(conf) 6498 ctlrManager := NewControllerManager(ctlr) 6499 6500 ctlrManager.StartAndWait(port) 6501 defer ctlrManager.StopServer() 6502 6503 RunReadUploadDeleteTests(t, baseURL) 6504 } 6505 6506 func TestReadUploadDeleteBoltDB(t *testing.T) { 6507 port := GetFreePort() 6508 baseURL := GetBaseURL(port) 6509 conf := config.New() 6510 conf.HTTP.Port = port 6511 conf.Storage.RootDirectory = t.TempDir() 6512 conf.Storage.GC = false 6513 conf.Log = &config.LogConfig{Level: "debug", Output: "/dev/null"} 6514 6515 defaultVal := true 6516 conf.Extensions = &extconf.ExtensionConfig{ 6517 Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, CVE: nil}, 6518 } 6519 6520 ctlr := api.NewController(conf) 6521 ctlrManager := NewControllerManager(ctlr) 6522 6523 ctlrManager.StartAndWait(port) 6524 defer ctlrManager.StopServer() 6525 6526 RunReadUploadDeleteTests(t, baseURL) 6527 } 6528 6529 func RunReadUploadDeleteTests(t *testing.T, baseURL string) { 6530 t.Helper() 6531 6532 repo1 := "repo1" 6533 image := CreateRandomImage() 6534 tag1 := "tag1" 6535 6536 imageWithoutTag := CreateRandomImage() 6537 6538 usedImages := []repoRef{ 6539 {repo1, tag1}, 6540 {repo1, imageWithoutTag.DigestStr()}, 6541 } 6542 6543 Convey("Push-Read-Delete", t, func() { 6544 results := GlobalSearchGQL("", baseURL) 6545 So(len(results.Images), ShouldEqual, 0) 6546 So(len(results.Repos), ShouldEqual, 0) 6547 6548 Convey("Push an image without tag", func() { 6549 err := UploadImage(imageWithoutTag, baseURL, repo1, imageWithoutTag.DigestStr()) 6550 So(err, ShouldBeNil) 6551 6552 results := GlobalSearchGQL("", baseURL) 6553 So(len(results.Repos), ShouldEqual, 0) 6554 6555 Convey("Add tag and delete it", func() { 6556 err := UploadImage(image, baseURL, repo1, tag1) 6557 So(err, ShouldBeNil) 6558 6559 results := GlobalSearchGQL("", baseURL) 6560 So(len(results.Repos), ShouldEqual, 1) 6561 6562 status, err := DeleteImage(repo1, tag1, baseURL) 6563 So(status, ShouldEqual, http.StatusAccepted) 6564 So(err, ShouldBeNil) 6565 6566 results = GlobalSearchGQL("", baseURL) 6567 So(len(results.Repos), ShouldEqual, 0) 6568 }) 6569 }) 6570 Convey("Push a random image", func() { 6571 err := UploadImage(image, baseURL, repo1, tag1) 6572 So(err, ShouldBeNil) 6573 6574 results := GlobalSearchGQL("", baseURL) 6575 So(len(results.Repos), ShouldEqual, 1) 6576 6577 Convey("Delete the image pushed", func() { 6578 status, err := DeleteImage(repo1, tag1, baseURL) 6579 So(status, ShouldEqual, http.StatusAccepted) 6580 So(err, ShouldBeNil) 6581 6582 results := GlobalSearchGQL("", baseURL) 6583 So(len(results.Repos), ShouldEqual, 0) 6584 6585 Convey("Push an image without tag", func() { 6586 err := UploadImage(imageWithoutTag, baseURL, repo1, imageWithoutTag.DigestStr()) 6587 So(err, ShouldBeNil) 6588 6589 results := GlobalSearchGQL("", baseURL) 6590 So(len(results.Repos), ShouldEqual, 0) 6591 }) 6592 }) 6593 Convey("Delete the image pushed multiple times", func() { 6594 for i := 0; i < 3; i++ { 6595 status, err := DeleteImage(repo1, tag1, baseURL) 6596 So(status, ShouldBeIn, []int{http.StatusAccepted, http.StatusNotFound, http.StatusBadRequest}) 6597 So(err, ShouldBeNil) 6598 6599 results := GlobalSearchGQL("", baseURL) 6600 So(len(results.Repos), ShouldEqual, 0) 6601 } 6602 }) 6603 Convey("Upload same image multiple times", func() { 6604 for i := 0; i < 3; i++ { 6605 err := UploadImage(image, baseURL, repo1, tag1) 6606 So(err, ShouldBeNil) 6607 } 6608 6609 results := GlobalSearchGQL("", baseURL) 6610 So(len(results.Repos), ShouldEqual, 1) 6611 }) 6612 }) 6613 6614 deleteUsedImages(usedImages, baseURL) 6615 }) 6616 6617 // Images with create time 6618 repoLatest := "repo-latest" 6619 6620 afterImage := CreateImageWith().DefaultLayers(). 6621 ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() 6622 tagAfter := "after" 6623 6624 middleImage := CreateImageWith().DefaultLayers(). 6625 ImageConfig(ispec.Image{Created: DateRef(2005, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() 6626 tagMiddle := "middle" 6627 6628 beforeImage := CreateImageWith().DefaultLayers(). 6629 ImageConfig(ispec.Image{Created: DateRef(2000, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() 6630 tagBefore := "before" 6631 6632 imageWithoutTag = CreateImageWith().DefaultLayers(). 6633 ImageConfig(ispec.Image{Created: DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() 6634 6635 imageWithoutCreateTime := CreateImageWith().DefaultLayers(). 6636 ImageConfig(ispec.Image{Created: nil}).Build() 6637 tagWithoutTime := "without-time" 6638 6639 usedImages = []repoRef{ 6640 {repoLatest, tagAfter}, 6641 {repoLatest, tagMiddle}, 6642 {repoLatest, tagBefore}, 6643 {repoLatest, tagWithoutTime}, 6644 {repoLatest, imageWithoutTag.DigestStr()}, 6645 } 6646 6647 Convey("Last Updated Image", t, func() { 6648 results := GlobalSearchGQL("", baseURL) 6649 So(len(results.Images), ShouldEqual, 0) 6650 So(len(results.Repos), ShouldEqual, 0) 6651 6652 Convey("Without time", func() { 6653 err := UploadImage(imageWithoutCreateTime, baseURL, repoLatest, tagWithoutTime) 6654 So(err, ShouldBeNil) 6655 6656 results := GlobalSearchGQL("", baseURL) 6657 So(len(results.Repos), ShouldEqual, 1) 6658 So(results.Repos[0].NewestImage.Digest, ShouldResemble, imageWithoutCreateTime.DigestStr()) 6659 6660 Convey("Add an image with create time and delete it", func() { 6661 err := UploadImage(beforeImage, baseURL, repoLatest, tagBefore) 6662 So(err, ShouldBeNil) 6663 6664 results := GlobalSearchGQL("", baseURL) 6665 So(len(results.Repos), ShouldEqual, 1) 6666 So(results.Repos[0].NewestImage.Digest, ShouldResemble, beforeImage.DigestStr()) 6667 6668 status, err := DeleteImage(repoLatest, tagBefore, baseURL) 6669 So(status, ShouldEqual, http.StatusAccepted) 6670 So(err, ShouldBeNil) 6671 6672 results = GlobalSearchGQL("", baseURL) 6673 So(len(results.Repos), ShouldEqual, 1) 6674 So(results.Repos[0].NewestImage.Digest, ShouldResemble, imageWithoutCreateTime.DigestStr()) 6675 }) 6676 }) 6677 Convey("Upload middle image", func() { 6678 err := UploadImage(middleImage, baseURL, repoLatest, tagMiddle) 6679 So(err, ShouldBeNil) 6680 6681 results := GlobalSearchGQL("", baseURL) 6682 So(len(results.Repos), ShouldEqual, 1) 6683 So(results.Repos[0].NewestImage.Digest, ShouldResemble, middleImage.DigestStr()) 6684 6685 Convey("Upload an image created before", func() { 6686 err := UploadImage(beforeImage, baseURL, repoLatest, tagBefore) 6687 So(err, ShouldBeNil) 6688 6689 results := GlobalSearchGQL("", baseURL) 6690 So(len(results.Repos), ShouldEqual, 1) 6691 So(results.Repos[0].NewestImage.Digest, ShouldResemble, middleImage.DigestStr()) 6692 6693 Convey("Upload an image created after", func() { 6694 err := UploadImage(afterImage, baseURL, repoLatest, tagAfter) 6695 So(err, ShouldBeNil) 6696 6697 results := GlobalSearchGQL("", baseURL) 6698 So(len(results.Repos), ShouldEqual, 1) 6699 So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr()) 6700 6701 Convey("Delete middle then after", func() { 6702 status, err := DeleteImage(repoLatest, tagMiddle, baseURL) 6703 So(status, ShouldEqual, http.StatusAccepted) 6704 So(err, ShouldBeNil) 6705 6706 results := GlobalSearchGQL("", baseURL) 6707 So(len(results.Repos), ShouldEqual, 1) 6708 So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr()) 6709 6710 status, err = DeleteImage(repoLatest, tagAfter, baseURL) 6711 So(status, ShouldEqual, http.StatusAccepted) 6712 So(err, ShouldBeNil) 6713 6714 results = GlobalSearchGQL("", baseURL) 6715 So(len(results.Repos), ShouldEqual, 1) 6716 So(results.Repos[0].NewestImage.Digest, ShouldResemble, beforeImage.DigestStr()) 6717 }) 6718 }) 6719 }) 6720 Convey("Upload an image created after", func() { 6721 err := UploadImage(afterImage, baseURL, repoLatest, tagAfter) 6722 So(err, ShouldBeNil) 6723 6724 results := GlobalSearchGQL("", baseURL) 6725 So(len(results.Repos), ShouldEqual, 1) 6726 So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr()) 6727 6728 Convey("Add newer image without tag", func() { 6729 err := UploadImage(imageWithoutTag, baseURL, repoLatest, imageWithoutTag.DigestStr()) 6730 So(err, ShouldBeNil) 6731 6732 results := GlobalSearchGQL("", baseURL) 6733 So(len(results.Repos), ShouldEqual, 1) 6734 So(results.Repos[0].NewestImage.Digest, ShouldResemble, afterImage.DigestStr()) 6735 }) 6736 6737 Convey("Delete afterImage", func() { 6738 status, err := DeleteImage(repoLatest, tagAfter, baseURL) 6739 So(status, ShouldEqual, http.StatusAccepted) 6740 So(err, ShouldBeNil) 6741 6742 results := GlobalSearchGQL("", baseURL) 6743 So(len(results.Repos), ShouldEqual, 1) 6744 So(results.Repos[0].NewestImage.Digest, ShouldResemble, middleImage.DigestStr()) 6745 }) 6746 }) 6747 }) 6748 6749 deleteUsedImages(usedImages, baseURL) 6750 }) 6751 } 6752 6753 type repoRef struct { 6754 Repo string 6755 Tag string 6756 } 6757 6758 func deleteUsedImages(repoTags []repoRef, baseURL string) { 6759 for _, image := range repoTags { 6760 status, err := DeleteImage(image.Repo, image.Tag, baseURL) 6761 So(status, ShouldBeIn, []int{http.StatusAccepted, http.StatusNotFound, http.StatusBadRequest}) 6762 So(err, ShouldBeNil) 6763 } 6764 } 6765 6766 func GlobalSearchGQL(query, baseURL string) *zcommon.GlobalSearchResultResp { 6767 queryStr := ` 6768 { 6769 GlobalSearch(query:"` + query + `"){ 6770 Images { 6771 RepoName Tag Digest MediaType Size DownloadCount LastUpdated IsSigned 6772 Description Licenses Labels Title Source Documentation Authors Vendor 6773 Manifests { 6774 Digest ConfigDigest LastUpdated Size IsSigned 6775 DownloadCount 6776 SignatureInfo {Tool IsTrusted Author} 6777 Platform {Os Arch} 6778 Layers {Size Digest} 6779 History { 6780 Layer { Size Digest } 6781 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 6782 } 6783 Vulnerabilities {Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity} 6784 Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}} 6785 } 6786 Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}} 6787 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 6788 SignatureInfo {Tool IsTrusted Author} 6789 } 6790 Repos { 6791 Name LastUpdated Size DownloadCount StarCount IsBookmarked IsStarred 6792 Platforms { Os Arch } 6793 Vendors 6794 NewestImage { 6795 RepoName Tag Digest MediaType Size DownloadCount LastUpdated IsSigned 6796 Description Licenses Labels Title Source Documentation Authors Vendor 6797 Manifests { 6798 Digest ConfigDigest LastUpdated Size IsSigned 6799 DownloadCount 6800 SignatureInfo {Tool IsTrusted Author} 6801 Platform {Os Arch} 6802 Layers {Size Digest} 6803 History { 6804 Layer { Size Digest } 6805 HistoryDescription { Author Comment Created CreatedBy EmptyLayer } 6806 } 6807 Vulnerabilities {Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity} 6808 Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}} 6809 } 6810 Referrers {MediaType ArtifactType Size Digest Annotations {Key Value}} 6811 Vulnerabilities { Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity } 6812 SignatureInfo {Tool IsTrusted Author} 6813 } 6814 } 6815 } 6816 }` 6817 6818 resp, err := resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(queryStr)) 6819 So(resp, ShouldNotBeNil) 6820 So(err, ShouldBeNil) 6821 So(resp.StatusCode(), ShouldEqual, 200) 6822 6823 responseStruct := &zcommon.GlobalSearchResultResp{} 6824 6825 err = json.Unmarshal(resp.Body(), responseStruct) 6826 So(err, ShouldBeNil) 6827 6828 return responseStruct 6829 }