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