zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/image_cmd_internal_test.go (about) 1 //go:build search 2 // +build search 3 4 package client 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "log" 12 "os" 13 "path" 14 "path/filepath" 15 "regexp" 16 "strings" 17 "sync" 18 "testing" 19 "time" 20 21 godigest "github.com/opencontainers/go-digest" 22 ispec "github.com/opencontainers/image-spec/specs-go/v1" 23 . "github.com/smartystreets/goconvey/convey" 24 25 zerr "zotregistry.dev/zot/errors" 26 "zotregistry.dev/zot/pkg/api" 27 "zotregistry.dev/zot/pkg/api/config" 28 "zotregistry.dev/zot/pkg/common" 29 extconf "zotregistry.dev/zot/pkg/extensions/config" 30 stypes "zotregistry.dev/zot/pkg/storage/types" 31 test "zotregistry.dev/zot/pkg/test/common" 32 . "zotregistry.dev/zot/pkg/test/image-utils" 33 ) 34 35 func TestSearchImageCmd(t *testing.T) { 36 Convey("Test image help", t, func() { 37 args := []string{"--help"} 38 configPath := makeConfigFile("") 39 defer os.Remove(configPath) 40 cmd := NewImageCommand(new(mockService)) 41 buff := bytes.NewBufferString("") 42 cmd.SetOut(buff) 43 cmd.SetErr(buff) 44 cmd.SetArgs(args) 45 err := cmd.Execute() 46 So(buff.String(), ShouldContainSubstring, "Usage") 47 So(err, ShouldBeNil) 48 Convey("with the shorthand", func() { 49 args[0] = "-h" 50 configPath := makeConfigFile("") 51 defer os.Remove(configPath) 52 cmd := NewImageCommand(new(mockService)) 53 buff := bytes.NewBufferString("") 54 cmd.SetOut(buff) 55 cmd.SetErr(buff) 56 cmd.SetArgs(args) 57 err := cmd.Execute() 58 So(buff.String(), ShouldContainSubstring, "Usage") 59 So(err, ShouldBeNil) 60 }) 61 }) 62 63 Convey("Test image no url", t, func() { 64 args := []string{"name", "dummyIdRandom", "--config", "imagetest"} 65 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) 66 defer os.Remove(configPath) 67 cmd := NewImageCommand(new(mockService)) 68 buff := bytes.NewBufferString("") 69 cmd.SetOut(buff) 70 cmd.SetErr(buff) 71 cmd.SetArgs(args) 72 err := cmd.Execute() 73 So(err, ShouldNotBeNil) 74 So(errors.Is(err, zerr.ErrNoURLProvided), ShouldBeTrue) 75 }) 76 77 Convey("Test image invalid home directory", t, func() { 78 args := []string{"name", "dummyImageName", "--config", "imagetest"} 79 80 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) 81 defer os.Remove(configPath) 82 83 err := os.Setenv("HOME", "nonExistentDirectory") 84 if err != nil { 85 panic(err) 86 } 87 88 cmd := NewImageCommand(new(mockService)) 89 buff := bytes.NewBufferString("") 90 cmd.SetOut(buff) 91 cmd.SetErr(buff) 92 cmd.SetArgs(args) 93 err = cmd.Execute() 94 So(err, ShouldNotBeNil) 95 96 home, err := os.UserHomeDir() 97 if err != nil { 98 panic(err) 99 } 100 err = os.Setenv("HOME", home) 101 if err != nil { 102 log.Fatal(err) 103 } 104 }) 105 106 Convey("Test image no params", t, func() { 107 args := []string{"--url", "someUrl"} 108 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) 109 defer os.Remove(configPath) 110 cmd := NewImageCommand(new(mockService)) 111 buff := bytes.NewBufferString("") 112 cmd.SetOut(buff) 113 cmd.SetErr(buff) 114 cmd.SetArgs(args) 115 err := cmd.Execute() 116 So(err, ShouldBeNil) 117 }) 118 119 Convey("Test image invalid url", t, func() { 120 args := []string{"name", "dummyImageName", "--url", "invalidUrl"} 121 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) 122 defer os.Remove(configPath) 123 cmd := NewImageCommand(new(searchService)) 124 buff := bytes.NewBufferString("") 125 cmd.SetOut(buff) 126 cmd.SetErr(buff) 127 cmd.SetArgs(args) 128 err := cmd.Execute() 129 So(err, ShouldNotBeNil) 130 So(strings.Contains(err.Error(), zerr.ErrInvalidURL.Error()), ShouldBeTrue) 131 So(buff.String(), ShouldContainSubstring, "invalid URL format") 132 }) 133 134 Convey("Test image invalid url port", t, func() { 135 args := []string{"name", "dummyImageName", "--url", "http://localhost:99999"} 136 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) 137 defer os.Remove(configPath) 138 cmd := NewImageCommand(new(searchService)) 139 buff := bytes.NewBufferString("") 140 cmd.SetOut(buff) 141 cmd.SetErr(buff) 142 cmd.SetArgs(args) 143 err := cmd.Execute() 144 So(err, ShouldNotBeNil) 145 So(buff.String(), ShouldContainSubstring, "invalid port") 146 147 Convey("without flags", func() { 148 args := []string{"list", "--url", "http://localhost:99999"} 149 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) 150 defer os.Remove(configPath) 151 cmd := NewImageCommand(new(searchService)) 152 buff := bytes.NewBufferString("") 153 cmd.SetOut(buff) 154 cmd.SetErr(buff) 155 cmd.SetArgs(args) 156 err = cmd.Execute() 157 So(err, ShouldNotBeNil) 158 So(buff.String(), ShouldContainSubstring, "invalid port") 159 }) 160 }) 161 162 Convey("Test image unreachable", t, func() { 163 args := []string{"name", "dummyImageName", "--url", "http://localhost:9999"} 164 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) 165 defer os.Remove(configPath) 166 cmd := NewImageCommand(new(searchService)) 167 buff := bytes.NewBufferString("") 168 cmd.SetOut(buff) 169 cmd.SetErr(buff) 170 cmd.SetArgs(args) 171 err := cmd.Execute() 172 So(err, ShouldNotBeNil) 173 }) 174 175 Convey("Test image url from config", t, func() { 176 args := []string{"name", "dummyImageName", "--config", "imagetest"} 177 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) 178 defer os.Remove(configPath) 179 cmd := NewImageCommand(new(mockService)) 180 buff := bytes.NewBufferString("") 181 cmd.SetOut(buff) 182 cmd.SetErr(buff) 183 cmd.SetArgs(args) 184 err := cmd.Execute() 185 space := regexp.MustCompile(`\s+`) 186 str := space.ReplaceAllString(buff.String(), " ") 187 So(strings.TrimSpace(str), ShouldEqual, 188 "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB") 189 So(err, ShouldBeNil) 190 }) 191 192 Convey("Test image by name", t, func() { 193 args := []string{"name", "dummyImageName", "--url", "http://127.0.0.1:8080"} 194 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) 195 defer os.Remove(configPath) 196 imageCmd := NewImageCommand(new(mockService)) 197 buff := &bytes.Buffer{} 198 imageCmd.SetOut(buff) 199 imageCmd.SetErr(buff) 200 imageCmd.SetArgs(args) 201 err := imageCmd.Execute() 202 space := regexp.MustCompile(`\s+`) 203 str := space.ReplaceAllString(buff.String(), " ") 204 So(strings.TrimSpace(str), ShouldEqual, 205 "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB") 206 So(err, ShouldBeNil) 207 }) 208 209 Convey("Test image by digest", t, func() { 210 searchConfig := getTestSearchConfig("http://127.0.0.1:8080", new(mockService)) 211 buff := &bytes.Buffer{} 212 searchConfig.ResultWriter = buff 213 err := SearchImagesByDigest(searchConfig, "6e2f80bf") 214 So(err, ShouldBeNil) 215 216 space := regexp.MustCompile(`\s+`) 217 str := space.ReplaceAllString(buff.String(), " ") 218 So(strings.TrimSpace(str), ShouldEqual, 219 "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB") 220 So(err, ShouldBeNil) 221 }) 222 } 223 224 func TestListRepos(t *testing.T) { 225 searchConfig := getTestSearchConfig("https://test-url.com", new(mockService)) 226 227 Convey("Test listing repositories", t, func() { 228 buff := &bytes.Buffer{} 229 searchConfig.ResultWriter = buff 230 err := SearchRepos(searchConfig) 231 So(err, ShouldBeNil) 232 }) 233 234 Convey("Test listing repositories with debug flag", t, func() { 235 args := []string{"list", "--config", "config-test", "--debug"} 236 configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) 237 defer os.Remove(configPath) 238 cmd := NewRepoCommand(new(searchService)) 239 240 buff := bytes.NewBufferString("") 241 cmd.SetOut(buff) 242 cmd.SetErr(buff) 243 cmd.SetArgs(args) 244 err := cmd.Execute() 245 So(err, ShouldNotBeNil) 246 space := regexp.MustCompile(`\s+`) 247 str := space.ReplaceAllString(buff.String(), " ") 248 actual := strings.TrimSpace(str) 249 So(actual, ShouldContainSubstring, "GET") 250 }) 251 252 Convey("Test error on home directory", t, func() { 253 args := []string{"list", "--config", "config-test"} 254 255 configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) 256 defer os.Remove(configPath) 257 258 err := os.Setenv("HOME", "nonExistentDirectory") 259 if err != nil { 260 panic(err) 261 } 262 263 cmd := NewRepoCommand(new(mockService)) 264 buff := bytes.NewBufferString("") 265 cmd.SetOut(buff) 266 cmd.SetErr(buff) 267 cmd.SetArgs(args) 268 err = cmd.Execute() 269 So(err, ShouldNotBeNil) 270 271 home, err := os.UserHomeDir() 272 if err != nil { 273 panic(err) 274 } 275 err = os.Setenv("HOME", home) 276 if err != nil { 277 log.Fatal(err) 278 } 279 }) 280 281 Convey("Test listing repositories error", t, func() { 282 args := []string{"list", "--config", "config-test"} 283 configPath := makeConfigFile(`{"configs":[{"_name":"config-test", 284 "url":"https://invalid.invalid","showspinner":false}]}`) 285 defer os.Remove(configPath) 286 cmd := NewRepoCommand(new(searchService)) 287 buff := bytes.NewBufferString("") 288 cmd.SetOut(buff) 289 cmd.SetErr(buff) 290 cmd.SetArgs(args) 291 err := cmd.Execute() 292 So(err, ShouldNotBeNil) 293 }) 294 295 Convey("Test unable to get config value", t, func() { 296 args := []string{"list", "--config", "config-test-nonexistent"} 297 configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) 298 defer os.Remove(configPath) 299 cmd := NewRepoCommand(new(mockService)) 300 buff := bytes.NewBufferString("") 301 cmd.SetOut(buff) 302 cmd.SetErr(buff) 303 cmd.SetArgs(args) 304 err := cmd.Execute() 305 So(err, ShouldNotBeNil) 306 }) 307 308 Convey("Test error - no url provided", t, func() { 309 args := []string{"list", "--config", "config-test"} 310 configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`) 311 defer os.Remove(configPath) 312 cmd := NewRepoCommand(new(mockService)) 313 buff := bytes.NewBufferString("") 314 cmd.SetOut(buff) 315 cmd.SetErr(buff) 316 cmd.SetArgs(args) 317 err := cmd.Execute() 318 So(err, ShouldNotBeNil) 319 }) 320 321 Convey("Test error - spinner config invalid", t, func() { 322 args := []string{"list", "--config", "config-test"} 323 configPath := makeConfigFile(`{"configs":[{"_name":"config-test", 324 "url":"https://test-url.com","showspinner":invalid}]}`) 325 defer os.Remove(configPath) 326 cmd := NewRepoCommand(new(mockService)) 327 buff := bytes.NewBufferString("") 328 cmd.SetOut(buff) 329 cmd.SetErr(buff) 330 cmd.SetArgs(args) 331 err := cmd.Execute() 332 So(err, ShouldNotBeNil) 333 }) 334 335 Convey("Test error - verifyTLSConfig fails", t, func() { 336 args := []string{"list", "--config", "config-test"} 337 configPath := makeConfigFile(`{"configs":[{"_name":"config-test", 338 "verify-tls":"invalid", "url":"https://test-url.com","showspinner":false}]}`) 339 defer os.Remove(configPath) 340 cmd := NewRepoCommand(new(mockService)) 341 buff := bytes.NewBufferString("") 342 cmd.SetOut(buff) 343 cmd.SetErr(buff) 344 cmd.SetArgs(args) 345 err := cmd.Execute() 346 So(err, ShouldNotBeNil) 347 }) 348 } 349 350 func TestOutputFormat(t *testing.T) { 351 Convey("Test text", t, func() { 352 args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "text"} 353 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) 354 defer os.Remove(configPath) 355 cmd := NewImageCommand(new(mockService)) 356 buff := bytes.NewBufferString("") 357 cmd.SetOut(buff) 358 cmd.SetErr(buff) 359 cmd.SetArgs(args) 360 err := cmd.Execute() 361 space := regexp.MustCompile(`\s+`) 362 str := space.ReplaceAllString(buff.String(), " ") 363 So(strings.TrimSpace(str), ShouldEqual, 364 "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB") 365 So(err, ShouldBeNil) 366 }) 367 368 Convey("Test json", t, func() { 369 args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "json"} 370 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) 371 defer os.Remove(configPath) 372 cmd := NewImageCommand(new(mockService)) 373 buff := bytes.NewBufferString("") 374 cmd.SetOut(buff) 375 cmd.SetErr(buff) 376 cmd.SetArgs(args) 377 err := cmd.Execute() 378 // Output is supposed to be in json lines format, keep all spaces as is for verification 379 So(buff.String(), ShouldEqual, `{"repoName":"dummyImageName","tag":"tag",`+ 380 `"digest":"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",`+ 381 `"mediaType":"application/vnd.oci.image.manifest.v1+json",`+ 382 `"manifests":[{"digest":"sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6",`+ 383 `"configDigest":"sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0",`+ 384 `"lastUpdated":"0001-01-01T00:00:00Z","size":"123445","platform":{"os":"os","arch":"arch",`+ 385 `"variant":""},"isSigned":false,"downloadCount":0,`+ 386 `"layers":[{"size":"","digest":"sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6",`+ 387 `"score":0}],"history":null,"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,`+ 388 `"mediumCount":0,"highCount":0,"criticalCount":0,"count":0},`+ 389 `"referrers":null,"artifactType":"","signatureInfo":null}],"size":"123445",`+ 390 `"downloadCount":0,"lastUpdated":"0001-01-01T00:00:00Z","description":"","isSigned":false,"licenses":"",`+ 391 `"labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",`+ 392 `"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,"highCount":0,`+ 393 `"criticalCount":0,"count":0},"referrers":null,"signatureInfo":null}`+"\n") 394 So(err, ShouldBeNil) 395 }) 396 397 Convey("Test yaml", t, func() { 398 args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "yaml"} 399 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) 400 defer os.Remove(configPath) 401 cmd := NewImageCommand(new(mockService)) 402 buff := bytes.NewBufferString("") 403 cmd.SetOut(buff) 404 cmd.SetErr(buff) 405 cmd.SetArgs(args) 406 err := cmd.Execute() 407 space := regexp.MustCompile(`\s+`) 408 str := space.ReplaceAllString(buff.String(), " ") 409 So( 410 strings.TrimSpace(str), 411 ShouldEqual, 412 `--- reponame: dummyImageName tag: tag `+ 413 `digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+ 414 `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - `+ 415 `digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+ 416 `configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+ 417 `lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+ 418 `issigned: false downloadcount: 0 layers: - size: "" `+ 419 `digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+ 420 `history: [] vulnerabilities: maxseverity: "" `+ 421 `unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 `+ 422 `referrers: [] artifacttype: "" `+ 423 `signatureinfo: [] size: "123445" downloadcount: 0 `+ 424 `lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+ 425 `title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+ 426 `unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+ 427 `count: 0 referrers: [] signatureinfo: []`, 428 ) 429 So(err, ShouldBeNil) 430 431 Convey("Test yml", func() { 432 args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "yml"} 433 configPath := makeConfigFile( 434 `{"configs":[{"_name":"imagetest",` + 435 `"url":"https://test-url.com","showspinner":false}]}`, 436 ) 437 defer os.Remove(configPath) 438 cmd := NewImageCommand(new(mockService)) 439 buff := bytes.NewBufferString("") 440 cmd.SetOut(buff) 441 cmd.SetErr(buff) 442 cmd.SetArgs(args) 443 err := cmd.Execute() 444 space := regexp.MustCompile(`\s+`) 445 str := space.ReplaceAllString(buff.String(), " ") 446 So( 447 strings.TrimSpace(str), 448 ShouldEqual, 449 `--- reponame: dummyImageName tag: tag `+ 450 `digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+ 451 `mediatype: application/vnd.oci.image.manifest.v1+json `+ 452 `manifests: - digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+ 453 `configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+ 454 `lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+ 455 `issigned: false downloadcount: 0 layers: - size: "" `+ 456 `digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+ 457 `history: [] vulnerabilities: maxseverity: "" unknowncount: 0 lowcount: 0 mediumcount: 0 `+ 458 `highcount: 0 criticalcount: 0 count: 0 referrers: [] artifacttype: "" `+ 459 `signatureinfo: [] size: "123445" downloadcount: 0 `+ 460 `lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+ 461 `title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+ 462 `unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+ 463 `count: 0 referrers: [] signatureinfo: []`, 464 ) 465 So(err, ShouldBeNil) 466 }) 467 }) 468 469 Convey("Test invalid", t, func() { 470 args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "random"} 471 configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) 472 defer os.Remove(configPath) 473 cmd := NewImageCommand(new(mockService)) 474 buff := bytes.NewBufferString("") 475 cmd.SetOut(buff) 476 cmd.SetErr(buff) 477 cmd.SetArgs(args) 478 err := cmd.Execute() 479 So(err, ShouldNotBeNil) 480 So(buff.String(), ShouldContainSubstring, "invalid cli output format") 481 }) 482 } 483 484 func TestImagesCommandGQL(t *testing.T) { 485 port := test.GetFreePort() 486 baseURL := test.GetBaseURL(port) 487 conf := config.New() 488 conf.HTTP.Port = port 489 490 defaultVal := true 491 conf.Extensions = &extconf.ExtensionConfig{ 492 Search: &extconf.SearchConfig{ 493 BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, 494 }, 495 } 496 ctlr := api.NewController(conf) 497 ctlr.Config.Storage.RootDirectory = t.TempDir() 498 cm := test.NewControllerManager(ctlr) 499 500 cm.StartAndWait(conf.HTTP.Port) 501 defer cm.StopServer() 502 503 Convey("commands with gql", t, func() { 504 err := removeLocalStorageContents(ctlr.StoreController.DefaultStore) 505 So(err, ShouldBeNil) 506 507 Convey("base and derived command", func() { 508 baseImage := CreateImageWith().LayerBlobs( 509 [][]byte{{1, 2, 3}, {11, 22, 33}}, 510 ).DefaultConfig().Build() 511 512 derivedImage := CreateImageWith().LayerBlobs( 513 [][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}}, 514 ).DefaultConfig().Build() 515 516 err := UploadImage(baseImage, baseURL, "repo", "base") 517 So(err, ShouldBeNil) 518 519 err = UploadImage(derivedImage, baseURL, "repo", "derived") 520 So(err, ShouldBeNil) 521 522 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 523 baseURL)) 524 defer os.Remove(configPath) 525 args := []string{"base", "repo:derived", "--config", "imagetest"} 526 527 cmd := NewImageCommand(NewSearchService()) 528 buff := bytes.NewBufferString("") 529 cmd.SetOut(buff) 530 cmd.SetErr(buff) 531 cmd.SetArgs(args) 532 err = cmd.Execute() 533 So(err, ShouldBeNil) 534 space := regexp.MustCompile(`\s+`) 535 str := space.ReplaceAllString(buff.String(), " ") 536 actual := strings.TrimSpace(str) 537 So(actual, ShouldContainSubstring, "repo base linux/amd64 df554ddd false 699B") 538 args = []string{"derived", "repo:base", "--config", "imagetest"} 539 540 cmd = NewImageCommand(NewSearchService()) 541 buff = bytes.NewBufferString("") 542 cmd.SetOut(buff) 543 cmd.SetErr(buff) 544 cmd.SetArgs(args) 545 err = cmd.Execute() 546 So(err, ShouldBeNil) 547 str = space.ReplaceAllString(buff.String(), " ") 548 actual = strings.TrimSpace(str) 549 So(actual, ShouldContainSubstring, "repo derived linux/amd64 79f4b82e false 854B") 550 }) 551 552 Convey("base and derived command errors", func() { 553 // too many parameters 554 buff := bytes.NewBufferString("") 555 args := []string{"too", "many", "args", "--config", "imagetest"} 556 cmd := NewImageBaseCommand(NewSearchService()) 557 cmd.SetOut(buff) 558 cmd.SetErr(buff) 559 cmd.SetArgs(args) 560 err := cmd.Execute() 561 So(err, ShouldNotBeNil) 562 563 cmd = NewImageDerivedCommand(NewSearchService()) 564 cmd.SetOut(buff) 565 cmd.SetErr(buff) 566 cmd.SetArgs(args) 567 err = cmd.Execute() 568 So(err, ShouldNotBeNil) 569 570 // bad input 571 buff = bytes.NewBufferString("") 572 args = []string{"only-repo"} 573 cmd = NewImageBaseCommand(NewSearchService()) 574 cmd.SetOut(buff) 575 cmd.SetErr(buff) 576 cmd.SetArgs(args) 577 err = cmd.Execute() 578 So(err, ShouldNotBeNil) 579 580 cmd = NewImageDerivedCommand(NewSearchService()) 581 cmd.SetOut(buff) 582 cmd.SetErr(buff) 583 cmd.SetArgs(args) 584 err = cmd.Execute() 585 So(err, ShouldNotBeNil) 586 587 // no url 588 buff = bytes.NewBufferString("") 589 args = []string{"repo:tag"} 590 cmd = NewImageBaseCommand(NewSearchService()) 591 cmd.SetOut(buff) 592 cmd.SetErr(buff) 593 cmd.SetArgs(args) 594 err = cmd.Execute() 595 So(err, ShouldNotBeNil) 596 597 cmd = NewImageDerivedCommand(NewSearchService()) 598 cmd.SetOut(buff) 599 cmd.SetErr(buff) 600 cmd.SetArgs(args) 601 err = cmd.Execute() 602 So(err, ShouldNotBeNil) 603 }) 604 605 Convey("digest command", func() { 606 image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() 607 608 err := UploadImage(image, baseURL, "repo", "img") 609 So(err, ShouldBeNil) 610 611 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 612 baseURL)) 613 defer os.Remove(configPath) 614 args := []string{"digest", image.DigestStr(), "--config", "imagetest"} 615 616 cmd := NewImageCommand(NewSearchService()) 617 buff := bytes.NewBufferString("") 618 cmd.SetOut(buff) 619 cmd.SetErr(buff) 620 cmd.SetArgs(args) 621 err = cmd.Execute() 622 So(err, ShouldBeNil) 623 space := regexp.MustCompile(`\s+`) 624 str := space.ReplaceAllString(buff.String(), " ") 625 actual := strings.TrimSpace(str) 626 So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B", 627 image.DigestStr()[7:7+8])) 628 }) 629 630 Convey("digest command errors", func() { 631 // too many parameters 632 buff := bytes.NewBufferString("") 633 args := []string{"too", "many", "args", "--config", "imagetest"} 634 cmd := NewImageDigestCommand(NewSearchService()) 635 cmd.SetOut(buff) 636 cmd.SetErr(buff) 637 cmd.SetArgs(args) 638 err := cmd.Execute() 639 So(err, ShouldNotBeNil) 640 641 // bad input 642 buff = bytes.NewBufferString("") 643 args = []string{"bad-digest"} 644 cmd = NewImageDigestCommand(NewSearchService()) 645 cmd.SetOut(buff) 646 cmd.SetErr(buff) 647 cmd.SetArgs(args) 648 err = cmd.Execute() 649 So(err, ShouldNotBeNil) 650 651 // no url 652 buff = bytes.NewBufferString("") 653 args = []string{godigest.FromString("str").String()} 654 cmd = NewImageDigestCommand(NewSearchService()) 655 cmd.SetOut(buff) 656 cmd.SetErr(buff) 657 cmd.SetArgs(args) 658 err = cmd.Execute() 659 So(err, ShouldNotBeNil) 660 }) 661 662 Convey("list command", func() { 663 image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() 664 665 err := UploadImage(image, baseURL, "repo", "img") 666 So(err, ShouldBeNil) 667 668 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 669 baseURL)) 670 defer os.Remove(configPath) 671 args := []string{"list", "--config", "imagetest"} 672 673 cmd := NewImageCommand(NewSearchService()) 674 buff := bytes.NewBufferString("") 675 cmd.SetOut(buff) 676 cmd.SetErr(buff) 677 cmd.SetArgs(args) 678 err = cmd.Execute() 679 So(err, ShouldBeNil) 680 space := regexp.MustCompile(`\s+`) 681 str := space.ReplaceAllString(buff.String(), " ") 682 actual := strings.TrimSpace(str) 683 fmt.Println(actual) 684 So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B", 685 image.DigestStr()[7:7+8])) 686 fmt.Println(actual) 687 }) 688 689 Convey("list command errors", func() { 690 // too many parameters 691 buff := bytes.NewBufferString("") 692 args := []string{"repo:img", "arg", "--config", "imagetest"} 693 cmd := NewImageListCommand(NewSearchService()) 694 cmd.SetOut(buff) 695 cmd.SetErr(buff) 696 cmd.SetArgs(args) 697 err := cmd.Execute() 698 So(err, ShouldNotBeNil) 699 700 // no url 701 buff = bytes.NewBufferString("") 702 args = []string{} 703 cmd = NewImageListCommand(NewSearchService()) 704 cmd.SetOut(buff) 705 cmd.SetErr(buff) 706 cmd.SetArgs(args) 707 err = cmd.Execute() 708 So(err, ShouldNotBeNil) 709 }) 710 711 Convey("name command", func() { 712 image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() 713 714 err := UploadImage(image, baseURL, "repo", "img") 715 So(err, ShouldBeNil) 716 717 err = UploadImage(CreateRandomImage(), baseURL, "repo", "img2") 718 So(err, ShouldBeNil) 719 720 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 721 baseURL)) 722 defer os.Remove(configPath) 723 args := []string{"name", "repo:img", "--config", "imagetest"} 724 725 cmd := NewImageCommand(NewSearchService()) 726 buff := bytes.NewBufferString("") 727 cmd.SetOut(buff) 728 cmd.SetErr(buff) 729 cmd.SetArgs(args) 730 err = cmd.Execute() 731 So(err, ShouldBeNil) 732 space := regexp.MustCompile(`\s+`) 733 str := space.ReplaceAllString(buff.String(), " ") 734 actual := strings.TrimSpace(str) 735 fmt.Println(actual) 736 So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B", 737 image.DigestStr()[7:7+8])) 738 fmt.Println(actual) 739 }) 740 741 Convey("name command errors", func() { 742 // too many parameters 743 buff := bytes.NewBufferString("") 744 args := []string{"repo:img", "arg", "--config", "imagetest"} 745 cmd := NewImageNameCommand(NewSearchService()) 746 cmd.SetOut(buff) 747 cmd.SetErr(buff) 748 cmd.SetArgs(args) 749 err := cmd.Execute() 750 So(err, ShouldNotBeNil) 751 752 // bad input 753 buff = bytes.NewBufferString("") 754 args = []string{":tag"} 755 cmd = NewImageNameCommand(NewSearchService()) 756 cmd.SetOut(buff) 757 cmd.SetErr(buff) 758 cmd.SetArgs(args) 759 err = cmd.Execute() 760 So(err, ShouldNotBeNil) 761 762 // no url 763 buff = bytes.NewBufferString("") 764 args = []string{"repo:tag"} 765 cmd = NewImageNameCommand(NewSearchService()) 766 cmd.SetOut(buff) 767 cmd.SetErr(buff) 768 cmd.SetArgs(args) 769 err = cmd.Execute() 770 So(err, ShouldNotBeNil) 771 }) 772 773 Convey("CVE", func() { 774 vulnImage := CreateDefaultVulnerableImage() 775 err := UploadImage(vulnImage, baseURL, "repo", "vuln") 776 So(err, ShouldBeNil) 777 778 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 779 baseURL)) 780 defer os.Remove(configPath) 781 782 args := []string{"cve", "repo:vuln", "--config", "imagetest"} 783 cmd := NewImageCommand(mockService{}) 784 buff := bytes.NewBufferString("") 785 cmd.SetOut(buff) 786 cmd.SetErr(buff) 787 cmd.SetArgs(args) 788 err = cmd.Execute() 789 So(err, ShouldBeNil) 790 space := regexp.MustCompile(`\s+`) 791 str := space.ReplaceAllString(buff.String(), " ") 792 actual := strings.TrimSpace(str) 793 So(actual, ShouldContainSubstring, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1") 794 So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE") 795 }) 796 797 Convey("CVE errors", func() { 798 count := 0 799 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 800 baseURL)) 801 defer os.Remove(configPath) 802 args := []string{"cve", "repo:vuln", "--config", "imagetest"} 803 cmd := NewImageCommand(mockService{ 804 getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username, password, 805 imageName, searchedCVE string) (*cveResult, error, 806 ) { 807 if count == 0 { 808 count++ 809 fmt.Println("Count:", count) 810 811 return &cveResult{}, zerr.ErrCVEDBNotFound 812 } 813 814 return &cveResult{}, zerr.ErrInjected 815 }, 816 }) 817 buff := bytes.NewBufferString("") 818 cmd.SetOut(buff) 819 cmd.SetErr(buff) 820 cmd.SetArgs(args) 821 err = cmd.Execute() 822 So(err, ShouldNotBeNil) 823 space := regexp.MustCompile(`\s+`) 824 str := space.ReplaceAllString(buff.String(), " ") 825 actual := strings.TrimSpace(str) 826 So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready") 827 }) 828 }) 829 830 Convey("Config error", t, func() { 831 args := []string{"base", "repo:derived", "--config", "imagetest"} 832 cmd := NewImageCommand(NewSearchService()) 833 buff := bytes.NewBufferString("") 834 cmd.SetOut(buff) 835 cmd.SetErr(buff) 836 cmd.SetArgs(args) 837 err := cmd.Execute() 838 So(err, ShouldNotBeNil) 839 So(err, ShouldNotBeNil) 840 841 args = []string{"derived", "repo:base"} 842 cmd = NewImageCommand(NewSearchService()) 843 buff = bytes.NewBufferString("") 844 cmd.SetOut(buff) 845 cmd.SetErr(buff) 846 cmd.SetArgs(args) 847 err = cmd.Execute() 848 So(err, ShouldNotBeNil) 849 850 args = []string{"digest", ispec.DescriptorEmptyJSON.Digest.String()} 851 cmd = NewImageCommand(NewSearchService()) 852 buff = bytes.NewBufferString("") 853 cmd.SetOut(buff) 854 cmd.SetErr(buff) 855 cmd.SetArgs(args) 856 err = cmd.Execute() 857 So(err, ShouldNotBeNil) 858 859 args = []string{"list"} 860 cmd = NewImageCommand(NewSearchService()) 861 buff = bytes.NewBufferString("") 862 cmd.SetOut(buff) 863 cmd.SetErr(buff) 864 cmd.SetArgs(args) 865 err = cmd.Execute() 866 So(err, ShouldNotBeNil) 867 868 args = []string{"name", "repo:img"} 869 cmd = NewImageCommand(NewSearchService()) 870 buff = bytes.NewBufferString("") 871 cmd.SetOut(buff) 872 cmd.SetErr(buff) 873 cmd.SetArgs(args) 874 err = cmd.Execute() 875 So(err, ShouldNotBeNil) 876 877 args = []string{"cve", "repo:vuln"} 878 cmd = NewImageCommand(mockService{}) 879 buff = bytes.NewBufferString("") 880 cmd.SetOut(buff) 881 cmd.SetErr(buff) 882 cmd.SetArgs(args) 883 err = cmd.Execute() 884 So(err, ShouldNotBeNil) 885 }) 886 } 887 888 func TestImageCommandREST(t *testing.T) { 889 port := test.GetFreePort() 890 baseURL := test.GetBaseURL(port) 891 conf := config.New() 892 conf.HTTP.Port = port 893 894 ctlr := api.NewController(conf) 895 ctlr.Config.Storage.RootDirectory = t.TempDir() 896 cm := test.NewControllerManager(ctlr) 897 898 cm.StartAndWait(conf.HTTP.Port) 899 defer cm.StopServer() 900 901 Convey("commands without gql", t, func() { 902 err := removeLocalStorageContents(ctlr.StoreController.DefaultStore) 903 So(err, ShouldBeNil) 904 905 Convey("base and derived command", func() { 906 baseImage := CreateImageWith().LayerBlobs( 907 [][]byte{{1, 2, 3}, {11, 22, 33}}, 908 ).DefaultConfig().Build() 909 910 derivedImage := CreateImageWith().LayerBlobs( 911 [][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}}, 912 ).DefaultConfig().Build() 913 914 err := UploadImage(baseImage, baseURL, "repo", "base") 915 So(err, ShouldBeNil) 916 917 err = UploadImage(derivedImage, baseURL, "repo", "derived") 918 So(err, ShouldBeNil) 919 920 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 921 baseURL)) 922 defer os.Remove(configPath) 923 924 args := []string{"base", "repo:derived", "--config", "imagetest"} 925 cmd := NewImageCommand(NewSearchService()) 926 buff := bytes.NewBufferString("") 927 cmd.SetOut(buff) 928 cmd.SetErr(buff) 929 cmd.SetArgs(args) 930 err = cmd.Execute() 931 So(err, ShouldNotBeNil) 932 933 args = []string{"derived", "repo:base"} 934 cmd = NewImageCommand(NewSearchService()) 935 buff = bytes.NewBufferString("") 936 cmd.SetOut(buff) 937 cmd.SetErr(buff) 938 cmd.SetArgs(args) 939 err = cmd.Execute() 940 So(err, ShouldNotBeNil) 941 }) 942 943 Convey("digest command", func() { 944 image := CreateRandomImage() 945 946 err := UploadImage(image, baseURL, "repo", "img") 947 So(err, ShouldBeNil) 948 949 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 950 baseURL)) 951 defer os.Remove(configPath) 952 953 args := []string{"digest", image.DigestStr(), "--config", "imagetest"} 954 cmd := NewImageCommand(NewSearchService()) 955 buff := bytes.NewBufferString("") 956 cmd.SetOut(buff) 957 cmd.SetErr(buff) 958 cmd.SetArgs(args) 959 err = cmd.Execute() 960 So(err, ShouldNotBeNil) 961 }) 962 963 Convey("list command", func() { 964 image := CreateRandomImage() 965 966 err := UploadImage(image, baseURL, "repo", "img") 967 So(err, ShouldBeNil) 968 969 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 970 baseURL)) 971 defer os.Remove(configPath) 972 973 args := []string{"list", "--config", "imagetest"} 974 cmd := NewImageCommand(NewSearchService()) 975 buff := bytes.NewBufferString("") 976 cmd.SetOut(buff) 977 cmd.SetErr(buff) 978 cmd.SetArgs(args) 979 err = cmd.Execute() 980 So(err, ShouldBeNil) 981 fmt.Println(buff.String()) 982 fmt.Println() 983 }) 984 985 Convey("name command", func() { 986 image := CreateRandomImage() 987 988 err := UploadImage(image, baseURL, "repo", "img") 989 So(err, ShouldBeNil) 990 991 err = UploadImage(CreateRandomImage(), baseURL, "repo", "img2") 992 So(err, ShouldBeNil) 993 994 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 995 baseURL)) 996 defer os.Remove(configPath) 997 998 args := []string{"name", "repo:img", "--config", "imagetest"} 999 cmd := NewImageCommand(NewSearchService()) 1000 buff := bytes.NewBufferString("") 1001 cmd.SetOut(buff) 1002 cmd.SetErr(buff) 1003 cmd.SetArgs(args) 1004 err = cmd.Execute() 1005 So(err, ShouldBeNil) 1006 fmt.Println(buff.String()) 1007 fmt.Println() 1008 }) 1009 1010 Convey("CVE", func() { 1011 vulnImage := CreateDefaultVulnerableImage() 1012 err := UploadImage(vulnImage, baseURL, "repo", "vuln") 1013 So(err, ShouldBeNil) 1014 1015 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, 1016 baseURL)) 1017 args := []string{"cve", "repo:vuln", "--config", "imagetest"} 1018 defer os.Remove(configPath) 1019 cmd := NewImageCommand(mockService{}) 1020 buff := bytes.NewBufferString("") 1021 cmd.SetOut(buff) 1022 cmd.SetErr(buff) 1023 cmd.SetArgs(args) 1024 err = cmd.Execute() 1025 So(err, ShouldNotBeNil) 1026 }) 1027 }) 1028 } 1029 1030 type mockService struct { 1031 getAllImagesFn func(ctx context.Context, config SearchConfig, username, password string, 1032 channel chan stringResult, wtgrp *sync.WaitGroup) 1033 1034 getImagesGQLFn func(ctx context.Context, config SearchConfig, username, password string, 1035 imageName string) (*common.ImageListResponse, error) 1036 1037 getImageByNameFn func(ctx context.Context, config SearchConfig, 1038 username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup, 1039 ) 1040 1041 getImagesByDigestFn func(ctx context.Context, config SearchConfig, username, 1042 password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, 1043 ) 1044 1045 getReferrersFn func(ctx context.Context, config SearchConfig, username, password string, 1046 repo, digest string, 1047 ) (referrersResult, error) 1048 1049 globalSearchGQLFn func(ctx context.Context, config SearchConfig, username, password string, 1050 query string, 1051 ) (*common.GlobalSearch, error) 1052 1053 getReferrersGQLFn func(ctx context.Context, config SearchConfig, username, password string, 1054 repo, digest string, 1055 ) (*common.ReferrersResp, error) 1056 1057 getDerivedImageListGQLFn func(ctx context.Context, config SearchConfig, username, password string, 1058 derivedImage string, 1059 ) (*common.DerivedImageListResponse, error) 1060 1061 getBaseImageListGQLFn func(ctx context.Context, config SearchConfig, username, password string, 1062 derivedImage string, 1063 ) (*common.BaseImageListResponse, error) 1064 1065 getImagesForDigestGQLFn func(ctx context.Context, config SearchConfig, username, password string, 1066 digest string, 1067 ) (*common.ImagesForDigest, error) 1068 1069 getCveByImageGQLFn func(ctx context.Context, config SearchConfig, username, password, 1070 imageName, searchedCVE string, 1071 ) (*cveResult, error) 1072 1073 getTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password, 1074 imageName, cveID string, 1075 ) (*common.ImagesForCve, error) 1076 1077 getFixedTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password, 1078 imageName, cveID string, 1079 ) (*common.ImageListWithCVEFixedResponse, error) 1080 1081 getCVEDiffListGQLFn func(ctx context.Context, config SearchConfig, username, password string, 1082 minuend, subtrahend ImageIdentifier, 1083 ) (*cveDiffListResp, error) 1084 } 1085 1086 func (service mockService) getCVEDiffListGQL(ctx context.Context, config SearchConfig, username, password string, 1087 minuend, subtrahend ImageIdentifier, 1088 ) (*cveDiffListResp, error) { 1089 if service.getCVEDiffListGQLFn != nil { 1090 return service.getCVEDiffListGQLFn(ctx, config, username, password, minuend, subtrahend) 1091 } 1092 1093 return &cveDiffListResp{}, nil 1094 } 1095 1096 func (service mockService) getRepos(ctx context.Context, config SearchConfig, username, 1097 password string, channel chan stringResult, wtgrp *sync.WaitGroup, 1098 ) { 1099 defer wtgrp.Done() 1100 defer close(channel) 1101 1102 fmt.Fprintln(config.ResultWriter, "\n\nREPOSITORY NAME") 1103 1104 fmt.Fprintln(config.ResultWriter, "repo1") 1105 fmt.Fprintln(config.ResultWriter, "repo2") 1106 } 1107 1108 func (service mockService) getReferrers(ctx context.Context, config SearchConfig, username, password string, 1109 repo, digest string, 1110 ) (referrersResult, error) { 1111 if service.getReferrersFn != nil { 1112 return service.getReferrersFn(ctx, config, username, password, repo, digest) 1113 } 1114 1115 return referrersResult{ 1116 common.Referrer{ 1117 ArtifactType: "art.type", 1118 Digest: ispec.DescriptorEmptyJSON.Digest.String(), 1119 MediaType: ispec.MediaTypeImageManifest, 1120 Size: 100, 1121 }, 1122 }, nil 1123 } 1124 1125 func (service mockService) globalSearchGQL(ctx context.Context, config SearchConfig, username, password string, 1126 query string, 1127 ) (*common.GlobalSearch, error) { 1128 if service.globalSearchGQLFn != nil { 1129 return service.globalSearchGQLFn(ctx, config, username, password, query) 1130 } 1131 1132 return &common.GlobalSearch{ 1133 Images: []common.ImageSummary{ 1134 { 1135 RepoName: "repo", 1136 MediaType: ispec.MediaTypeImageManifest, 1137 Size: "100", 1138 Manifests: []common.ManifestSummary{ 1139 { 1140 Digest: godigest.FromString("str").String(), 1141 Size: "100", 1142 ConfigDigest: ispec.DescriptorEmptyJSON.Digest.String(), 1143 }, 1144 }, 1145 }, 1146 }, 1147 Repos: []common.RepoSummary{ 1148 { 1149 Name: "repo", 1150 Size: "100", 1151 LastUpdated: time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC), 1152 }, 1153 }, 1154 }, nil 1155 } 1156 1157 func (service mockService) getReferrersGQL(ctx context.Context, config SearchConfig, username, password string, 1158 repo, digest string, 1159 ) (*common.ReferrersResp, error) { 1160 if service.getReferrersGQLFn != nil { 1161 return service.getReferrersGQLFn(ctx, config, username, password, repo, digest) 1162 } 1163 1164 return &common.ReferrersResp{ 1165 ReferrersResult: common.ReferrersResult{ 1166 Referrers: []common.Referrer{ 1167 { 1168 MediaType: "MediaType", 1169 ArtifactType: "ArtifactType", 1170 Size: 100, 1171 Digest: "Digest", 1172 }, 1173 }, 1174 }, 1175 }, nil 1176 } 1177 1178 func (service mockService) getDerivedImageListGQL(ctx context.Context, config SearchConfig, username, password string, 1179 derivedImage string, 1180 ) (*common.DerivedImageListResponse, error) { 1181 if service.getDerivedImageListGQLFn != nil { 1182 return service.getDerivedImageListGQLFn(ctx, config, username, password, derivedImage) 1183 } 1184 1185 imageListGQLResponse := &common.DerivedImageListResponse{} 1186 imageListGQLResponse.DerivedImageList.Results = []common.ImageSummary{ 1187 { 1188 RepoName: "dummyImageName", 1189 Tag: "tag", 1190 Manifests: []common.ManifestSummary{ 1191 { 1192 Digest: godigest.FromString("Digest").String(), 1193 ConfigDigest: godigest.FromString("ConfigDigest").String(), 1194 Size: "123445", 1195 Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, 1196 Platform: common.Platform{Os: "os", Arch: "arch"}, 1197 }, 1198 }, 1199 Size: "123445", 1200 }, 1201 } 1202 1203 return imageListGQLResponse, nil 1204 } 1205 1206 func (service mockService) getBaseImageListGQL(ctx context.Context, config SearchConfig, username, password string, 1207 baseImage string, 1208 ) (*common.BaseImageListResponse, error) { 1209 if service.getBaseImageListGQLFn != nil { 1210 return service.getBaseImageListGQLFn(ctx, config, username, password, baseImage) 1211 } 1212 1213 imageListGQLResponse := &common.BaseImageListResponse{} 1214 imageListGQLResponse.BaseImageList.Results = []common.ImageSummary{ 1215 { 1216 RepoName: "dummyImageName", 1217 Tag: "tag", 1218 Manifests: []common.ManifestSummary{ 1219 { 1220 Digest: godigest.FromString("Digest").String(), 1221 ConfigDigest: godigest.FromString("ConfigDigest").String(), 1222 Size: "123445", 1223 Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, 1224 Platform: common.Platform{Os: "os", Arch: "arch"}, 1225 }, 1226 }, 1227 Size: "123445", 1228 }, 1229 } 1230 1231 return imageListGQLResponse, nil 1232 } 1233 1234 func (service mockService) getImagesGQL(ctx context.Context, config SearchConfig, username, password string, 1235 imageName string, 1236 ) (*common.ImageListResponse, error) { 1237 if service.getImagesGQLFn != nil { 1238 return service.getImagesGQLFn(ctx, config, username, password, imageName) 1239 } 1240 1241 imageListGQLResponse := &common.ImageListResponse{} 1242 imageListGQLResponse.PaginatedImagesResult.Results = []common.ImageSummary{ 1243 { 1244 RepoName: "dummyImageName", 1245 Tag: "tag", 1246 MediaType: ispec.MediaTypeImageManifest, 1247 Digest: godigest.FromString("test").String(), 1248 Manifests: []common.ManifestSummary{ 1249 { 1250 Digest: godigest.FromString("Digest").String(), 1251 ConfigDigest: godigest.FromString("ConfigDigest").String(), 1252 Size: "123445", 1253 Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, 1254 Platform: common.Platform{Os: "os", Arch: "arch"}, 1255 }, 1256 }, 1257 Size: "123445", 1258 }, 1259 } 1260 1261 return imageListGQLResponse, nil 1262 } 1263 1264 func (service mockService) getImagesForDigestGQL(ctx context.Context, config SearchConfig, username, password string, 1265 digest string, 1266 ) (*common.ImagesForDigest, error) { 1267 if service.getImagesForDigestGQLFn != nil { 1268 return service.getImagesForDigestGQLFn(ctx, config, username, password, digest) 1269 } 1270 1271 imageListGQLResponse := &common.ImagesForDigest{} 1272 imageListGQLResponse.Results = []common.ImageSummary{ 1273 { 1274 RepoName: "randomimageName", 1275 Tag: "tag", 1276 MediaType: ispec.MediaTypeImageManifest, 1277 Digest: godigest.FromString("test").String(), 1278 Manifests: []common.ManifestSummary{ 1279 { 1280 Digest: godigest.FromString("Digest").String(), 1281 ConfigDigest: godigest.FromString("ConfigDigest").String(), 1282 Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, 1283 Size: "123445", 1284 Platform: common.Platform{Os: "os", Arch: "arch"}, 1285 }, 1286 }, 1287 Size: "123445", 1288 }, 1289 } 1290 1291 return imageListGQLResponse, nil 1292 } 1293 1294 func (service mockService) getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, 1295 imageName, cveID string, 1296 ) (*common.ImagesForCve, error) { 1297 if service.getTagsForCVEGQLFn != nil { 1298 return service.getTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID) 1299 } 1300 1301 images := &common.ImagesForCve{ 1302 Errors: nil, 1303 ImagesForCVEList: struct { 1304 common.PaginatedImagesResult `json:"ImageListForCVE"` //nolint:tagliatelle // graphQL schema 1305 }{}, 1306 } 1307 1308 if imageName == "" { 1309 imageName = "image-name" 1310 } 1311 1312 images.Errors = nil 1313 1314 mockedImage := service.getMockedImageByName(imageName) 1315 images.Results = []common.ImageSummary{common.ImageSummary(mockedImage)} 1316 1317 return images, nil 1318 } 1319 1320 func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, 1321 imageName, cveID string, 1322 ) (*common.ImageListWithCVEFixedResponse, error) { 1323 if service.getFixedTagsForCVEGQLFn != nil { 1324 return service.getFixedTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID) 1325 } 1326 1327 fixedTags := &common.ImageListWithCVEFixedResponse{ 1328 Errors: nil, 1329 ImageListWithCVEFixed: struct { 1330 common.PaginatedImagesResult `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema 1331 }{}, 1332 } 1333 1334 fixedTags.Errors = nil 1335 1336 mockedImage := service.getMockedImageByName(imageName) 1337 fixedTags.Results = []common.ImageSummary{common.ImageSummary(mockedImage)} 1338 1339 return fixedTags, nil 1340 } 1341 1342 func (service mockService) getCveByImageGQL(ctx context.Context, config SearchConfig, username, password, 1343 imageName, searchedCVE string, 1344 ) (*cveResult, error) { 1345 if service.getCveByImageGQLFn != nil { 1346 return service.getCveByImageGQLFn(ctx, config, username, password, imageName, searchedCVE) 1347 } 1348 cveRes := &cveResult{} 1349 cveRes.Data = cveData{ 1350 CVEListForImage: cveListForImage{ 1351 Tag: imageName, 1352 CVEList: []cve{ 1353 { 1354 ID: "dummyCVEID", 1355 Description: "Description of the CVE", 1356 Title: "Title of that CVE", 1357 Severity: "HIGH", 1358 PackageList: []packageList{ 1359 { 1360 Name: "packagename", 1361 FixedVersion: "fixedver", 1362 InstalledVersion: "installedver", 1363 }, 1364 }, 1365 }, 1366 }, 1367 Summary: common.ImageVulnerabilitySummary{ 1368 Count: 1, 1369 UnknownCount: 0, 1370 LowCount: 0, 1371 MediumCount: 0, 1372 HighCount: 1, 1373 CriticalCount: 0, 1374 MaxSeverity: "HIGH", 1375 }, 1376 }, 1377 } 1378 1379 return cveRes, nil 1380 } 1381 1382 //nolint:goconst 1383 func (service mockService) getMockedImageByName(imageName string) imageStruct { 1384 image := imageStruct{} 1385 image.RepoName = imageName 1386 image.Tag = "tag" 1387 image.MediaType = ispec.MediaTypeImageManifest 1388 image.Manifests = []common.ManifestSummary{ 1389 { 1390 Digest: godigest.FromString("Digest").String(), 1391 ConfigDigest: godigest.FromString("ConfigDigest").String(), 1392 Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, 1393 Size: "123445", 1394 Platform: common.Platform{Os: "os", Arch: "arch"}, 1395 }, 1396 } 1397 image.Size = "123445" 1398 1399 return image 1400 } 1401 1402 func (service mockService) getAllImages(ctx context.Context, config SearchConfig, username, password string, 1403 channel chan stringResult, wtgrp *sync.WaitGroup, 1404 ) { 1405 defer wtgrp.Done() 1406 defer close(channel) 1407 1408 if service.getAllImagesFn != nil { 1409 service.getAllImagesFn(ctx, config, username, password, channel, wtgrp) 1410 1411 return 1412 } 1413 1414 image := &imageStruct{} 1415 image.RepoName = "randomimageName" 1416 image.Tag = "tag" 1417 image.Digest = godigest.FromString("test").String() 1418 image.MediaType = ispec.MediaTypeImageManifest 1419 image.Manifests = []common.ManifestSummary{ 1420 { 1421 Digest: godigest.FromString("Digest").String(), 1422 ConfigDigest: godigest.FromString("ConfigDigest").String(), 1423 Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, 1424 Size: "123445", 1425 Platform: common.Platform{Os: "os", Arch: "arch"}, 1426 }, 1427 } 1428 image.Size = "123445" 1429 1430 str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose) 1431 if err != nil { 1432 channel <- stringResult{"", err} 1433 1434 return 1435 } 1436 1437 channel <- stringResult{str, nil} 1438 } 1439 1440 func (service mockService) getImageByName(ctx context.Context, config SearchConfig, 1441 username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup, 1442 ) { 1443 defer wtgrp.Done() 1444 defer close(channel) 1445 1446 if service.getImageByNameFn != nil { 1447 service.getImageByNameFn(ctx, config, username, password, imageName, channel, wtgrp) 1448 1449 return 1450 } 1451 1452 image := &imageStruct{} 1453 image.RepoName = imageName 1454 image.Tag = "tag" 1455 image.Digest = godigest.FromString("test").String() 1456 image.MediaType = ispec.MediaTypeImageManifest 1457 image.Manifests = []common.ManifestSummary{ 1458 { 1459 Digest: godigest.FromString("Digest").String(), 1460 ConfigDigest: godigest.FromString("ConfigDigest").String(), 1461 Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, 1462 Size: "123445", 1463 Platform: common.Platform{Os: "os", Arch: "arch"}, 1464 }, 1465 } 1466 image.Size = "123445" 1467 1468 str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose) 1469 if err != nil { 1470 channel <- stringResult{"", err} 1471 1472 return 1473 } 1474 1475 channel <- stringResult{str, nil} 1476 } 1477 1478 func (service mockService) getImagesByDigest(ctx context.Context, config SearchConfig, username, 1479 password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, 1480 ) { 1481 if service.getImagesByDigestFn != nil { 1482 defer wtgrp.Done() 1483 defer close(rch) 1484 1485 service.getImagesByDigestFn(ctx, config, username, password, digest, rch, wtgrp) 1486 1487 return 1488 } 1489 1490 service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp) 1491 } 1492 1493 func makeConfigFile(content string) string { 1494 os.Setenv("HOME", os.TempDir()) 1495 1496 home, err := os.UserHomeDir() 1497 if err != nil { 1498 panic(err) 1499 } 1500 1501 configPath := path.Join(home, "/.zot") 1502 1503 if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil { 1504 panic(err) 1505 } 1506 1507 return configPath 1508 } 1509 1510 func getTestSearchConfig(url string, searchService SearchService) SearchConfig { 1511 var ( 1512 user string 1513 outputFormat string 1514 verbose bool 1515 debug bool 1516 verifyTLS bool 1517 ) 1518 1519 return SearchConfig{ 1520 SearchService: searchService, 1521 SortBy: "alpha-asc", 1522 ServURL: url, 1523 User: user, 1524 OutputFormat: outputFormat, 1525 Verbose: verbose, 1526 Debug: debug, 1527 VerifyTLS: verifyTLS, 1528 ResultWriter: nil, 1529 } 1530 } 1531 1532 func removeLocalStorageContents(imageStore stypes.ImageStore) error { 1533 repos, err := imageStore.GetRepositories() 1534 if err != nil { 1535 return err 1536 } 1537 1538 for _, repo := range repos { 1539 // take just the first path 1540 err = os.RemoveAll(filepath.Join(imageStore.RootDir(), filepath.SplitList(repo)[0])) 1541 if err != nil { 1542 return err 1543 } 1544 } 1545 1546 return nil 1547 }