zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/resolver_test.go (about) 1 //go:build search 2 3 package search //nolint 4 5 import ( 6 "context" 7 "errors" 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/99designs/gqlgen/graphql" 13 godigest "github.com/opencontainers/go-digest" 14 ispec "github.com/opencontainers/image-spec/specs-go/v1" 15 . "github.com/smartystreets/goconvey/convey" 16 17 zerr "zotregistry.dev/zot/errors" 18 "zotregistry.dev/zot/pkg/common" 19 "zotregistry.dev/zot/pkg/extensions/search/convert" 20 cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve" 21 cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model" 22 "zotregistry.dev/zot/pkg/extensions/search/gql_generated" 23 "zotregistry.dev/zot/pkg/log" 24 "zotregistry.dev/zot/pkg/meta/boltdb" 25 mConvert "zotregistry.dev/zot/pkg/meta/convert" 26 mTypes "zotregistry.dev/zot/pkg/meta/types" 27 reqCtx "zotregistry.dev/zot/pkg/requestcontext" 28 "zotregistry.dev/zot/pkg/storage" 29 . "zotregistry.dev/zot/pkg/test/image-utils" 30 "zotregistry.dev/zot/pkg/test/mocks" 31 ociutils "zotregistry.dev/zot/pkg/test/oci-utils" 32 ) 33 34 var ErrTestError = errors.New("TestError") 35 36 func TestResolverGlobalSearch(t *testing.T) { 37 Convey("globalSearch", t, func() { 38 const query = "repo1" 39 Convey("MetaDB SearchRepos error", func() { 40 mockMetaDB := mocks.MetaDBMock{ 41 SearchReposFn: func(ctx context.Context, searchText string, 42 ) ([]mTypes.RepoMeta, error) { 43 return []mTypes.RepoMeta{}, ErrTestError 44 }, 45 } 46 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 47 graphql.DefaultRecover) 48 mockCve := mocks.CveInfoMock{} 49 repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{}, 50 &gql_generated.PageInput{}, mockCve, log.NewLogger("debug", "")) 51 So(err, ShouldNotBeNil) 52 So(images, ShouldBeEmpty) 53 So(layers, ShouldBeEmpty) 54 So(repos.Results, ShouldBeEmpty) 55 }) 56 57 Convey("paginated fail", func() { 58 pageInput := &gql_generated.PageInput{ 59 Limit: ref(-1), 60 } 61 62 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 63 graphql.DefaultRecover) 64 65 _, _, _, err := globalSearch(responseContext, "repo", mocks.MetaDBMock{}, &gql_generated.Filter{}, 66 pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 67 So(err, ShouldNotBeNil) 68 69 _, _, _, err = globalSearch(responseContext, "repo:tag", mocks.MetaDBMock{}, &gql_generated.Filter{}, 70 pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 71 So(err, ShouldNotBeNil) 72 }) 73 74 Convey("MetaDB SearchTags gives error", func() { 75 mockMetaDB := mocks.MetaDBMock{ 76 SearchTagsFn: func(ctx context.Context, searchText string) ([]mTypes.FullImageMeta, error) { 77 return []mTypes.FullImageMeta{}, ErrTestError 78 }, 79 } 80 const query = "repo1:1.0.1" 81 mockCve := mocks.CveInfoMock{} 82 83 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 84 graphql.DefaultRecover) 85 repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{}, 86 &gql_generated.PageInput{}, mockCve, log.NewLogger("debug", "")) 87 So(err, ShouldNotBeNil) 88 So(images, ShouldBeEmpty) 89 So(layers, ShouldBeEmpty) 90 So(repos.Results, ShouldBeEmpty) 91 }) 92 93 Convey("Searching by digest", func() { 94 ctx := context.Background() 95 query := "sha256:aabb12341baf2" 96 mockMetaDB := mocks.MetaDBMock{ 97 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, 98 filterFunc mTypes.FilterFunc, 99 ) ([]mTypes.FullImageMeta, error) { 100 return []mTypes.FullImageMeta{}, ErrTestError 101 }, 102 } 103 104 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover) 105 repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{}, 106 &gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 107 So(err, ShouldNotBeNil) 108 So(images, ShouldBeEmpty) 109 So(layers, ShouldBeEmpty) 110 So(repos.Results, ShouldBeEmpty) 111 }) 112 113 Convey("Searching by digest with bad pagination", func() { 114 ctx := context.Background() 115 query := "sha256:aabb12341baf2" 116 117 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover) 118 repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{}, 119 &gql_generated.PageInput{Limit: ref(-10)}, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 120 So(err, ShouldNotBeNil) 121 So(images, ShouldBeEmpty) 122 So(layers, ShouldBeEmpty) 123 So(repos.Results, ShouldBeEmpty) 124 }) 125 126 Convey("Searching by tag returns a filter error", func() { 127 ctx := context.Background() 128 query := ":test" 129 mockMetaDB := mocks.MetaDBMock{ 130 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, 131 filterFunc mTypes.FilterFunc, 132 ) ([]mTypes.FullImageMeta, error) { 133 return []mTypes.FullImageMeta{}, ErrTestError 134 }, 135 } 136 137 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover) 138 repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{}, 139 &gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 140 So(err, ShouldNotBeNil) 141 So(images, ShouldBeEmpty) 142 So(layers, ShouldBeEmpty) 143 So(repos.Results, ShouldBeEmpty) 144 }) 145 146 Convey("Searching by tag returns a pagination error", func() { 147 ctx := context.Background() 148 query := ":test" 149 150 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover) 151 repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{}, 152 &gql_generated.PageInput{Limit: ref(-10)}, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 153 So(err, ShouldNotBeNil) 154 So(images, ShouldBeEmpty) 155 So(layers, ShouldBeEmpty) 156 So(repos.Results, ShouldBeEmpty) 157 }) 158 159 Convey("Searching with a bad query", func() { 160 ctx := context.Background() 161 query := ":" 162 163 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover) 164 repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{}, 165 &gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 166 So(err, ShouldNotBeNil) 167 So(images, ShouldBeEmpty) 168 So(layers, ShouldBeEmpty) 169 So(repos.Results, ShouldBeEmpty) 170 }) 171 }) 172 } 173 174 func TestRepoListWithNewestImage(t *testing.T) { 175 Convey("RepoListWithNewestImage", t, func() { 176 Convey("MetaDB SearchRepos error", func() { 177 mockMetaDB := mocks.MetaDBMock{ 178 SearchReposFn: func(ctx context.Context, searchText string) ([]mTypes.RepoMeta, error) { 179 return []mTypes.RepoMeta{}, ErrTestError 180 }, 181 } 182 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 183 graphql.DefaultRecover) 184 mockCve := mocks.CveInfoMock{} 185 186 pageInput := gql_generated.PageInput{ 187 Limit: ref(1), 188 Offset: ref(0), 189 SortBy: ref(gql_generated.SortCriteriaUpdateTime), 190 } 191 repos, err := repoListWithNewestImage(responseContext, mockCve, log.NewLogger("debug", ""), &pageInput, mockMetaDB) 192 So(err, ShouldNotBeNil) 193 So(repos.Results, ShouldBeEmpty) 194 }) 195 196 Convey("paginated fail", func() { 197 pageInput := &gql_generated.PageInput{ 198 Limit: ref(-1), 199 } 200 201 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 202 graphql.DefaultRecover) 203 204 _, err := repoListWithNewestImage(responseContext, mocks.CveInfoMock{}, log.NewLogger("debug", ""), 205 pageInput, mocks.MetaDBMock{}) 206 So(err, ShouldNotBeNil) 207 }) 208 209 Convey("Working SearchRepo function", func() { 210 createTime := time.Now() 211 createTime2 := createTime.Add(time.Second) 212 img1 := CreateImageWith().DefaultLayers(). 213 ImageConfig(ispec.Image{ 214 Config: ispec.ImageConfig{ 215 Labels: map[string]string{}, 216 }, 217 Created: &createTime, 218 }).Build() 219 220 img2 := CreateImageWith().DefaultLayers(). 221 ImageConfig(ispec.Image{ 222 Config: ispec.ImageConfig{ 223 Labels: map[string]string{}, 224 }, 225 Created: &createTime2, 226 }).Build() 227 228 mockMetaDB := mocks.MetaDBMock{ 229 SearchReposFn: func(ctx context.Context, searchText string) ([]mTypes.RepoMeta, error) { 230 repos := []mTypes.RepoMeta{ 231 { 232 Name: "repo1", 233 Tags: map[mTypes.Tag]mTypes.Descriptor{ 234 "1.0.1": { 235 Digest: img1.DigestStr(), 236 MediaType: ispec.MediaTypeImageManifest, 237 }, 238 }, 239 Signatures: map[mTypes.ImageDigest]mTypes.ManifestSignatures{ 240 img1.DigestStr(): { 241 "cosign": []mTypes.SignatureInfo{ 242 {SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}}, 243 }, 244 }, 245 }, 246 StarCount: 100, 247 LastUpdatedImage: &mTypes.LastUpdatedImage{ 248 Descriptor: mTypes.Descriptor{ 249 Digest: img1.DigestStr(), 250 MediaType: ispec.MediaTypeImageManifest, 251 }, 252 Tag: "1.0.1", 253 LastUpdated: &createTime, 254 }, 255 }, 256 { 257 Name: "repo2", 258 Tags: map[mTypes.Tag]mTypes.Descriptor{ 259 "1.0.2": { 260 Digest: img2.DigestStr(), 261 MediaType: ispec.MediaTypeImageManifest, 262 }, 263 }, 264 Signatures: map[mTypes.ImageDigest]mTypes.ManifestSignatures{ 265 img1.DigestStr(): { 266 "cosign": []mTypes.SignatureInfo{ 267 {SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}}, 268 }, 269 }, 270 }, 271 StarCount: 100, 272 LastUpdatedImage: &mTypes.LastUpdatedImage{ 273 Descriptor: mTypes.Descriptor{ 274 Digest: img2.DigestStr(), 275 MediaType: ispec.MediaTypeImageManifest, 276 }, 277 Tag: "1.0.2", 278 LastUpdated: &createTime2, 279 }, 280 }, 281 } 282 283 return repos, nil 284 }, 285 FilterImageMetaFn: func(ctx context.Context, digests []string, 286 ) (map[string]mTypes.ImageMeta, error) { 287 return map[string]mTypes.ImageMeta{ 288 img1.DigestStr(): mConvert.GetImageManifestMeta(img1.Manifest, img1.Config, 289 img1.ManifestDescriptor.Size, img1.ManifestDescriptor.Digest), 290 img2.DigestStr(): mConvert.GetImageManifestMeta(img2.Manifest, img2.Config, 291 img2.ManifestDescriptor.Size, img2.ManifestDescriptor.Digest), 292 }, nil 293 }, 294 } 295 Convey("MetaDB missing requestedPage", func() { 296 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 297 graphql.DefaultRecover) 298 mockCve := mocks.CveInfoMock{} 299 repos, err := repoListWithNewestImage(responseContext, mockCve, log.NewLogger("debug", ""), nil, mockMetaDB) 300 So(err, ShouldBeNil) 301 So(repos.Results, ShouldNotBeEmpty) 302 }) 303 304 Convey("MetaDB SearchRepo is successful", func() { 305 pageInput := gql_generated.PageInput{ 306 Limit: ref(2), 307 Offset: ref(0), 308 SortBy: ref(gql_generated.SortCriteriaUpdateTime), 309 } 310 311 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 312 graphql.DefaultRecover) 313 314 mockCve := mocks.CveInfoMock{} 315 repos, err := repoListWithNewestImage(responseContext, mockCve, 316 log.NewLogger("debug", ""), &pageInput, mockMetaDB) 317 So(err, ShouldBeNil) 318 So(repos, ShouldNotBeEmpty) 319 So(len(repos.Results), ShouldEqual, 2) 320 So(*repos.Results[0].Name, ShouldEqual, "repo2") 321 So(*repos.Results[0].LastUpdated, ShouldEqual, createTime2) 322 }) 323 }) 324 }) 325 } 326 327 func TestGetFilteredPaginatedRepos(t *testing.T) { 328 ctx := context.Background() 329 log := log.NewLogger("debug", "") 330 331 Convey("getFilteredPaginatedRepos", t, func() { 332 metaDB := mocks.MetaDBMock{} 333 334 Convey("FilterRepos", func() { 335 metaDB.FilterReposFn = func(ctx context.Context, rankName mTypes.FilterRepoNameFunc, 336 filterFunc mTypes.FilterFullRepoFunc, 337 ) ([]mTypes.RepoMeta, error) { 338 return nil, ErrTestError 339 } 340 _, err := getFilteredPaginatedRepos(ctx, nil, func(repo string) bool { return true }, log, 341 &gql_generated.PageInput{}, metaDB) 342 So(err, ShouldNotBeNil) 343 }) 344 Convey("FilterImageMeta", func() { 345 metaDB.FilterImageMetaFn = func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 346 return nil, ErrTestError 347 } 348 _, err := getFilteredPaginatedRepos(ctx, nil, func(repo string) bool { return true }, log, 349 &gql_generated.PageInput{}, metaDB) 350 So(err, ShouldNotBeNil) 351 }) 352 Convey("PaginatedRepoMeta2RepoSummaries", func() { 353 _, err := getFilteredPaginatedRepos(ctx, nil, func(repo string) bool { return true }, log, 354 &gql_generated.PageInput{Limit: ref(-10)}, metaDB) 355 So(err, ShouldNotBeNil) 356 }) 357 }) 358 } 359 360 func TestGetBookmarkedRepos(t *testing.T) { 361 Convey("getBookmarkedRepos", t, func() { 362 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 363 graphql.DefaultRecover) 364 _, err := getBookmarkedRepos( 365 responseContext, 366 mocks.CveInfoMock{}, 367 log.NewLogger("debug", ""), 368 nil, 369 mocks.MetaDBMock{ 370 GetBookmarkedReposFn: func(ctx context.Context) ([]string, error) { 371 return []string{}, ErrTestError 372 }, 373 }, 374 ) 375 So(err, ShouldNotBeNil) 376 }) 377 } 378 379 func TestGetStarredRepos(t *testing.T) { 380 Convey("getStarredRepos", t, func() { 381 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 382 graphql.DefaultRecover) 383 _, err := getStarredRepos( 384 responseContext, 385 mocks.CveInfoMock{}, 386 log.NewLogger("debug", ""), 387 nil, 388 mocks.MetaDBMock{ 389 GetStarredReposFn: func(ctx context.Context) ([]string, error) { 390 return []string{}, ErrTestError 391 }, 392 }, 393 ) 394 So(err, ShouldNotBeNil) 395 }) 396 } 397 398 func getTestRepoMetaWithImages(repo string, images []Image) mTypes.RepoMeta { 399 tags := map[mTypes.Tag]mTypes.Descriptor{"": {}} 400 statistics := map[mTypes.Tag]mTypes.DescriptorStatistics{"": {}} 401 signatures := map[mTypes.ImageDigest]mTypes.ManifestSignatures{"": {}} 402 referrers := map[string][]mTypes.ReferrerInfo{"": {}} 403 404 for i := range images { 405 tags[images[i].DigestStr()] = mTypes.Descriptor{} 406 statistics[images[i].DigestStr()] = mTypes.DescriptorStatistics{} 407 signatures[images[i].DigestStr()] = mTypes.ManifestSignatures{} 408 referrers[images[i].DigestStr()] = []mTypes.ReferrerInfo{} 409 } 410 411 return mTypes.RepoMeta{ 412 Name: repo, 413 Tags: tags, 414 Statistics: statistics, 415 Signatures: signatures, 416 Referrers: referrers, 417 } 418 } 419 420 func TestImageListForDigest(t *testing.T) { 421 Convey("getImageList", t, func() { 422 Convey("no page requested, FilterTagsFn returns error", func() { 423 mockMetaDB := mocks.MetaDBMock{ 424 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, 425 filterFunc mTypes.FilterFunc, 426 ) ([]mTypes.FullImageMeta, error) { 427 return []mTypes.FullImageMeta{}, ErrTestError 428 }, 429 } 430 431 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 432 graphql.DefaultRecover) 433 _, err := getImageListForDigest(responseContext, "invalid", mockMetaDB, mocks.CveInfoMock{}, nil) 434 So(err, ShouldNotBeNil) 435 }) 436 437 Convey("Paginated convert fails", func() { 438 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 439 graphql.DefaultRecover) 440 _, err := getImageListForDigest(responseContext, "invalid", mocks.MetaDBMock{}, mocks.CveInfoMock{}, 441 &gql_generated.PageInput{Limit: ref(-1)}) 442 So(err, ShouldNotBeNil) 443 }) 444 445 Convey("valid imageListForDigest returned for matching manifest digest", func() { 446 img1, img2 := CreateRandomImage(), CreateRandomImage() 447 mockMetaDB := mocks.MetaDBMock{ 448 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, 449 filterFunc mTypes.FilterFunc, 450 ) ([]mTypes.FullImageMeta, error) { 451 fullImageMetaList := []mTypes.ImageMeta{img1.AsImageMeta(), img2.AsImageMeta()} 452 repoMeta := getTestRepoMetaWithImages("repo", []Image{img1, img2}) 453 tags := []string{"tag1", "tag2"} 454 455 acceptedImages := []mTypes.FullImageMeta{} 456 457 for i := range fullImageMetaList { 458 if filterFunc(repoMeta, fullImageMetaList[i]) { 459 acceptedImages = append(acceptedImages, 460 convert.GetFullImageMeta(tags[i], repoMeta, fullImageMetaList[i])) 461 462 continue 463 } 464 } 465 466 return acceptedImages, nil 467 }, 468 } 469 470 pageInput := gql_generated.PageInput{ 471 Limit: ref(1), 472 Offset: ref(0), 473 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 474 } 475 476 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 477 graphql.DefaultRecover) 478 imageSummaries, err := getImageListForDigest(responseContext, img1.DigestStr(), 479 mockMetaDB, mocks.CveInfoMock{}, &pageInput) 480 So(err, ShouldBeNil) 481 So(len(imageSummaries.Results), ShouldEqual, 1) 482 483 imageSummaries, err = getImageListForDigest(responseContext, "invalid", 484 mockMetaDB, mocks.CveInfoMock{}, &pageInput) 485 So(err, ShouldBeNil) 486 So(len(imageSummaries.Results), ShouldEqual, 0) 487 488 imageSummaries, err = getImageListForDigest(responseContext, img1.Manifest.Config.Digest.String(), 489 mockMetaDB, mocks.CveInfoMock{}, &pageInput) 490 So(err, ShouldBeNil) 491 So(len(imageSummaries.Results), ShouldEqual, 1) 492 493 imageSummaries, err = getImageListForDigest(responseContext, img1.Manifest.Layers[0].Digest.String(), 494 mockMetaDB, mocks.CveInfoMock{}, &pageInput) 495 So(err, ShouldBeNil) 496 So(len(imageSummaries.Results), ShouldEqual, 1) 497 }) 498 499 Convey("valid imageListForDigest, multiple matching tags", func() { 500 img1 := CreateRandomImage() 501 502 mockMetaDB := mocks.MetaDBMock{ 503 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, 504 filterFunc mTypes.FilterFunc, 505 ) ([]mTypes.FullImageMeta, error) { 506 fullImageMetaList := []mTypes.ImageMeta{img1.AsImageMeta()} 507 repoMeta := getTestRepoMetaWithImages("repo", []Image{img1, img1}) 508 tags := []string{"tag1", "tag2"} 509 510 acceptedImages := []mTypes.FullImageMeta{} 511 512 for i := range fullImageMetaList { 513 if filterFunc(repoMeta, fullImageMetaList[i]) { 514 acceptedImages = append(acceptedImages, 515 convert.GetFullImageMeta(tags[i], repoMeta, fullImageMetaList[i])) 516 517 continue 518 } 519 } 520 521 return acceptedImages, nil 522 }, 523 } 524 525 pageInput := gql_generated.PageInput{ 526 Limit: ref(1), 527 Offset: ref(0), 528 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 529 } 530 531 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 532 graphql.DefaultRecover) 533 534 imageSummaries, err := getImageListForDigest(responseContext, img1.DigestStr(), 535 mockMetaDB, mocks.CveInfoMock{}, &pageInput) 536 So(err, ShouldBeNil) 537 So(len(imageSummaries.Results), ShouldEqual, 1) 538 }) 539 }) 540 } 541 542 func TestGetImageSummaryError(t *testing.T) { 543 Convey("getImageSummary", t, func() { 544 metaDB := mocks.MetaDBMock{ 545 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 546 return mTypes.RepoMeta{Tags: map[mTypes.Tag]mTypes.Descriptor{"tag": {}}}, nil 547 }, 548 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 549 return nil, ErrTestError 550 }, 551 } 552 log := log.NewLogger("debug", "") 553 554 _, err := getImageSummary(context.Background(), "repo", "tag", nil, convert.SkipQGLField{}, 555 metaDB, nil, log) 556 So(err, ShouldNotBeNil) 557 }) 558 } 559 560 func TestImageListError(t *testing.T) { 561 Convey("getImageList", t, func() { 562 testLogger := log.NewLogger("debug", "/dev/null") 563 Convey("no page requested, SearchRepoFn returns error", func() { 564 mockMetaDB := mocks.MetaDBMock{ 565 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 566 ) ([]mTypes.FullImageMeta, error) { 567 return []mTypes.FullImageMeta{}, ErrTestError 568 }, 569 } 570 571 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 572 graphql.DefaultRecover) 573 574 _, err := getImageList(responseContext, "test", mockMetaDB, mocks.CveInfoMock{}, nil, testLogger) 575 So(err, ShouldNotBeNil) 576 }) 577 578 Convey("Paginated convert fails", func() { 579 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 580 graphql.DefaultRecover) 581 582 _, err := getImageList(responseContext, "test", mocks.MetaDBMock{}, mocks.CveInfoMock{}, 583 &gql_generated.PageInput{Limit: ref(-1)}, log.NewLogger("debug", "")) 584 585 So(err, ShouldNotBeNil) 586 }) 587 588 Convey("valid repoList returned", func() { 589 mockMetaDB := mocks.MetaDBMock{ 590 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 591 ) ([]mTypes.FullImageMeta, error) { 592 repoName := "correct-repo" 593 594 if !filterRepoTag(repoName, "tag") { 595 return []mTypes.FullImageMeta{}, nil 596 } 597 598 image := CreateDefaultImage() 599 repoMeta := mTypes.RepoMeta{ 600 Name: "repo", 601 Tags: map[mTypes.Tag]mTypes.Descriptor{image.DigestStr(): { 602 Digest: image.DigestStr(), 603 MediaType: ispec.MediaTypeImageManifest, 604 }}, 605 } 606 607 return []mTypes.FullImageMeta{convert.GetFullImageMeta("tag", repoMeta, image.AsImageMeta())}, nil 608 }, 609 } 610 611 pageInput := gql_generated.PageInput{ 612 Limit: ref(1), 613 Offset: ref(0), 614 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 615 } 616 617 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 618 graphql.DefaultRecover) 619 620 imageSummaries, err := getImageList(responseContext, "correct-repo", mockMetaDB, 621 mocks.CveInfoMock{}, &pageInput, testLogger) 622 So(err, ShouldBeNil) 623 So(len(imageSummaries.Results), ShouldEqual, 1) 624 625 imageSummaries, err = getImageList(responseContext, "invalid", mockMetaDB, 626 mocks.CveInfoMock{}, &pageInput, testLogger) 627 So(err, ShouldBeNil) 628 So(len(imageSummaries.Results), ShouldEqual, 0) 629 }) 630 }) 631 } 632 633 func TestGetReferrers(t *testing.T) { 634 Convey("getReferrers", t, func() { 635 referredDigest := godigest.FromString("t").String() 636 637 Convey("referredDigest is empty", func() { 638 testLogger := log.NewLogger("debug", "") 639 640 _, err := getReferrers(mocks.MetaDBMock{}, "test", "", nil, testLogger) 641 So(err, ShouldNotBeNil) 642 }) 643 644 Convey("GetReferrers returns error", func() { 645 testLogger := log.NewLogger("debug", "") 646 mockedStore := mocks.MetaDBMock{ 647 GetReferrersInfoFn: func(repo string, referredDigest godigest.Digest, artifactTypes []string, 648 ) ([]mTypes.ReferrerInfo, error) { 649 return nil, ErrTestError 650 }, 651 } 652 653 _, err := getReferrers(mockedStore, "test", referredDigest, nil, testLogger) 654 So(err, ShouldNotBeNil) 655 }) 656 657 Convey("GetReferrers return index of descriptors", func() { 658 testLogger := log.NewLogger("debug", "") 659 referrerDescriptor := ispec.Descriptor{ 660 MediaType: ispec.MediaTypeImageManifest, 661 ArtifactType: "com.artifact.test", 662 Size: 403, 663 Digest: godigest.FromString("test"), 664 Annotations: map[string]string{ 665 "key": "value", 666 }, 667 } 668 mockedStore := mocks.MetaDBMock{ 669 GetReferrersInfoFn: func(repo string, referredDigest godigest.Digest, artifactTypes []string, 670 ) ([]mTypes.ReferrerInfo, error) { 671 return []mTypes.ReferrerInfo{ 672 { 673 Digest: referrerDescriptor.Digest.String(), 674 MediaType: referrerDescriptor.MediaType, 675 ArtifactType: referrerDescriptor.ArtifactType, 676 Size: int(referrerDescriptor.Size), 677 Annotations: referrerDescriptor.Annotations, 678 }, 679 }, nil 680 }, 681 } 682 683 referrers, err := getReferrers(mockedStore, "test", referredDigest, nil, testLogger) 684 So(err, ShouldBeNil) 685 So(*referrers[0].ArtifactType, ShouldEqual, referrerDescriptor.ArtifactType) 686 So(*referrers[0].MediaType, ShouldEqual, referrerDescriptor.MediaType) 687 So(*referrers[0].Size, ShouldEqual, referrerDescriptor.Size) 688 So(*referrers[0].Digest, ShouldEqual, referrerDescriptor.Digest.String()) 689 So(*referrers[0].Annotations[0].Value, ShouldEqual, referrerDescriptor.Annotations["key"]) 690 }) 691 }) 692 } 693 694 func TestQueryResolverErrors(t *testing.T) { 695 Convey("Errors", t, func() { 696 log := log.NewLogger("debug", "") 697 ctx := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 698 graphql.DefaultRecover) 699 700 Convey("GlobalSearch error bad requested page", func() { 701 resolverConfig := NewResolver(log, storage.StoreController{}, mocks.MetaDBMock{}, mocks.CveInfoMock{}) 702 resolver := queryResolver{resolverConfig} 703 pageInput := gql_generated.PageInput{ 704 Limit: ref(-1), 705 Offset: ref(0), 706 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 707 } 708 709 _, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput) 710 So(err, ShouldNotBeNil) 711 712 pageInput = gql_generated.PageInput{ 713 Limit: ref(0), 714 Offset: ref(-1), 715 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 716 } 717 718 _, err = resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput) 719 So(err, ShouldNotBeNil) 720 }) 721 722 Convey("GlobalSearch error filte image meta", func() { 723 resolverConfig := NewResolver(log, storage.StoreController{}, mocks.MetaDBMock{ 724 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 725 return nil, ErrTestError 726 }, 727 }, mocks.CveInfoMock{}) 728 resolver := queryResolver{resolverConfig} 729 730 _, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, getGQLPageInput(1, 1)) 731 So(err, ShouldNotBeNil) 732 }) 733 734 Convey("CVEDiffListForImages nill cveinfo", func() { 735 resolverConfig := NewResolver( 736 log, 737 storage.StoreController{ 738 DefaultStore: mocks.MockedImageStore{}, 739 }, 740 mocks.MetaDBMock{ 741 GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool, 742 ) ([]mTypes.RepoMeta, error) { 743 return []mTypes.RepoMeta{}, ErrTestError 744 }, 745 }, 746 nil, 747 ) 748 749 qr := queryResolver{resolverConfig} 750 751 _, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{}, 752 &gql_generated.PageInput{}, nil, nil) 753 So(err, ShouldNotBeNil) 754 }) 755 756 Convey("CVEDiffListForImages error", func() { 757 resolverConfig := NewResolver( 758 log, 759 storage.StoreController{ 760 DefaultStore: mocks.MockedImageStore{}, 761 }, 762 mocks.MetaDBMock{ 763 GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool, 764 ) ([]mTypes.RepoMeta, error) { 765 return []mTypes.RepoMeta{}, ErrTestError 766 }, 767 }, 768 mocks.CveInfoMock{}, 769 ) 770 771 qr := queryResolver{resolverConfig} 772 773 _, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{}, 774 &gql_generated.PageInput{}, nil, nil) 775 So(err, ShouldNotBeNil) 776 }) 777 778 Convey("ImageListForCve error in GetMultipleRepoMeta", func() { 779 resolverConfig := NewResolver( 780 log, 781 storage.StoreController{ 782 DefaultStore: mocks.MockedImageStore{}, 783 }, 784 mocks.MetaDBMock{ 785 GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool, 786 ) ([]mTypes.RepoMeta, error) { 787 return []mTypes.RepoMeta{}, ErrTestError 788 }, 789 }, 790 mocks.CveInfoMock{}, 791 ) 792 793 qr := queryResolver{resolverConfig} 794 795 _, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.Filter{}, &gql_generated.PageInput{}) 796 So(err, ShouldNotBeNil) 797 }) 798 799 Convey("ImageListForCve error in FilterTags", func() { 800 resolverConfig := NewResolver( 801 log, 802 storage.StoreController{ 803 DefaultStore: mocks.MockedImageStore{}, 804 }, 805 mocks.MetaDBMock{ 806 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 807 ) ([]mTypes.FullImageMeta, error) { 808 return []mTypes.FullImageMeta{}, ErrTestError 809 }, 810 }, 811 mocks.CveInfoMock{}, 812 ) 813 814 qr := queryResolver{resolverConfig} 815 816 _, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.Filter{}, &gql_generated.PageInput{}) 817 So(err, ShouldNotBeNil) 818 }) 819 820 Convey("ImageListWithCVEFixed error in FilterTags", func() { 821 resolverConfig := NewResolver( 822 log, 823 storage.StoreController{ 824 DefaultStore: mocks.MockedImageStore{}, 825 }, 826 mocks.MetaDBMock{ 827 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 828 ) ([]mTypes.FullImageMeta, error) { 829 return []mTypes.FullImageMeta{}, ErrTestError 830 }, 831 }, 832 mocks.CveInfoMock{}, 833 ) 834 835 qr := queryResolver{resolverConfig} 836 837 _, err := qr.ImageListWithCVEFixed(ctx, "cve1", "image", &gql_generated.Filter{}, &gql_generated.PageInput{}) 838 So(err, ShouldNotBeNil) 839 }) 840 841 Convey("RepoListWithNewestImage repoListWithNewestImage() filter image meta error", func() { 842 resolverConfig := NewResolver( 843 log, 844 storage.StoreController{ 845 DefaultStore: mocks.MockedImageStore{}, 846 }, 847 mocks.MetaDBMock{ 848 SearchReposFn: func(ctx context.Context, searchText string, 849 ) ([]mTypes.RepoMeta, error) { 850 return []mTypes.RepoMeta{}, nil 851 }, 852 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 853 return nil, ErrTestError 854 }, 855 }, 856 mocks.CveInfoMock{}, 857 ) 858 859 qr := queryResolver{resolverConfig} 860 861 _, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{}) 862 So(err, ShouldNotBeNil) 863 }) 864 865 Convey("RepoListWithNewestImage repoListWithNewestImage() errors mocked StoreController", func() { 866 resolverConfig := NewResolver( 867 log, 868 storage.StoreController{ 869 DefaultStore: mocks.MockedImageStore{}, 870 }, 871 mocks.MetaDBMock{ 872 SearchReposFn: func(ctx context.Context, searchText string, 873 ) ([]mTypes.RepoMeta, error) { 874 return nil, ErrTestError 875 }, 876 }, 877 mocks.CveInfoMock{}, 878 ) 879 880 qr := queryResolver{resolverConfig} 881 882 _, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{}) 883 So(err, ShouldNotBeNil) 884 }) 885 886 Convey("RepoListWithNewestImage repoListWithNewestImage() errors valid StoreController", func() { 887 resolverConfig := NewResolver( 888 log, 889 storage.StoreController{}, 890 mocks.MetaDBMock{ 891 SearchReposFn: func(ctx context.Context, searchText string, 892 ) ([]mTypes.RepoMeta, error) { 893 return nil, ErrTestError 894 }, 895 }, 896 mocks.CveInfoMock{}, 897 ) 898 899 qr := queryResolver{resolverConfig} 900 901 _, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{}) 902 So(err, ShouldNotBeNil) 903 }) 904 905 Convey("ImageList getImageList() errors", func() { 906 resolverConfig := NewResolver( 907 log, 908 storage.StoreController{}, 909 mocks.MetaDBMock{ 910 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 911 ) ([]mTypes.FullImageMeta, error) { 912 return []mTypes.FullImageMeta{}, ErrTestError 913 }, 914 }, 915 mocks.CveInfoMock{}, 916 ) 917 918 qr := queryResolver{resolverConfig} 919 920 _, err := qr.ImageList(ctx, "repo", &gql_generated.PageInput{}) 921 So(err, ShouldNotBeNil) 922 }) 923 924 Convey("DerivedImageList ExpandedRepoInfo() errors", func() { 925 resolverConfig := NewResolver( 926 log, 927 storage.StoreController{ 928 DefaultStore: mocks.MockedImageStore{ 929 GetRepositoriesFn: func() ([]string, error) { 930 return []string{"sub1/repo"}, nil 931 }, 932 GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) { 933 return []byte("{}"), "digest", "str", nil 934 }, 935 }, 936 }, 937 mocks.MetaDBMock{ 938 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 939 return mTypes.RepoMeta{}, ErrTestError 940 }, 941 }, 942 mocks.CveInfoMock{}, 943 ) 944 945 qr := queryResolver{resolverConfig} 946 947 _, err := qr.DerivedImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{}) 948 So(err, ShouldNotBeNil) 949 }) 950 951 Convey("BaseImageList ExpandedRepoInfo() errors", func() { 952 resolverConfig := NewResolver( 953 log, 954 storage.StoreController{ 955 DefaultStore: mocks.MockedImageStore{ 956 GetRepositoriesFn: func() ([]string, error) { 957 return []string{"sub1/repo"}, nil 958 }, 959 GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) { 960 return []byte("{}"), "digest", "str", nil 961 }, 962 }, 963 }, 964 mocks.MetaDBMock{ 965 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 966 return mTypes.RepoMeta{}, ErrTestError 967 }, 968 }, 969 mocks.CveInfoMock{}, 970 ) 971 972 qr := queryResolver{resolverConfig} 973 974 _, err := qr.BaseImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{}) 975 So(err, ShouldNotBeNil) 976 }) 977 978 Convey("DerivedImageList and BaseImage List FilterTags() errors", func() { 979 image := CreateDefaultImage() 980 981 resolverConfig := NewResolver( 982 log, 983 storage.StoreController{}, 984 mocks.MetaDBMock{ 985 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 986 ) ([]mTypes.FullImageMeta, error) { 987 return []mTypes.FullImageMeta{}, ErrTestError 988 }, 989 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 990 return mTypes.RepoMeta{ 991 Name: "repo", 992 Tags: map[mTypes.Tag]mTypes.Descriptor{ 993 "tag": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 994 }, 995 }, nil 996 }, 997 GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) { 998 return image.AsImageMeta(), nil 999 }, 1000 }, 1001 mocks.CveInfoMock{}, 1002 ) 1003 1004 resolver := queryResolver{resolverConfig} 1005 1006 _, err := resolver.DerivedImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{}) 1007 So(err, ShouldNotBeNil) 1008 1009 _, err = resolver.BaseImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{}) 1010 So(err, ShouldNotBeNil) 1011 }) 1012 1013 Convey("GetReferrers error", func() { 1014 resolverConfig := NewResolver( 1015 log, 1016 storage.StoreController{ 1017 DefaultStore: mocks.MockedImageStore{ 1018 GetReferrersFn: func(repo string, digest godigest.Digest, artifactTypes []string) (ispec.Index, error) { 1019 return ispec.Index{}, ErrTestError 1020 }, 1021 }, 1022 }, 1023 mocks.MetaDBMock{}, 1024 mocks.CveInfoMock{}, 1025 ) 1026 1027 qr := queryResolver{resolverConfig} 1028 1029 _, err := qr.Referrers(ctx, "repo", "", nil) 1030 So(err, ShouldNotBeNil) 1031 }) 1032 }) 1033 } 1034 1035 func TestCVEResolvers(t *testing.T) { //nolint:gocyclo 1036 ctx := context.Background() 1037 log := log.NewLogger("debug", "") 1038 LINUX := "linux" 1039 AMD := "amd" 1040 ARM := "arm64" 1041 1042 boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: t.TempDir()}) 1043 if err != nil { 1044 panic(err) 1045 } 1046 1047 metaDB, err := boltdb.New(boltDriver, log) 1048 if err != nil { 1049 panic(err) 1050 } 1051 1052 image1 := CreateImageWith().RandomLayers(5, 2).ImageConfig(ispec.Image{ 1053 Created: DateRef(2008, 1, 1, 12, 0, 0, 0, time.UTC), 1054 Platform: ispec.Platform{ 1055 Architecture: AMD, 1056 OS: LINUX, 1057 }, 1058 }).Build() 1059 digest1 := image1.Digest() 1060 1061 image2 := CreateImageWith().RandomLayers(5, 2).ImageConfig(ispec.Image{ 1062 Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC), 1063 Platform: ispec.Platform{ 1064 Architecture: AMD, 1065 OS: LINUX, 1066 }, 1067 }).Build() 1068 digest2 := image2.Digest() 1069 1070 image3 := CreateImageWith().RandomLayers(5, 2).ImageConfig(ispec.Image{ 1071 Created: DateRef(2010, 1, 1, 12, 0, 0, 0, time.UTC), 1072 Platform: ispec.Platform{ 1073 Architecture: ARM, 1074 OS: LINUX, 1075 }, 1076 }).Build() 1077 digest3 := image3.Digest() 1078 1079 ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, 1080 ociutils.Repo{ 1081 Name: "repo1", Images: []ociutils.RepoImage{ 1082 {Image: image1, Reference: "1.0.0"}, 1083 {Image: image2, Reference: "1.0.1"}, 1084 {Image: image3, Reference: "1.1.0"}, 1085 {Image: image3, Reference: "latest"}, 1086 }, 1087 }, 1088 ociutils.Repo{ 1089 Name: "repo2", Images: []ociutils.RepoImage{ 1090 {Image: image1, Reference: "2.0.0"}, 1091 {Image: image2, Reference: "2.0.1"}, 1092 {Image: image3, Reference: "2.1.0"}, 1093 {Image: image3, Reference: "latest"}, 1094 }, 1095 }, 1096 ociutils.Repo{ 1097 Name: "repo3", Images: []ociutils.RepoImage{ 1098 {Image: image2, Reference: "3.0.1"}, 1099 {Image: image3, Reference: "3.1.0"}, 1100 {Image: image3, Reference: "latest"}, 1101 }, 1102 }, 1103 ) 1104 if err != nil { 1105 panic(err) 1106 } 1107 1108 getCveResults := func(digestStr string) map[string]cvemodel.CVE { 1109 if digestStr == digest1.String() { 1110 return map[string]cvemodel.CVE{ 1111 "CVE1": { 1112 ID: "CVE1", 1113 Severity: "HIGH", 1114 Title: "Title CVE1", 1115 Description: "Description CVE1", 1116 }, 1117 "CVE2": { 1118 ID: "CVE2", 1119 Severity: "MEDIUM", 1120 Title: "Title CVE2", 1121 Description: "Description CVE2", 1122 }, 1123 "CVE3": { 1124 ID: "CVE3", 1125 Severity: "LOW", 1126 Title: "Title CVE3", 1127 Description: "Description CVE3", 1128 }, 1129 "CVE34": { 1130 ID: "CVE34", 1131 Severity: "LOW", 1132 Title: "Title for CVE34", 1133 Description: "Description CVE34", 1134 }, 1135 } 1136 } 1137 1138 if digestStr == digest2.String() { 1139 return map[string]cvemodel.CVE{ 1140 "CVE2": { 1141 ID: "CVE2", 1142 Severity: "MEDIUM", 1143 Title: "Title CVE2", 1144 Description: "Description CVE2", 1145 }, 1146 "CVE3": { 1147 ID: "CVE3", 1148 Severity: "LOW", 1149 Title: "Title CVE3", 1150 Description: "Description CVE3", 1151 }, 1152 } 1153 } 1154 1155 if digestStr == digest3.String() { 1156 return map[string]cvemodel.CVE{ 1157 "CVE3": { 1158 ID: "CVE3", 1159 Severity: "LOW", 1160 Title: "Title CVE3", 1161 Description: "Description CVE3", 1162 }, 1163 } 1164 } 1165 1166 // By default the image has no vulnerabilities 1167 return map[string]cvemodel.CVE{} 1168 } 1169 1170 // MetaDB loaded with initial data, now mock the scanner 1171 // Setup test CVE data in mock scanner 1172 scanner := mocks.CveScannerMock{ 1173 ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { 1174 repo, ref, _, _ := common.GetRepoReference(image) 1175 1176 if common.IsDigest(ref) { 1177 return getCveResults(ref), nil 1178 } 1179 1180 repoMeta, _ := metaDB.GetRepoMeta(ctx, repo) 1181 1182 if _, ok := repoMeta.Tags[ref]; !ok { 1183 panic("unexpected tag '" + ref + "', test might be wrong") 1184 } 1185 1186 return getCveResults(repoMeta.Tags[ref].Digest), nil 1187 }, 1188 GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE { 1189 return getCveResults(digestStr) 1190 }, 1191 IsResultCachedFn: func(digestStr string) bool { 1192 return true 1193 }, 1194 } 1195 1196 cveInfo := &cveinfo.BaseCveInfo{ 1197 Log: log, 1198 Scanner: scanner, 1199 MetaDB: metaDB, 1200 } 1201 1202 Convey("Get CVE list for image ", t, func() { 1203 Convey("Unpaginated request to get all CVEs in an image", func() { 1204 pageInput := &gql_generated.PageInput{ 1205 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 1206 } 1207 1208 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1209 graphql.DefaultRecover) 1210 1211 dig := godigest.FromString("dig") 1212 repoWithDigestRef := fmt.Sprintf("repo@%s", dig) 1213 1214 _, err := getCVEListForImage(responseContext, repoWithDigestRef, cveInfo, pageInput, "", "", "", log) 1215 So(err, ShouldBeNil) 1216 1217 cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "", "", "", log) 1218 So(err, ShouldBeNil) 1219 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1220 1221 expectedCves := []string{"CVE1", "CVE2", "CVE3", "CVE34"} 1222 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1223 1224 for _, cve := range cveResult.CVEList { 1225 So(expectedCves, ShouldContain, *cve.ID) 1226 } 1227 1228 // test searching CVE by id in results 1229 cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE3", "", "", log) 1230 So(err, ShouldBeNil) 1231 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1232 1233 expectedCves = []string{"CVE3", "CVE34"} 1234 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1235 1236 for _, cve := range cveResult.CVEList { 1237 So(expectedCves, ShouldContain, *cve.ID) 1238 } 1239 1240 // test searching CVE by id in results - no matches 1241 cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE100", "", "", log) 1242 So(err, ShouldBeNil) 1243 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1244 So(len(cveResult.CVEList), ShouldEqual, 0) 1245 1246 // test searching CVE by id in results - partial name 1247 cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "VE3", "", "", log) 1248 So(err, ShouldBeNil) 1249 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1250 1251 expectedCves = []string{"CVE3", "CVE34"} 1252 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1253 1254 for _, cve := range cveResult.CVEList { 1255 So(expectedCves, ShouldContain, *cve.ID) 1256 } 1257 1258 // test searching CVE by title in results 1259 cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Title CVE", "", "", log) 1260 So(err, ShouldBeNil) 1261 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1262 1263 expectedCves = []string{"CVE1", "CVE2", "CVE3"} 1264 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1265 1266 for _, cve := range cveResult.CVEList { 1267 So(expectedCves, ShouldContain, *cve.ID) 1268 } 1269 1270 cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, pageInput, "", "", "", log) 1271 So(err, ShouldBeNil) 1272 So(*cveResult.Tag, ShouldEqual, "1.0.1") 1273 1274 expectedCves = []string{"CVE2", "CVE3"} 1275 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1276 1277 for _, cve := range cveResult.CVEList { 1278 So(expectedCves, ShouldContain, *cve.ID) 1279 } 1280 1281 cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, "", "", "", log) 1282 So(err, ShouldBeNil) 1283 So(*cveResult.Tag, ShouldEqual, "1.1.0") 1284 1285 expectedCves = []string{"CVE3"} 1286 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1287 1288 for _, cve := range cveResult.CVEList { 1289 So(expectedCves, ShouldContain, *cve.ID) 1290 } 1291 }) 1292 1293 Convey("Unpaginated request to get all CVEs in an image excluding some", func() { 1294 pageInput := &gql_generated.PageInput{ 1295 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 1296 } 1297 1298 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1299 graphql.DefaultRecover) 1300 1301 cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Title CVE", 1302 "Title CVE2", "", log) 1303 So(err, ShouldBeNil) 1304 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1305 1306 expectedCves := []string{"CVE1", "CVE3"} 1307 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1308 1309 for _, cve := range cveResult.CVEList { 1310 So(expectedCves, ShouldContain, *cve.ID) 1311 } 1312 1313 cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Description", 1314 "Description CVE2", "", log) 1315 So(err, ShouldBeNil) 1316 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1317 1318 expectedCves = []string{"CVE1", "CVE3", "CVE34"} 1319 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1320 1321 for _, cve := range cveResult.CVEList { 1322 So(expectedCves, ShouldContain, *cve.ID) 1323 } 1324 }) 1325 1326 Convey("Unpaginated request to get all CVEs in an image filtered by severity", func() { 1327 pageInput := &gql_generated.PageInput{ 1328 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 1329 } 1330 1331 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1332 graphql.DefaultRecover) 1333 1334 cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "", 1335 "", "HIGH", log) 1336 So(err, ShouldBeNil) 1337 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1338 1339 expectedCves := []string{"CVE1"} 1340 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1341 1342 for _, cve := range cveResult.CVEList { 1343 So(expectedCves, ShouldContain, *cve.ID) 1344 } 1345 1346 cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Description", 1347 "Description CVE2", "LOW", log) 1348 So(err, ShouldBeNil) 1349 So(*cveResult.Tag, ShouldEqual, "1.0.0") 1350 1351 expectedCves = []string{"CVE3", "CVE34"} 1352 So(len(cveResult.CVEList), ShouldEqual, len(expectedCves)) 1353 1354 for _, cve := range cveResult.CVEList { 1355 So(expectedCves, ShouldContain, *cve.ID) 1356 } 1357 }) 1358 1359 Convey("paginated fail", func() { 1360 pageInput := &gql_generated.PageInput{ 1361 Limit: ref(-1), 1362 } 1363 1364 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1365 graphql.DefaultRecover) 1366 1367 _, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, "", "", "", log) 1368 So(err, ShouldNotBeNil) 1369 }) 1370 }) 1371 1372 Convey("Get a list of images affected by a particular CVE ", t, func() { 1373 Convey("Unpaginated request", func() { 1374 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1375 graphql.DefaultRecover) 1376 1377 images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, nil, metaDB, log) 1378 So(err, ShouldBeNil) 1379 1380 expectedImages := []string{ 1381 "repo1:1.0.0", 1382 "repo2:2.0.0", 1383 } 1384 So(len(images.Results), ShouldEqual, len(expectedImages)) 1385 1386 for _, image := range images.Results { 1387 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1388 } 1389 1390 images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, nil, metaDB, log) 1391 So(err, ShouldBeNil) 1392 1393 expectedImages = []string{ 1394 "repo1:1.0.0", "repo1:1.0.1", 1395 "repo2:2.0.0", "repo2:2.0.1", 1396 "repo3:3.0.1", 1397 } 1398 So(len(images.Results), ShouldEqual, len(expectedImages)) 1399 1400 for _, image := range images.Results { 1401 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1402 } 1403 1404 images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, nil, metaDB, log) 1405 So(err, ShouldBeNil) 1406 1407 expectedImages = []string{ 1408 "repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest", 1409 "repo2:2.0.0", "repo2:2.0.1", "repo2:2.1.0", "repo2:latest", 1410 "repo3:3.0.1", "repo3:3.1.0", "repo3:latest", 1411 } 1412 So(len(images.Results), ShouldEqual, len(expectedImages)) 1413 1414 for _, image := range images.Results { 1415 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1416 } 1417 }) 1418 1419 Convey("paginated fail", func() { 1420 pageInput := &gql_generated.PageInput{ 1421 Limit: ref(-1), 1422 } 1423 1424 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1425 graphql.DefaultRecover) 1426 1427 _, err = getImageListForCVE(responseContext, "repo1:1.1.0", cveInfo, &gql_generated.Filter{}, 1428 pageInput, mocks.MetaDBMock{}, log) 1429 So(err, ShouldNotBeNil) 1430 }) 1431 1432 Convey("context done", func() { 1433 pageInput := getGQLPageInput(1, 0) 1434 1435 ctx, cancel := context.WithCancel(ctx) 1436 cancel() 1437 1438 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1439 graphql.DefaultRecover) 1440 1441 canceledScanner := scanner 1442 1443 canceledScanner.ScanImageFn = func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { 1444 return nil, ctx.Err() 1445 } 1446 1447 cveInfo.Scanner = canceledScanner 1448 1449 defer func() { 1450 cveInfo.Scanner = scanner 1451 }() 1452 1453 _, err = getImageListForCVE(responseContext, "repo1:1.1.0", cveInfo, &gql_generated.Filter{}, 1454 pageInput, metaDB, log) 1455 So(err, ShouldEqual, ctx.Err()) 1456 }) 1457 1458 Convey("Paginated requests", func() { 1459 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1460 graphql.DefaultRecover, 1461 ) 1462 1463 pageInput := getGQLPageInput(1, 0) 1464 1465 images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1466 So(err, ShouldBeNil) 1467 1468 expectedImages := []string{ 1469 "repo1:1.0.0", 1470 } 1471 So(len(images.Results), ShouldEqual, len(expectedImages)) 1472 1473 for _, image := range images.Results { 1474 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1475 } 1476 1477 pageInput = getGQLPageInput(1, 1) 1478 1479 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1480 So(err, ShouldBeNil) 1481 1482 expectedImages = []string{ 1483 "repo2:2.0.0", 1484 } 1485 So(len(images.Results), ShouldEqual, len(expectedImages)) 1486 1487 for _, image := range images.Results { 1488 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1489 } 1490 1491 pageInput = getGQLPageInput(1, 2) 1492 1493 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1494 So(err, ShouldBeNil) 1495 So(len(images.Results), ShouldEqual, 0) 1496 1497 pageInput = getGQLPageInput(1, 5) 1498 1499 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1500 So(err, ShouldBeNil) 1501 So(len(images.Results), ShouldEqual, 0) 1502 1503 pageInput = getGQLPageInput(2, 0) 1504 1505 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1506 So(err, ShouldBeNil) 1507 1508 expectedImages = []string{ 1509 "repo1:1.0.0", 1510 "repo2:2.0.0", 1511 } 1512 So(len(images.Results), ShouldEqual, len(expectedImages)) 1513 1514 for _, image := range images.Results { 1515 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1516 } 1517 1518 pageInput = getGQLPageInput(5, 0) 1519 1520 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1521 So(err, ShouldBeNil) 1522 1523 expectedImages = []string{ 1524 "repo1:1.0.0", 1525 "repo2:2.0.0", 1526 } 1527 So(len(images.Results), ShouldEqual, len(expectedImages)) 1528 1529 for _, image := range images.Results { 1530 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1531 } 1532 1533 pageInput = getGQLPageInput(5, 1) 1534 1535 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1536 So(err, ShouldBeNil) 1537 1538 expectedImages = []string{ 1539 "repo2:2.0.0", 1540 } 1541 So(len(images.Results), ShouldEqual, len(expectedImages)) 1542 1543 for _, image := range images.Results { 1544 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1545 } 1546 1547 pageInput = getGQLPageInput(5, 2) 1548 1549 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1550 So(err, ShouldBeNil) 1551 So(len(images.Results), ShouldEqual, 0) 1552 1553 pageInput = getGQLPageInput(5, 5) 1554 1555 images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log) 1556 So(err, ShouldBeNil) 1557 So(len(images.Results), ShouldEqual, 0) 1558 1559 pageInput = getGQLPageInput(5, 0) 1560 1561 images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log) 1562 So(err, ShouldBeNil) 1563 1564 expectedImages = []string{ 1565 "repo1:1.0.0", "repo1:1.0.1", 1566 "repo2:2.0.0", "repo2:2.0.1", 1567 "repo3:3.0.1", 1568 } 1569 So(len(images.Results), ShouldEqual, len(expectedImages)) 1570 1571 for _, image := range images.Results { 1572 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1573 } 1574 1575 pageInput = getGQLPageInput(5, 3) 1576 1577 images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log) 1578 So(err, ShouldBeNil) 1579 1580 expectedImages = []string{ 1581 "repo2:2.0.1", 1582 "repo3:3.0.1", 1583 } 1584 So(len(images.Results), ShouldEqual, len(expectedImages)) 1585 1586 for _, image := range images.Results { 1587 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1588 } 1589 1590 pageInput = getGQLPageInput(5, 0) 1591 1592 images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log) 1593 So(err, ShouldBeNil) 1594 1595 expectedImages = []string{ 1596 "repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest", 1597 "repo2:2.0.0", 1598 } 1599 So(len(images.Results), ShouldEqual, len(expectedImages)) 1600 1601 for _, image := range images.Results { 1602 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1603 } 1604 1605 pageInput = getGQLPageInput(5, 5) 1606 1607 images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log) 1608 So(err, ShouldBeNil) 1609 1610 expectedImages = []string{ 1611 "repo2:2.0.1", "repo2:2.1.0", "repo2:latest", 1612 "repo3:3.0.1", "repo3:3.1.0", 1613 } 1614 So(len(images.Results), ShouldEqual, len(expectedImages)) 1615 1616 for _, image := range images.Results { 1617 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1618 } 1619 1620 pageInput = getGQLPageInput(5, 10) 1621 1622 images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log) 1623 So(err, ShouldBeNil) 1624 1625 expectedImages = []string{ 1626 "repo3:latest", 1627 } 1628 So(len(images.Results), ShouldEqual, len(expectedImages)) 1629 1630 for _, image := range images.Results { 1631 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1632 } 1633 1634 amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}} 1635 pageInput = getGQLPageInput(5, 0) 1636 1637 images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log) 1638 So(err, ShouldBeNil) 1639 1640 expectedImages = []string{ 1641 "repo1:1.0.0", "repo1:1.0.1", 1642 "repo2:2.0.0", "repo2:2.0.1", 1643 "repo3:3.0.1", 1644 } 1645 So(len(images.Results), ShouldEqual, len(expectedImages)) 1646 1647 for _, image := range images.Results { 1648 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1649 } 1650 1651 pageInput = getGQLPageInput(2, 2) 1652 1653 images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log) 1654 So(err, ShouldBeNil) 1655 1656 expectedImages = []string{ 1657 "repo2:2.0.0", "repo2:2.0.1", 1658 } 1659 So(len(images.Results), ShouldEqual, len(expectedImages)) 1660 1661 for _, image := range images.Results { 1662 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1663 } 1664 }) 1665 }) 1666 1667 Convey("Get a list of images where a particular CVE is fixed", t, func() { 1668 Convey("Unpaginated request", func() { 1669 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1670 graphql.DefaultRecover) 1671 1672 images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, nil, metaDB, log) 1673 So(err, ShouldBeNil) 1674 1675 expectedImages := []string{ 1676 "repo1:1.0.1", "repo1:1.1.0", "repo1:latest", 1677 } 1678 So(len(images.Results), ShouldEqual, len(expectedImages)) 1679 1680 for _, image := range images.Results { 1681 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1682 } 1683 1684 images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, nil, metaDB, log) 1685 So(err, ShouldBeNil) 1686 1687 expectedImages = []string{ 1688 "repo1:1.1.0", "repo1:latest", 1689 } 1690 So(len(images.Results), ShouldEqual, len(expectedImages)) 1691 1692 for _, image := range images.Results { 1693 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1694 } 1695 1696 images, err = getImageListWithCVEFixed(responseContext, "CVE3", "repo1", cveInfo, nil, nil, metaDB, log) 1697 So(err, ShouldBeNil) 1698 So(len(images.Results), ShouldEqual, 0) 1699 }) 1700 1701 Convey("paginated fail", func() { 1702 pageInput := &gql_generated.PageInput{ 1703 Limit: ref(-1), 1704 } 1705 1706 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1707 graphql.DefaultRecover) 1708 1709 _, err = getImageListWithCVEFixed(responseContext, "cve", "repo1:1.1.0", cveInfo, &gql_generated.Filter{}, 1710 pageInput, mocks.MetaDBMock{ 1711 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 1712 return mTypes.RepoMeta{ 1713 Tags: map[mTypes.Tag]mTypes.Descriptor{ 1714 "1.1.0": { 1715 Digest: godigest.FromString("str").String(), 1716 MediaType: ispec.MediaTypeImageManifest, 1717 }, 1718 }, 1719 }, nil 1720 }, 1721 }, log) 1722 So(err, ShouldNotBeNil) 1723 }) 1724 1725 Convey("context done", func() { 1726 ctx, cancel := context.WithCancel(ctx) 1727 cancel() 1728 1729 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1730 graphql.DefaultRecover) 1731 1732 _, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, nil, metaDB, log) 1733 So(err, ShouldNotBeNil) 1734 }) 1735 1736 Convey("Paginated requests", func() { 1737 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 1738 graphql.DefaultRecover, 1739 ) 1740 1741 pageInput := getGQLPageInput(1, 0) 1742 1743 images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1744 So(err, ShouldBeNil) 1745 1746 expectedImages := []string{ 1747 "repo1:1.0.1", 1748 } 1749 So(len(images.Results), ShouldEqual, len(expectedImages)) 1750 1751 for _, image := range images.Results { 1752 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1753 } 1754 1755 pageInput = getGQLPageInput(1, 1) 1756 1757 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1758 So(err, ShouldBeNil) 1759 1760 expectedImages = []string{ 1761 "repo1:1.1.0", 1762 } 1763 So(len(images.Results), ShouldEqual, len(expectedImages)) 1764 1765 for _, image := range images.Results { 1766 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1767 } 1768 1769 pageInput = getGQLPageInput(1, 2) 1770 1771 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1772 So(err, ShouldBeNil) 1773 1774 expectedImages = []string{ 1775 "repo1:latest", 1776 } 1777 So(len(images.Results), ShouldEqual, len(expectedImages)) 1778 1779 for _, image := range images.Results { 1780 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1781 } 1782 1783 pageInput = getGQLPageInput(1, 3) 1784 1785 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1786 So(err, ShouldBeNil) 1787 So(len(images.Results), ShouldEqual, 0) 1788 1789 pageInput = getGQLPageInput(1, 10) 1790 1791 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1792 So(err, ShouldBeNil) 1793 So(len(images.Results), ShouldEqual, 0) 1794 1795 pageInput = getGQLPageInput(2, 0) 1796 1797 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1798 So(err, ShouldBeNil) 1799 1800 expectedImages = []string{ 1801 "repo1:1.0.1", "repo1:1.1.0", 1802 } 1803 So(len(images.Results), ShouldEqual, len(expectedImages)) 1804 1805 for _, image := range images.Results { 1806 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1807 } 1808 1809 pageInput = getGQLPageInput(2, 1) 1810 1811 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1812 So(err, ShouldBeNil) 1813 1814 expectedImages = []string{ 1815 "repo1:1.1.0", "repo1:latest", 1816 } 1817 So(len(images.Results), ShouldEqual, len(expectedImages)) 1818 1819 for _, image := range images.Results { 1820 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1821 } 1822 1823 pageInput = getGQLPageInput(2, 2) 1824 1825 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1826 So(err, ShouldBeNil) 1827 1828 expectedImages = []string{ 1829 "repo1:latest", 1830 } 1831 So(len(images.Results), ShouldEqual, len(expectedImages)) 1832 1833 for _, image := range images.Results { 1834 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1835 } 1836 1837 pageInput = getGQLPageInput(5, 0) 1838 1839 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log) 1840 So(err, ShouldBeNil) 1841 1842 expectedImages = []string{ 1843 "repo1:1.0.1", "repo1:1.1.0", "repo1:latest", 1844 } 1845 So(len(images.Results), ShouldEqual, len(expectedImages)) 1846 1847 for _, image := range images.Results { 1848 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1849 } 1850 1851 pageInput = getGQLPageInput(5, 0) 1852 1853 images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log) 1854 So(err, ShouldBeNil) 1855 1856 expectedImages = []string{ 1857 "repo1:1.1.0", "repo1:latest", 1858 } 1859 So(len(images.Results), ShouldEqual, len(expectedImages)) 1860 1861 for _, image := range images.Results { 1862 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1863 } 1864 1865 pageInput = getGQLPageInput(5, 2) 1866 1867 images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log) 1868 So(err, ShouldBeNil) 1869 So(len(images.Results), ShouldEqual, 0) 1870 1871 amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}} 1872 armFilter := &gql_generated.Filter{Arch: []*string{&ARM}} 1873 1874 pageInput = getGQLPageInput(3, 0) 1875 1876 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, amdFilter, pageInput, metaDB, log) 1877 So(err, ShouldBeNil) 1878 1879 expectedImages = []string{"repo1:1.0.1"} 1880 So(len(images.Results), ShouldEqual, len(expectedImages)) 1881 1882 for _, image := range images.Results { 1883 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1884 } 1885 1886 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, armFilter, pageInput, metaDB, log) 1887 So(err, ShouldBeNil) 1888 1889 expectedImages = []string{"repo1:1.1.0", "repo1:latest"} 1890 So(len(images.Results), ShouldEqual, len(expectedImages)) 1891 1892 for _, image := range images.Results { 1893 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1894 } 1895 1896 pageInput = getGQLPageInput(1, 1) 1897 1898 images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, armFilter, pageInput, metaDB, log) 1899 So(err, ShouldBeNil) 1900 1901 expectedImages = []string{"repo1:latest"} 1902 So(len(images.Results), ShouldEqual, len(expectedImages)) 1903 1904 for _, image := range images.Results { 1905 So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages) 1906 } 1907 }) 1908 }) 1909 1910 Convey("Errors for cve resolvers", t, func() { 1911 _, err := getImageListForCVE( 1912 ctx, 1913 "id", 1914 mocks.CveInfoMock{ 1915 GetImageListForCVEFn: func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) { 1916 return []cvemodel.TagInfo{}, ErrTestError 1917 }, 1918 }, 1919 nil, 1920 nil, 1921 mocks.MetaDBMock{ 1922 GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool, 1923 ) ([]mTypes.RepoMeta, error) { 1924 return []mTypes.RepoMeta{{}}, nil 1925 }, 1926 }, 1927 log, 1928 ) 1929 So(err, ShouldNotBeNil) 1930 }) 1931 1932 Convey("CVE Diff between images", t, func() { 1933 // image := "image:tag" 1934 // baseImage := "base:basetag" 1935 ctx := context.Background() 1936 pageInput := &gql_generated.PageInput{ 1937 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 1938 } 1939 1940 boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: t.TempDir()}) 1941 if err != nil { 1942 panic(err) 1943 } 1944 1945 metaDB, err := boltdb.New(boltDriver, log) 1946 if err != nil { 1947 panic(err) 1948 } 1949 1950 layer1 := []byte{10, 20, 30} 1951 layer2 := []byte{11, 21, 31} 1952 layer3 := []byte{12, 22, 23} 1953 1954 otherImage := CreateImageWith().LayerBlobs([][]byte{ 1955 layer1, 1956 }).DefaultConfig().Build() 1957 1958 baseImage := CreateImageWith().LayerBlobs([][]byte{ 1959 layer1, 1960 layer2, 1961 }).PlatformConfig("testArch", "testOs").Build() 1962 1963 image := CreateImageWith().LayerBlobs([][]byte{ 1964 layer1, 1965 layer2, 1966 layer3, 1967 }).PlatformConfig("testArch", "testOs").Build() 1968 1969 multiArchBase := CreateMultiarchWith().Images([]Image{baseImage, CreateRandomImage(), CreateRandomImage()}). 1970 Build() 1971 multiArchImage := CreateMultiarchWith().Images([]Image{image, CreateRandomImage(), CreateRandomImage()}). 1972 Build() 1973 1974 getCveResults := func(digestStr string) map[string]cvemodel.CVE { 1975 switch digestStr { 1976 case image.DigestStr(): 1977 return map[string]cvemodel.CVE{ 1978 "CVE1": { 1979 ID: "CVE1", 1980 Severity: "HIGH", 1981 Title: "Title CVE1", 1982 Description: "Description CVE1", 1983 PackageList: []cvemodel.Package{{}}, 1984 }, 1985 "CVE2": { 1986 ID: "CVE2", 1987 Severity: "MEDIUM", 1988 Title: "Title CVE2", 1989 Description: "Description CVE2", 1990 PackageList: []cvemodel.Package{{}}, 1991 }, 1992 "CVE3": { 1993 ID: "CVE3", 1994 Severity: "LOW", 1995 Title: "Title CVE3", 1996 Description: "Description CVE3", 1997 PackageList: []cvemodel.Package{{}}, 1998 }, 1999 } 2000 case baseImage.DigestStr(): 2001 return map[string]cvemodel.CVE{ 2002 "CVE1": { 2003 ID: "CVE1", 2004 Severity: "HIGH", 2005 Title: "Title CVE1", 2006 Description: "Description CVE1", 2007 PackageList: []cvemodel.Package{{}}, 2008 }, 2009 "CVE2": { 2010 ID: "CVE2", 2011 Severity: "MEDIUM", 2012 Title: "Title CVE2", 2013 Description: "Description CVE2", 2014 PackageList: []cvemodel.Package{{}}, 2015 }, 2016 } 2017 case otherImage.DigestStr(): 2018 return map[string]cvemodel.CVE{ 2019 "CVE1": { 2020 ID: "CVE1", 2021 Severity: "HIGH", 2022 Title: "Title CVE1", 2023 Description: "Description CVE1", 2024 PackageList: []cvemodel.Package{{}}, 2025 }, 2026 } 2027 } 2028 2029 // By default the image has no vulnerabilities 2030 return map[string]cvemodel.CVE{} 2031 } 2032 2033 // MetaDB loaded with initial data, now mock the scanner 2034 // Setup test CVE data in mock scanner 2035 scanner := mocks.CveScannerMock{ 2036 ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { 2037 repo, ref, _, _ := common.GetRepoReference(image) 2038 2039 if common.IsDigest(ref) { 2040 return getCveResults(ref), nil 2041 } 2042 2043 repoMeta, _ := metaDB.GetRepoMeta(ctx, repo) 2044 2045 if _, ok := repoMeta.Tags[ref]; !ok { 2046 panic("unexpected tag '" + ref + "', test might be wrong") 2047 } 2048 2049 return getCveResults(repoMeta.Tags[ref].Digest), nil 2050 }, 2051 GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE { 2052 return getCveResults(digestStr) 2053 }, 2054 IsResultCachedFn: func(digestStr string) bool { 2055 return true 2056 }, 2057 } 2058 2059 cveInfo := &cveinfo.BaseCveInfo{ 2060 Log: log, 2061 Scanner: scanner, 2062 MetaDB: metaDB, 2063 } 2064 2065 ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, 2066 ociutils.Repo{ 2067 Name: "repo", 2068 Images: []ociutils.RepoImage{ 2069 {Image: otherImage, Reference: "other-image"}, 2070 {Image: baseImage, Reference: "base-image"}, 2071 {Image: image, Reference: "image"}, 2072 }, 2073 }, 2074 ociutils.Repo{ 2075 Name: "repo-multi", 2076 MultiArchImages: []ociutils.RepoMultiArchImage{ 2077 {MultiarchImage: CreateRandomMultiarch(), Reference: "multi-rand"}, 2078 {MultiarchImage: multiArchBase, Reference: "multi-base"}, 2079 {MultiarchImage: multiArchImage, Reference: "multi-img"}, 2080 }, 2081 }, 2082 ) 2083 So(err, ShouldBeNil) 2084 2085 minuend := gql_generated.ImageInput{Repo: "repo", Tag: "image"} 2086 subtrahend := gql_generated.ImageInput{Repo: "repo", Tag: "image"} 2087 diffResult, err := getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) 2088 So(err, ShouldBeNil) 2089 So(len(diffResult.CVEList), ShouldEqual, 0) 2090 2091 minuend = gql_generated.ImageInput{Repo: "repo", Tag: "image"} 2092 subtrahend = gql_generated.ImageInput{} 2093 diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) 2094 So(err, ShouldBeNil) 2095 So(len(diffResult.CVEList), ShouldEqual, 1) 2096 2097 minuend = gql_generated.ImageInput{Repo: "repo", Tag: "base-image"} 2098 subtrahend = gql_generated.ImageInput{Repo: "repo", Tag: "image"} 2099 diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) 2100 So(err, ShouldBeNil) 2101 So(len(diffResult.CVEList), ShouldEqual, 0) 2102 2103 minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{}} 2104 subtrahend = gql_generated.ImageInput{} 2105 _, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) 2106 So(err, ShouldNotBeNil) 2107 2108 minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{ 2109 Os: ref("testOs"), 2110 Arch: ref("testArch"), 2111 }} 2112 subtrahend = gql_generated.ImageInput{} 2113 diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) 2114 So(err, ShouldBeNil) 2115 So(len(diffResult.CVEList), ShouldEqual, 1) 2116 So(diffResult.Subtrahend.Repo, ShouldEqual, "repo-multi") 2117 So(diffResult.Subtrahend.Tag, ShouldEqual, "multi-base") 2118 So(dderef(diffResult.Subtrahend.Platform.Os), ShouldResemble, "testOs") 2119 So(dderef(diffResult.Subtrahend.Platform.Arch), ShouldResemble, "testArch") 2120 2121 minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{ 2122 Os: ref("testOs"), 2123 Arch: ref("testArch"), 2124 }} 2125 subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{ 2126 Os: ref("testOs"), 2127 Arch: ref("testArch"), 2128 }} 2129 diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) 2130 So(err, ShouldBeNil) 2131 So(len(diffResult.CVEList), ShouldEqual, 1) 2132 2133 minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{ 2134 Os: ref("testOs"), 2135 Arch: ref("testArch"), 2136 }} 2137 subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{}} 2138 _, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) 2139 So(err, ShouldNotBeNil) 2140 }) 2141 2142 Convey("CVE Diff Errors", t, func() { 2143 ctx := context.Background() 2144 metaDB := mocks.MetaDBMock{} 2145 cveInfo := mocks.CveInfoMock{} 2146 emptyImage := gql_generated.ImageInput{} 2147 2148 Convey("minuend is empty", func() { 2149 _, err := getCVEDiffListForImages(ctx, emptyImage, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) 2150 So(err, ShouldNotBeNil) 2151 }) 2152 2153 Convey("no ", func() { 2154 minuend := gql_generated.ImageInput{Repo: "repo", Tag: "bad-tag"} 2155 _, err := getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) 2156 So(err, ShouldNotBeNil) 2157 }) 2158 2159 Convey("getImageSummary for subtrahend errors", func() { 2160 metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2161 return mTypes.RepoMeta{}, ErrTestError 2162 } 2163 minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"} 2164 _, err := getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) 2165 So(err, ShouldNotBeNil) 2166 2167 metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2168 return mTypes.RepoMeta{}, zerr.ErrRepoMetaNotFound 2169 } 2170 minuend = gql_generated.ImageInput{Repo: "test", Tag: "img"} 2171 _, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) 2172 So(err, ShouldNotBeNil) 2173 }) 2174 2175 Convey("FilterTags for subtrahend errors", func() { 2176 metaDB.FilterTagsFn = func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2177 ) ([]mTypes.FullImageMeta, error) { 2178 return nil, ErrTestError 2179 } 2180 minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"} 2181 _, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) 2182 So(err, ShouldNotBeNil) 2183 }) 2184 2185 Convey("GetCVEDiffListForImages errors", func() { 2186 cveInfo.GetCVEDiffListForImagesFn = func(ctx context.Context, minuend, subtrahend, searchedCVE, excluded string, 2187 pageInput cvemodel.PageInput, 2188 ) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) { 2189 return nil, cvemodel.ImageCVESummary{}, common.PageInfo{}, ErrTestError 2190 } 2191 minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"} 2192 subtrahend := gql_generated.ImageInput{Repo: "sub", Tag: "img"} 2193 _, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) 2194 So(err, ShouldNotBeNil) 2195 }) 2196 }) 2197 } 2198 2199 func TestMockedDerivedImageList(t *testing.T) { 2200 Convey("MetaDB FilterTags error", t, func() { 2201 log := log.NewLogger("debug", "/dev/null") 2202 2203 image := CreateRandomImage() 2204 mockMetaDB := mocks.MetaDBMock{ 2205 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2206 ) ([]mTypes.FullImageMeta, error) { 2207 return []mTypes.FullImageMeta{}, ErrTestError 2208 }, 2209 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2210 return mTypes.RepoMeta{}, ErrTestError 2211 }, 2212 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2213 return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil 2214 }, 2215 } 2216 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2217 graphql.DefaultRecover) 2218 2219 mockCve := mocks.CveInfoMock{} 2220 images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &gql_generated.PageInput{}, 2221 mockCve, log) 2222 So(err, ShouldNotBeNil) 2223 So(images.Results, ShouldBeEmpty) 2224 }) 2225 2226 Convey("paginated fail", t, func() { 2227 log := log.NewLogger("debug", "/dev/null") 2228 image := CreateRandomImage() 2229 pageInput := &gql_generated.PageInput{ 2230 Limit: ref(-1), 2231 } 2232 2233 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2234 graphql.DefaultRecover) 2235 2236 _, err := derivedImageList(responseContext, "repo1:1.0.1", nil, 2237 mocks.MetaDBMock{ 2238 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2239 return mTypes.RepoMeta{ 2240 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2241 "1.0.1": { 2242 Digest: image.DigestStr(), 2243 MediaType: ispec.MediaTypeImageManifest, 2244 }, 2245 }, 2246 }, nil 2247 }, 2248 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2249 return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil 2250 }, 2251 }, 2252 pageInput, 2253 mocks.CveInfoMock{}, log) 2254 So(err, ShouldNotBeNil) 2255 }) 2256 2257 //nolint: dupl 2258 Convey("MetaDB FilterTags no repo available", t, func() { 2259 log := log.NewLogger("debug", "/dev/null") 2260 image := CreateDefaultImage() 2261 2262 mockMetaDB := mocks.MetaDBMock{ 2263 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2264 ) ([]mTypes.FullImageMeta, error) { 2265 return []mTypes.FullImageMeta{}, nil 2266 }, 2267 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2268 return mTypes.RepoMeta{ 2269 Name: "repo1", 2270 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2271 "1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2272 }, 2273 }, nil 2274 }, 2275 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2276 return map[string]mTypes.ImageMeta{ 2277 digests[0]: image.AsImageMeta(), 2278 }, nil 2279 }, 2280 GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) { 2281 return image.AsImageMeta(), nil 2282 }, 2283 } 2284 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2285 graphql.DefaultRecover) 2286 2287 mockCve := mocks.CveInfoMock{} 2288 images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &gql_generated.PageInput{}, 2289 mockCve, log) 2290 So(err, ShouldBeNil) 2291 So(images.Results, ShouldBeEmpty) 2292 }) 2293 2294 //nolint: dupl 2295 Convey("derived image list working", t, func() { 2296 log := log.NewLogger("debug", "/dev/null") 2297 layer1 := []byte{10, 11, 10, 11} 2298 layer2 := []byte{11, 11, 11, 11} 2299 layer3 := []byte{10, 10, 10, 11} 2300 layer4 := []byte{13, 14, 15, 11} 2301 2302 image := CreateImageWith(). 2303 LayerBlobs([][]byte{ 2304 layer1, 2305 layer2, 2306 layer3, 2307 }).DefaultConfig().Build() 2308 2309 derivedImage := CreateImageWith(). 2310 LayerBlobs([][]byte{ 2311 layer1, 2312 layer2, 2313 layer3, 2314 layer4, 2315 }).DefaultConfig().Build() 2316 2317 imageMetaMap := map[string]mTypes.ImageMeta{ 2318 image.DigestStr(): image.AsImageMeta(), 2319 derivedImage.DigestStr(): derivedImage.AsImageMeta(), 2320 } 2321 2322 mockMetaDB := mocks.MetaDBMock{ 2323 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2324 return mTypes.RepoMeta{ 2325 Name: "repo1", 2326 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2327 "1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2328 }, 2329 }, nil 2330 }, 2331 GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) { 2332 return imageMetaMap[digest.String()], nil 2333 }, 2334 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2335 result := map[string]mTypes.ImageMeta{} 2336 2337 for _, digest := range digests { 2338 result[digest] = imageMetaMap[digest] 2339 } 2340 2341 return result, nil 2342 }, 2343 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2344 ) ([]mTypes.FullImageMeta, error) { 2345 fullImageMetaList := []mTypes.FullImageMeta{} 2346 repos := []mTypes.RepoMeta{{ 2347 Name: "repo1", 2348 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2349 "1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2350 "1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2351 "1.0.3": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2352 }, 2353 }} 2354 2355 for _, repo := range repos { 2356 for tag, descriptor := range repo.Tags { 2357 if filterFunc(repo, imageMetaMap[descriptor.Digest]) { 2358 fullImageMetaList = append(fullImageMetaList, 2359 convert.GetFullImageMeta(tag, repo, imageMetaMap[descriptor.Digest])) 2360 } 2361 } 2362 } 2363 2364 return fullImageMetaList, nil 2365 }, 2366 } 2367 2368 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2369 graphql.DefaultRecover) 2370 2371 mockCve := mocks.CveInfoMock{} 2372 2373 Convey("valid derivedImageList, results not affected by pageInput", func() { 2374 images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &gql_generated.PageInput{}, 2375 mockCve, log) 2376 So(err, ShouldBeNil) 2377 So(images.Results, ShouldNotBeEmpty) 2378 So(len(images.Results), ShouldEqual, 2) 2379 }) 2380 2381 Convey("valid derivedImageList, results affected by pageInput", func() { 2382 pageInput := gql_generated.PageInput{ 2383 Limit: ref(1), 2384 Offset: ref(0), 2385 SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), 2386 } 2387 2388 images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockMetaDB, &pageInput, 2389 mockCve, log) 2390 So(err, ShouldBeNil) 2391 So(images.Results, ShouldNotBeEmpty) 2392 So(len(images.Results), ShouldEqual, 1) 2393 }) 2394 }) 2395 } 2396 2397 func TestMockedBaseImageList(t *testing.T) { 2398 Convey("MetaDB FilterTags error", t, func() { 2399 mockMetaDB := mocks.MetaDBMock{ 2400 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2401 ) ([]mTypes.FullImageMeta, error) { 2402 return []mTypes.FullImageMeta{}, ErrTestError 2403 }, 2404 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2405 return mTypes.RepoMeta{}, ErrTestError 2406 }, 2407 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2408 return map[string]mTypes.ImageMeta{}, ErrTestError 2409 }, 2410 } 2411 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2412 graphql.DefaultRecover) 2413 2414 mockCve := mocks.CveInfoMock{} 2415 images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, &gql_generated.PageInput{}, 2416 mockCve, log.NewLogger("debug", "")) 2417 So(err, ShouldNotBeNil) 2418 So(images.Results, ShouldBeEmpty) 2419 }) 2420 2421 Convey("paginated fail", t, func() { 2422 image := CreateDefaultImage() 2423 pageInput := &gql_generated.PageInput{Limit: ref(-1)} 2424 2425 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2426 graphql.DefaultRecover) 2427 _, err := baseImageList(responseContext, "repo1:1.0.2", nil, 2428 mocks.MetaDBMock{ 2429 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2430 return mTypes.RepoMeta{ 2431 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2432 "1.0.2": { 2433 Digest: image.DigestStr(), 2434 MediaType: ispec.MediaTypeImageManifest, 2435 }, 2436 }, 2437 }, nil 2438 }, 2439 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2440 return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil 2441 }, 2442 }, 2443 pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", "")) 2444 So(err, ShouldNotBeNil) 2445 }) 2446 2447 //nolint: dupl 2448 Convey("MetaDB FilterTags no repo available", t, func() { 2449 image := CreateDefaultImage() 2450 2451 mockMetaDB := mocks.MetaDBMock{ 2452 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2453 ) ([]mTypes.FullImageMeta, error) { 2454 return []mTypes.FullImageMeta{}, nil 2455 }, 2456 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2457 return mTypes.RepoMeta{ 2458 Name: "repo1", 2459 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2460 "1.0.2": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2461 }, 2462 }, nil 2463 }, 2464 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2465 return map[string]mTypes.ImageMeta{image.DigestStr(): image.AsImageMeta()}, nil 2466 }, 2467 } 2468 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2469 graphql.DefaultRecover) 2470 2471 mockCve := mocks.CveInfoMock{} 2472 images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, &gql_generated.PageInput{}, 2473 mockCve, log.NewLogger("debug", "")) 2474 So(err, ShouldBeNil) 2475 So(images.Results, ShouldBeEmpty) 2476 }) 2477 2478 //nolint: dupl 2479 Convey("base image list working", t, func() { 2480 layer1 := []byte{10, 11, 10, 11} 2481 layer2 := []byte{11, 11, 11, 11} 2482 layer3 := []byte{10, 10, 10, 11} 2483 layer4 := []byte{13, 14, 15, 11} 2484 2485 image := CreateImageWith(). 2486 LayerBlobs([][]byte{ 2487 layer1, 2488 layer2, 2489 layer3, 2490 }).DefaultConfig().Build() 2491 2492 derivedImage := CreateImageWith(). 2493 LayerBlobs([][]byte{ 2494 layer1, 2495 layer2, 2496 layer3, 2497 layer4, 2498 }).DefaultConfig().Build() 2499 2500 imageMetaMap := map[string]mTypes.ImageMeta{ 2501 image.DigestStr(): image.AsImageMeta(), 2502 derivedImage.DigestStr(): derivedImage.AsImageMeta(), 2503 } 2504 2505 mockMetaDB := mocks.MetaDBMock{ 2506 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2507 return mTypes.RepoMeta{ 2508 Name: "repo1", 2509 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2510 "1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2511 }, 2512 }, nil 2513 }, 2514 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2515 return imageMetaMap, nil 2516 }, 2517 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2518 ) ([]mTypes.FullImageMeta, error) { 2519 fullImageMetaList := []mTypes.FullImageMeta{} 2520 repos := []mTypes.RepoMeta{{ 2521 Name: "repo1", 2522 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2523 "1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2524 "1.0.3": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2525 "1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2526 }, 2527 }} 2528 2529 for _, repo := range repos { 2530 for tag, descriptor := range repo.Tags { 2531 if filterFunc(repo, imageMetaMap[descriptor.Digest]) { 2532 fullImageMetaList = append(fullImageMetaList, 2533 convert.GetFullImageMeta(tag, repo, imageMetaMap[descriptor.Digest])) 2534 } 2535 } 2536 } 2537 2538 return fullImageMetaList, nil 2539 }, 2540 } 2541 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2542 graphql.DefaultRecover) 2543 2544 mockCve := mocks.CveInfoMock{} 2545 2546 Convey("valid baseImageList, results not affected by pageInput", func() { 2547 images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, 2548 &gql_generated.PageInput{}, mockCve, log.NewLogger("debug", "")) 2549 So(err, ShouldBeNil) 2550 So(images.Results, ShouldNotBeEmpty) 2551 So(len(images.Results), ShouldEqual, 2) 2552 expectedTags := []string{"1.0.1", "1.0.3"} 2553 So(expectedTags, ShouldContain, *images.Results[0].Tag) 2554 So(expectedTags, ShouldContain, *images.Results[1].Tag) 2555 }) 2556 2557 Convey("valid baseImageList, results affected by pageInput", func() { 2558 limit := 1 2559 offset := 0 2560 sortCriteria := gql_generated.SortCriteriaAlphabeticAsc 2561 pageInput := gql_generated.PageInput{ 2562 Limit: &limit, 2563 Offset: &offset, 2564 SortBy: &sortCriteria, 2565 } 2566 2567 images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, 2568 &pageInput, mockCve, log.NewLogger("debug", "")) 2569 So(err, ShouldBeNil) 2570 So(images.Results, ShouldNotBeEmpty) 2571 So(len(images.Results), ShouldEqual, limit) 2572 So(*images.Results[0].Tag, ShouldEqual, "1.0.1") 2573 }) 2574 }) 2575 2576 //nolint: dupl 2577 Convey("filterTags working, no base image list found", t, func() { 2578 layer1 := []byte{10, 11, 10, 11} 2579 layer2 := []byte{11, 11, 11, 11} 2580 layer3 := []byte{10, 10, 10, 11} 2581 layer4 := []byte{13, 14, 15, 11} 2582 2583 image := CreateImageWith(). 2584 LayerBlobs([][]byte{ 2585 layer1, 2586 layer2, 2587 layer3, 2588 }).DefaultConfig().Build() 2589 2590 derivedImage := CreateImageWith(). 2591 LayerBlobs([][]byte{ 2592 layer4, 2593 }).DefaultConfig().Build() 2594 2595 imageMetaMap := map[string]mTypes.ImageMeta{ 2596 image.DigestStr(): image.AsImageMeta(), 2597 derivedImage.DigestStr(): derivedImage.AsImageMeta(), 2598 } 2599 2600 mockMetaDB := mocks.MetaDBMock{ 2601 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2602 return mTypes.RepoMeta{ 2603 Name: "repo1", 2604 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2605 "1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2606 }, 2607 }, nil 2608 }, 2609 FilterImageMetaFn: func(ctx context.Context, digests []string) (map[string]mTypes.ImageMeta, error) { 2610 return imageMetaMap, nil 2611 }, 2612 FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, 2613 ) ([]mTypes.FullImageMeta, error) { 2614 fullImageMetaList := []mTypes.FullImageMeta{} 2615 repos := []mTypes.RepoMeta{{ 2616 Name: "repo1", 2617 Tags: map[mTypes.Tag]mTypes.Descriptor{ 2618 "1.0.1": {Digest: image.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2619 "1.0.2": {Digest: derivedImage.DigestStr(), MediaType: ispec.MediaTypeImageManifest}, 2620 }, 2621 }} 2622 2623 for _, repo := range repos { 2624 for tag, descriptor := range repo.Tags { 2625 if filterFunc(repo, imageMetaMap[descriptor.Digest]) { 2626 fullImageMetaList = append(fullImageMetaList, 2627 convert.GetFullImageMeta(tag, repo, imageMetaMap[descriptor.Digest])) 2628 } 2629 } 2630 } 2631 2632 return fullImageMetaList, nil 2633 }, 2634 } 2635 responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter, 2636 graphql.DefaultRecover) 2637 2638 mockCve := mocks.CveInfoMock{} 2639 images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockMetaDB, &gql_generated.PageInput{}, 2640 mockCve, log.NewLogger("debug", "")) 2641 So(err, ShouldBeNil) 2642 So(images.Results, ShouldBeEmpty) 2643 }) 2644 } 2645 2646 func TestExpandedRepoInfoErrors(t *testing.T) { 2647 log := log.NewLogger("debug", "") 2648 2649 Convey("Access error", t, func() { 2650 userAc := reqCtx.NewUserAccessControl() 2651 userAc.SetUsername("user") 2652 userAc.SetGlobPatterns("read", map[string]bool{ 2653 "repo": false, 2654 }) 2655 2656 ctx := userAc.DeriveContext(context.Background()) 2657 2658 responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, 2659 graphql.DefaultRecover) 2660 2661 _, err := expandedRepoInfo(responseContext, "repo", mocks.MetaDBMock{}, mocks.CveInfoMock{}, log) 2662 So(err, ShouldBeNil) 2663 }) 2664 } 2665 2666 func TestUtils(t *testing.T) { 2667 Convey("utils", t, func() { 2668 Convey("", func() { 2669 So(isMatchingPlatform(ispec.Platform{OS: "test"}, gql_generated.PlatformInput{Os: ref("t")}), ShouldBeFalse) 2670 So(getArch(ispec.Platform{OS: "t", Architecture: "e", Variant: "st"}), ShouldResemble, "e/st") 2671 }) 2672 2673 Convey("checkImageInput", func() { 2674 _, err := resolveImageData(context.Background(), gql_generated.ImageInput{Repo: "test"}, mocks.MetaDBMock{}) 2675 So(err, ShouldNotBeNil) 2676 }) 2677 2678 Convey("checkImageInput can't find index data", func() { 2679 _, err := resolveImageData(context.Background(), gql_generated.ImageInput{ 2680 Repo: "test", Tag: "test", Digest: ref("dig"), 2681 }, 2682 mocks.MetaDBMock{ 2683 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2684 return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{ 2685 "test": {MediaType: ispec.MediaTypeImageIndex}, 2686 }}, nil 2687 }, 2688 GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) { 2689 return mTypes.ImageMeta{}, ErrTestError 2690 }, 2691 }) 2692 So(err, ShouldNotBeNil) 2693 }) 2694 Convey("checkImageInput image meta not found", func() { 2695 _, err := resolveImageData(context.Background(), gql_generated.ImageInput{ 2696 Repo: "test", Tag: "test", Digest: ref("dig"), 2697 }, 2698 mocks.MetaDBMock{ 2699 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2700 return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{ 2701 "test": {MediaType: ispec.MediaTypeImageIndex}, 2702 }}, nil 2703 }, 2704 GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) { 2705 return mTypes.ImageMeta{}, nil 2706 }, 2707 }) 2708 So(err, ShouldNotBeNil) 2709 }) 2710 Convey("checkImageInput image meta bad media type", func() { 2711 _, err := resolveImageData(context.Background(), gql_generated.ImageInput{ 2712 Repo: "test", Tag: "test", Digest: ref("dig"), 2713 }, 2714 mocks.MetaDBMock{ 2715 GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { 2716 return mTypes.RepoMeta{Tags: map[string]mTypes.Descriptor{ 2717 "test": {MediaType: "bad-type"}, 2718 }}, nil 2719 }, 2720 GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) { 2721 return mTypes.ImageMeta{}, nil 2722 }, 2723 }) 2724 So(err, ShouldBeNil) 2725 }) 2726 }) 2727 } 2728 2729 func getGQLPageInput(limit int, offset int) *gql_generated.PageInput { 2730 sortCriteria := gql_generated.SortCriteriaAlphabeticAsc 2731 2732 return &gql_generated.PageInput{ 2733 Limit: &limit, 2734 Offset: &offset, 2735 SortBy: &sortCriteria, 2736 } 2737 }