//go:build search // +build search package client import ( "bytes" "context" "errors" "fmt" "log" "os" "path" "path/filepath" "regexp" "strings" "sync" "testing" "time" godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" zerr "zotregistry.dev/zot/errors" "zotregistry.dev/zot/pkg/api" "zotregistry.dev/zot/pkg/api/config" "zotregistry.dev/zot/pkg/common" extconf "zotregistry.dev/zot/pkg/extensions/config" stypes "zotregistry.dev/zot/pkg/storage/types" test "zotregistry.dev/zot/pkg/test/common" . "zotregistry.dev/zot/pkg/test/image-utils" ) func TestSearchImageCmd(t *testing.T) { Convey("Test image help", t, func() { args := []string{"--help"} configPath := makeConfigFile("") defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(buff.String(), ShouldContainSubstring, "Usage") So(err, ShouldBeNil) Convey("with the shorthand", func() { args[0] = "-h" configPath := makeConfigFile("") defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(buff.String(), ShouldContainSubstring, "Usage") So(err, ShouldBeNil) }) }) Convey("Test image no url", t, func() { args := []string{"name", "dummyIdRandom", "--config", "imagetest"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) So(errors.Is(err, zerr.ErrNoURLProvided), ShouldBeTrue) }) Convey("Test image invalid home directory", t, func() { args := []string{"name", "dummyImageName", "--config", "imagetest"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) err := os.Setenv("HOME", "nonExistentDirectory") if err != nil { panic(err) } cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) home, err := os.UserHomeDir() if err != nil { panic(err) } err = os.Setenv("HOME", home) if err != nil { log.Fatal(err) } }) Convey("Test image no params", t, func() { args := []string{"--url", "someUrl"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldBeNil) }) Convey("Test image invalid url", t, func() { args := []string{"name", "dummyImageName", "--url", "invalidUrl"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(searchService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) So(strings.Contains(err.Error(), zerr.ErrInvalidURL.Error()), ShouldBeTrue) So(buff.String(), ShouldContainSubstring, "invalid URL format") }) Convey("Test image invalid url port", t, func() { args := []string{"name", "dummyImageName", "--url", "http://localhost:99999"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(searchService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) So(buff.String(), ShouldContainSubstring, "invalid port") Convey("without flags", func() { args := []string{"list", "--url", "http://localhost:99999"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(searchService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) So(buff.String(), ShouldContainSubstring, "invalid port") }) }) Convey("Test image unreachable", t, func() { args := []string{"name", "dummyImageName", "--url", "http://localhost:9999"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(searchService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) }) Convey("Test image url from config", t, func() { args := []string{"name", "dummyImageName", "--config", "imagetest"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So(strings.TrimSpace(str), ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB") So(err, ShouldBeNil) }) Convey("Test image by name", t, func() { args := []string{"name", "dummyImageName", "--url", "http://127.0.0.1:8080"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) defer os.Remove(configPath) imageCmd := NewImageCommand(new(mockService)) buff := &bytes.Buffer{} imageCmd.SetOut(buff) imageCmd.SetErr(buff) imageCmd.SetArgs(args) err := imageCmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So(strings.TrimSpace(str), ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB") So(err, ShouldBeNil) }) Convey("Test image by digest", t, func() { searchConfig := getTestSearchConfig("http://127.0.0.1:8080", new(mockService)) buff := &bytes.Buffer{} searchConfig.ResultWriter = buff err := SearchImagesByDigest(searchConfig, "6e2f80bf") So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So(strings.TrimSpace(str), ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB") So(err, ShouldBeNil) }) } func TestListRepos(t *testing.T) { searchConfig := getTestSearchConfig("https://test-url.com", new(mockService)) Convey("Test listing repositories", t, func() { buff := &bytes.Buffer{} searchConfig.ResultWriter = buff err := SearchRepos(searchConfig) So(err, ShouldBeNil) }) Convey("Test listing repositories with debug flag", t, func() { args := []string{"list", "--config", "config-test", "--debug"} configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewRepoCommand(new(searchService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") actual := strings.TrimSpace(str) So(actual, ShouldContainSubstring, "GET") }) Convey("Test error on home directory", t, func() { args := []string{"list", "--config", "config-test"} configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) err := os.Setenv("HOME", "nonExistentDirectory") if err != nil { panic(err) } cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) home, err := os.UserHomeDir() if err != nil { panic(err) } err = os.Setenv("HOME", home) if err != nil { log.Fatal(err) } }) Convey("Test listing repositories error", t, func() { args := []string{"list", "--config", "config-test"} configPath := makeConfigFile(`{"configs":[{"_name":"config-test", "url":"https://invalid.invalid","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewRepoCommand(new(searchService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) }) Convey("Test unable to get config value", t, func() { args := []string{"list", "--config", "config-test-nonexistent"} configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) }) Convey("Test error - no url provided", t, func() { args := []string{"list", "--config", "config-test"} configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) }) Convey("Test error - spinner config invalid", t, func() { args := []string{"list", "--config", "config-test"} configPath := makeConfigFile(`{"configs":[{"_name":"config-test", "url":"https://test-url.com","showspinner":invalid}]}`) defer os.Remove(configPath) cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) }) Convey("Test error - verifyTLSConfig fails", t, func() { args := []string{"list", "--config", "config-test"} configPath := makeConfigFile(`{"configs":[{"_name":"config-test", "verify-tls":"invalid", "url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) }) } func TestOutputFormat(t *testing.T) { Convey("Test text", t, func() { args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "text"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So(strings.TrimSpace(str), ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB") So(err, ShouldBeNil) }) Convey("Test json", t, func() { args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "json"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() // Output is supposed to be in json lines format, keep all spaces as is for verification So(buff.String(), ShouldEqual, `{"repoName":"dummyImageName","tag":"tag",`+ `"digest":"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",`+ `"mediaType":"application/vnd.oci.image.manifest.v1+json",`+ `"manifests":[{"digest":"sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6",`+ `"configDigest":"sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0",`+ `"lastUpdated":"0001-01-01T00:00:00Z","size":"123445","platform":{"os":"os","arch":"arch",`+ `"variant":""},"isSigned":false,"downloadCount":0,`+ `"layers":[{"size":"","digest":"sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6",`+ `"score":0}],"history":null,"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,`+ `"mediumCount":0,"highCount":0,"criticalCount":0,"count":0},`+ `"referrers":null,"artifactType":"","signatureInfo":null}],"size":"123445",`+ `"downloadCount":0,"lastUpdated":"0001-01-01T00:00:00Z","description":"","isSigned":false,"licenses":"",`+ `"labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",`+ `"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,"highCount":0,`+ `"criticalCount":0,"count":0},"referrers":null,"signatureInfo":null}`+"\n") So(err, ShouldBeNil) }) Convey("Test yaml", t, func() { args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "yaml"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So( strings.TrimSpace(str), ShouldEqual, `--- reponame: dummyImageName tag: tag `+ `digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+ `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - `+ `digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+ `configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+ `lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+ `issigned: false downloadcount: 0 layers: - size: "" `+ `digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+ `history: [] vulnerabilities: maxseverity: "" `+ `unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 `+ `referrers: [] artifacttype: "" `+ `signatureinfo: [] size: "123445" downloadcount: 0 `+ `lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+ `title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+ `unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+ `count: 0 referrers: [] signatureinfo: []`, ) So(err, ShouldBeNil) Convey("Test yml", func() { args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "yml"} configPath := makeConfigFile( `{"configs":[{"_name":"imagetest",` + `"url":"https://test-url.com","showspinner":false}]}`, ) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So( strings.TrimSpace(str), ShouldEqual, `--- reponame: dummyImageName tag: tag `+ `digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+ `mediatype: application/vnd.oci.image.manifest.v1+json `+ `manifests: - digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+ `configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+ `lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+ `issigned: false downloadcount: 0 layers: - size: "" `+ `digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+ `history: [] vulnerabilities: maxseverity: "" unknowncount: 0 lowcount: 0 mediumcount: 0 `+ `highcount: 0 criticalcount: 0 count: 0 referrers: [] artifacttype: "" `+ `signatureinfo: [] size: "123445" downloadcount: 0 `+ `lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+ `title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+ `unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+ `count: 0 referrers: [] signatureinfo: []`, ) So(err, ShouldBeNil) }) }) Convey("Test invalid", t, func() { args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "random"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) So(buff.String(), ShouldContainSubstring, "invalid cli output format") }) } func TestImagesCommandGQL(t *testing.T) { port := test.GetFreePort() baseURL := test.GetBaseURL(port) conf := config.New() conf.HTTP.Port = port defaultVal := true conf.Extensions = &extconf.ExtensionConfig{ Search: &extconf.SearchConfig{ BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, }, } ctlr := api.NewController(conf) ctlr.Config.Storage.RootDirectory = t.TempDir() cm := test.NewControllerManager(ctlr) cm.StartAndWait(conf.HTTP.Port) defer cm.StopServer() Convey("commands with gql", t, func() { err := removeLocalStorageContents(ctlr.StoreController.DefaultStore) So(err, ShouldBeNil) Convey("base and derived command", func() { baseImage := CreateImageWith().LayerBlobs( [][]byte{{1, 2, 3}, {11, 22, 33}}, ).DefaultConfig().Build() derivedImage := CreateImageWith().LayerBlobs( [][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}}, ).DefaultConfig().Build() err := UploadImage(baseImage, baseURL, "repo", "base") So(err, ShouldBeNil) err = UploadImage(derivedImage, baseURL, "repo", "derived") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"base", "repo:derived", "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") actual := strings.TrimSpace(str) So(actual, ShouldContainSubstring, "repo base linux/amd64 df554ddd false 699B") args = []string{"derived", "repo:base", "--config", "imagetest"} cmd = NewImageCommand(NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) str = space.ReplaceAllString(buff.String(), " ") actual = strings.TrimSpace(str) So(actual, ShouldContainSubstring, "repo derived linux/amd64 79f4b82e false 854B") }) Convey("base and derived command errors", func() { // too many parameters buff := bytes.NewBufferString("") args := []string{"too", "many", "args", "--config", "imagetest"} cmd := NewImageBaseCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) cmd = NewImageDerivedCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) // bad input buff = bytes.NewBufferString("") args = []string{"only-repo"} cmd = NewImageBaseCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) cmd = NewImageDerivedCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) // no url buff = bytes.NewBufferString("") args = []string{"repo:tag"} cmd = NewImageBaseCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) cmd = NewImageDerivedCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) Convey("digest command", func() { image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() err := UploadImage(image, baseURL, "repo", "img") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"digest", image.DigestStr(), "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") actual := strings.TrimSpace(str) So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B", image.DigestStr()[7:7+8])) }) Convey("digest command errors", func() { // too many parameters buff := bytes.NewBufferString("") args := []string{"too", "many", "args", "--config", "imagetest"} cmd := NewImageDigestCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) // bad input buff = bytes.NewBufferString("") args = []string{"bad-digest"} cmd = NewImageDigestCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) // no url buff = bytes.NewBufferString("") args = []string{godigest.FromString("str").String()} cmd = NewImageDigestCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) Convey("list command", func() { image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() err := UploadImage(image, baseURL, "repo", "img") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"list", "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") actual := strings.TrimSpace(str) fmt.Println(actual) So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B", image.DigestStr()[7:7+8])) fmt.Println(actual) }) Convey("list command errors", func() { // too many parameters buff := bytes.NewBufferString("") args := []string{"repo:img", "arg", "--config", "imagetest"} cmd := NewImageListCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) // no url buff = bytes.NewBufferString("") args = []string{} cmd = NewImageListCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) Convey("name command", func() { image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() err := UploadImage(image, baseURL, "repo", "img") So(err, ShouldBeNil) err = UploadImage(CreateRandomImage(), baseURL, "repo", "img2") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"name", "repo:img", "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") actual := strings.TrimSpace(str) fmt.Println(actual) So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B", image.DigestStr()[7:7+8])) fmt.Println(actual) }) Convey("name command errors", func() { // too many parameters buff := bytes.NewBufferString("") args := []string{"repo:img", "arg", "--config", "imagetest"} cmd := NewImageNameCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) // bad input buff = bytes.NewBufferString("") args = []string{":tag"} cmd = NewImageNameCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) // no url buff = bytes.NewBufferString("") args = []string{"repo:tag"} cmd = NewImageNameCommand(NewSearchService()) cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) Convey("CVE", func() { vulnImage := CreateDefaultVulnerableImage() err := UploadImage(vulnImage, baseURL, "repo", "vuln") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"cve", "repo:vuln", "--config", "imagetest"} cmd := NewImageCommand(mockService{}) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") actual := strings.TrimSpace(str) So(actual, ShouldContainSubstring, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1") So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE") }) Convey("CVE errors", func() { count := 0 configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"cve", "repo:vuln", "--config", "imagetest"} cmd := NewImageCommand(mockService{ getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string) (*cveResult, error, ) { if count == 0 { count++ fmt.Println("Count:", count) return &cveResult{}, zerr.ErrCVEDBNotFound } return &cveResult{}, zerr.ErrInjected }, }) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") actual := strings.TrimSpace(str) So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready") }) }) Convey("Config error", t, func() { args := []string{"base", "repo:derived", "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() So(err, ShouldNotBeNil) So(err, ShouldNotBeNil) args = []string{"derived", "repo:base"} cmd = NewImageCommand(NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) args = []string{"digest", ispec.DescriptorEmptyJSON.Digest.String()} cmd = NewImageCommand(NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) args = []string{"list"} cmd = NewImageCommand(NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) args = []string{"name", "repo:img"} cmd = NewImageCommand(NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) args = []string{"cve", "repo:vuln"} cmd = NewImageCommand(mockService{}) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) } func TestImageCommandREST(t *testing.T) { port := test.GetFreePort() baseURL := test.GetBaseURL(port) conf := config.New() conf.HTTP.Port = port ctlr := api.NewController(conf) ctlr.Config.Storage.RootDirectory = t.TempDir() cm := test.NewControllerManager(ctlr) cm.StartAndWait(conf.HTTP.Port) defer cm.StopServer() Convey("commands without gql", t, func() { err := removeLocalStorageContents(ctlr.StoreController.DefaultStore) So(err, ShouldBeNil) Convey("base and derived command", func() { baseImage := CreateImageWith().LayerBlobs( [][]byte{{1, 2, 3}, {11, 22, 33}}, ).DefaultConfig().Build() derivedImage := CreateImageWith().LayerBlobs( [][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}}, ).DefaultConfig().Build() err := UploadImage(baseImage, baseURL, "repo", "base") So(err, ShouldBeNil) err = UploadImage(derivedImage, baseURL, "repo", "derived") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"base", "repo:derived", "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) args = []string{"derived", "repo:base"} cmd = NewImageCommand(NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) Convey("digest command", func() { image := CreateRandomImage() err := UploadImage(image, baseURL, "repo", "img") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"digest", image.DigestStr(), "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) Convey("list command", func() { image := CreateRandomImage() err := UploadImage(image, baseURL, "repo", "img") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"list", "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) fmt.Println(buff.String()) fmt.Println() }) Convey("name command", func() { image := CreateRandomImage() err := UploadImage(image, baseURL, "repo", "img") So(err, ShouldBeNil) err = UploadImage(CreateRandomImage(), baseURL, "repo", "img2") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) defer os.Remove(configPath) args := []string{"name", "repo:img", "--config", "imagetest"} cmd := NewImageCommand(NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldBeNil) fmt.Println(buff.String()) fmt.Println() }) Convey("CVE", func() { vulnImage := CreateDefaultVulnerableImage() err := UploadImage(vulnImage, baseURL, "repo", "vuln") So(err, ShouldBeNil) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, baseURL)) args := []string{"cve", "repo:vuln", "--config", "imagetest"} defer os.Remove(configPath) cmd := NewImageCommand(mockService{}) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) cmd.SetArgs(args) err = cmd.Execute() So(err, ShouldNotBeNil) }) }) } type mockService struct { getAllImagesFn func(ctx context.Context, config SearchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup) getImagesGQLFn func(ctx context.Context, config SearchConfig, username, password string, imageName string) (*common.ImageListResponse, error) getImageByNameFn func(ctx context.Context, config SearchConfig, username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup, ) getImagesByDigestFn func(ctx context.Context, config SearchConfig, username, password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, ) getReferrersFn func(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (referrersResult, error) globalSearchGQLFn func(ctx context.Context, config SearchConfig, username, password string, query string, ) (*common.GlobalSearch, error) getReferrersGQLFn func(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (*common.ReferrersResp, error) getDerivedImageListGQLFn func(ctx context.Context, config SearchConfig, username, password string, derivedImage string, ) (*common.DerivedImageListResponse, error) getBaseImageListGQLFn func(ctx context.Context, config SearchConfig, username, password string, derivedImage string, ) (*common.BaseImageListResponse, error) getImagesForDigestGQLFn func(ctx context.Context, config SearchConfig, username, password string, digest string, ) (*common.ImagesForDigest, error) getCveByImageGQLFn func(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string, ) (*cveResult, error) getTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImagesForCve, error) getFixedTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImageListWithCVEFixedResponse, error) getCVEDiffListGQLFn func(ctx context.Context, config SearchConfig, username, password string, minuend, subtrahend ImageIdentifier, ) (*cveDiffListResp, error) } func (service mockService) getCVEDiffListGQL(ctx context.Context, config SearchConfig, username, password string, minuend, subtrahend ImageIdentifier, ) (*cveDiffListResp, error) { if service.getCVEDiffListGQLFn != nil { return service.getCVEDiffListGQLFn(ctx, config, username, password, minuend, subtrahend) } return &cveDiffListResp{}, nil } func (service mockService) getRepos(ctx context.Context, config SearchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() defer close(channel) fmt.Fprintln(config.ResultWriter, "\n\nREPOSITORY NAME") fmt.Fprintln(config.ResultWriter, "repo1") fmt.Fprintln(config.ResultWriter, "repo2") } func (service mockService) getReferrers(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (referrersResult, error) { if service.getReferrersFn != nil { return service.getReferrersFn(ctx, config, username, password, repo, digest) } return referrersResult{ common.Referrer{ ArtifactType: "art.type", Digest: ispec.DescriptorEmptyJSON.Digest.String(), MediaType: ispec.MediaTypeImageManifest, Size: 100, }, }, nil } func (service mockService) globalSearchGQL(ctx context.Context, config SearchConfig, username, password string, query string, ) (*common.GlobalSearch, error) { if service.globalSearchGQLFn != nil { return service.globalSearchGQLFn(ctx, config, username, password, query) } return &common.GlobalSearch{ Images: []common.ImageSummary{ { RepoName: "repo", MediaType: ispec.MediaTypeImageManifest, Size: "100", Manifests: []common.ManifestSummary{ { Digest: godigest.FromString("str").String(), Size: "100", ConfigDigest: ispec.DescriptorEmptyJSON.Digest.String(), }, }, }, }, Repos: []common.RepoSummary{ { Name: "repo", Size: "100", LastUpdated: time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC), }, }, }, nil } func (service mockService) getReferrersGQL(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (*common.ReferrersResp, error) { if service.getReferrersGQLFn != nil { return service.getReferrersGQLFn(ctx, config, username, password, repo, digest) } return &common.ReferrersResp{ ReferrersResult: common.ReferrersResult{ Referrers: []common.Referrer{ { MediaType: "MediaType", ArtifactType: "ArtifactType", Size: 100, Digest: "Digest", }, }, }, }, nil } func (service mockService) getDerivedImageListGQL(ctx context.Context, config SearchConfig, username, password string, derivedImage string, ) (*common.DerivedImageListResponse, error) { if service.getDerivedImageListGQLFn != nil { return service.getDerivedImageListGQLFn(ctx, config, username, password, derivedImage) } imageListGQLResponse := &common.DerivedImageListResponse{} imageListGQLResponse.DerivedImageList.Results = []common.ImageSummary{ { RepoName: "dummyImageName", Tag: "tag", Manifests: []common.ManifestSummary{ { Digest: godigest.FromString("Digest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(), Size: "123445", Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, Platform: common.Platform{Os: "os", Arch: "arch"}, }, }, Size: "123445", }, } return imageListGQLResponse, nil } func (service mockService) getBaseImageListGQL(ctx context.Context, config SearchConfig, username, password string, baseImage string, ) (*common.BaseImageListResponse, error) { if service.getBaseImageListGQLFn != nil { return service.getBaseImageListGQLFn(ctx, config, username, password, baseImage) } imageListGQLResponse := &common.BaseImageListResponse{} imageListGQLResponse.BaseImageList.Results = []common.ImageSummary{ { RepoName: "dummyImageName", Tag: "tag", Manifests: []common.ManifestSummary{ { Digest: godigest.FromString("Digest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(), Size: "123445", Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, Platform: common.Platform{Os: "os", Arch: "arch"}, }, }, Size: "123445", }, } return imageListGQLResponse, nil } func (service mockService) getImagesGQL(ctx context.Context, config SearchConfig, username, password string, imageName string, ) (*common.ImageListResponse, error) { if service.getImagesGQLFn != nil { return service.getImagesGQLFn(ctx, config, username, password, imageName) } imageListGQLResponse := &common.ImageListResponse{} imageListGQLResponse.PaginatedImagesResult.Results = []common.ImageSummary{ { RepoName: "dummyImageName", Tag: "tag", MediaType: ispec.MediaTypeImageManifest, Digest: godigest.FromString("test").String(), Manifests: []common.ManifestSummary{ { Digest: godigest.FromString("Digest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(), Size: "123445", Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, Platform: common.Platform{Os: "os", Arch: "arch"}, }, }, Size: "123445", }, } return imageListGQLResponse, nil } func (service mockService) getImagesForDigestGQL(ctx context.Context, config SearchConfig, username, password string, digest string, ) (*common.ImagesForDigest, error) { if service.getImagesForDigestGQLFn != nil { return service.getImagesForDigestGQLFn(ctx, config, username, password, digest) } imageListGQLResponse := &common.ImagesForDigest{} imageListGQLResponse.Results = []common.ImageSummary{ { RepoName: "randomimageName", Tag: "tag", MediaType: ispec.MediaTypeImageManifest, Digest: godigest.FromString("test").String(), Manifests: []common.ManifestSummary{ { Digest: godigest.FromString("Digest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(), Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, Size: "123445", Platform: common.Platform{Os: "os", Arch: "arch"}, }, }, Size: "123445", }, } return imageListGQLResponse, nil } func (service mockService) getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImagesForCve, error) { if service.getTagsForCVEGQLFn != nil { return service.getTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID) } images := &common.ImagesForCve{ Errors: nil, ImagesForCVEList: struct { common.PaginatedImagesResult `json:"ImageListForCVE"` //nolint:tagliatelle // graphQL schema }{}, } if imageName == "" { imageName = "image-name" } images.Errors = nil mockedImage := service.getMockedImageByName(imageName) images.Results = []common.ImageSummary{common.ImageSummary(mockedImage)} return images, nil } func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImageListWithCVEFixedResponse, error) { if service.getFixedTagsForCVEGQLFn != nil { return service.getFixedTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID) } fixedTags := &common.ImageListWithCVEFixedResponse{ Errors: nil, ImageListWithCVEFixed: struct { common.PaginatedImagesResult `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema }{}, } fixedTags.Errors = nil mockedImage := service.getMockedImageByName(imageName) fixedTags.Results = []common.ImageSummary{common.ImageSummary(mockedImage)} return fixedTags, nil } func (service mockService) getCveByImageGQL(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string, ) (*cveResult, error) { if service.getCveByImageGQLFn != nil { return service.getCveByImageGQLFn(ctx, config, username, password, imageName, searchedCVE) } cveRes := &cveResult{} cveRes.Data = cveData{ CVEListForImage: cveListForImage{ Tag: imageName, CVEList: []cve{ { ID: "dummyCVEID", Description: "Description of the CVE", Title: "Title of that CVE", Severity: "HIGH", PackageList: []packageList{ { Name: "packagename", FixedVersion: "fixedver", InstalledVersion: "installedver", }, }, }, }, Summary: common.ImageVulnerabilitySummary{ Count: 1, UnknownCount: 0, LowCount: 0, MediumCount: 0, HighCount: 1, CriticalCount: 0, MaxSeverity: "HIGH", }, }, } return cveRes, nil } //nolint:goconst func (service mockService) getMockedImageByName(imageName string) imageStruct { image := imageStruct{} image.RepoName = imageName image.Tag = "tag" image.MediaType = ispec.MediaTypeImageManifest image.Manifests = []common.ManifestSummary{ { Digest: godigest.FromString("Digest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(), Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, Size: "123445", Platform: common.Platform{Os: "os", Arch: "arch"}, }, } image.Size = "123445" return image } func (service mockService) getAllImages(ctx context.Context, config SearchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() defer close(channel) if service.getAllImagesFn != nil { service.getAllImagesFn(ctx, config, username, password, channel, wtgrp) return } image := &imageStruct{} image.RepoName = "randomimageName" image.Tag = "tag" image.Digest = godigest.FromString("test").String() image.MediaType = ispec.MediaTypeImageManifest image.Manifests = []common.ManifestSummary{ { Digest: godigest.FromString("Digest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(), Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, Size: "123445", Platform: common.Platform{Os: "os", Arch: "arch"}, }, } image.Size = "123445" str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose) if err != nil { channel <- stringResult{"", err} return } channel <- stringResult{str, nil} } func (service mockService) getImageByName(ctx context.Context, config SearchConfig, username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() defer close(channel) if service.getImageByNameFn != nil { service.getImageByNameFn(ctx, config, username, password, imageName, channel, wtgrp) return } image := &imageStruct{} image.RepoName = imageName image.Tag = "tag" image.Digest = godigest.FromString("test").String() image.MediaType = ispec.MediaTypeImageManifest image.Manifests = []common.ManifestSummary{ { Digest: godigest.FromString("Digest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(), Layers: []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}}, Size: "123445", Platform: common.Platform{Os: "os", Arch: "arch"}, }, } image.Size = "123445" str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose) if err != nil { channel <- stringResult{"", err} return } channel <- stringResult{str, nil} } func (service mockService) getImagesByDigest(ctx context.Context, config SearchConfig, username, password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { if service.getImagesByDigestFn != nil { defer wtgrp.Done() defer close(rch) service.getImagesByDigestFn(ctx, config, username, password, digest, rch, wtgrp) return } service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp) } func makeConfigFile(content string) string { os.Setenv("HOME", os.TempDir()) home, err := os.UserHomeDir() if err != nil { panic(err) } configPath := path.Join(home, "/.zot") if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil { panic(err) } return configPath } func getTestSearchConfig(url string, searchService SearchService) SearchConfig { var ( user string outputFormat string verbose bool debug bool verifyTLS bool ) return SearchConfig{ SearchService: searchService, SortBy: "alpha-asc", ServURL: url, User: user, OutputFormat: outputFormat, Verbose: verbose, Debug: debug, VerifyTLS: verifyTLS, ResultWriter: nil, } } func removeLocalStorageContents(imageStore stypes.ImageStore) error { repos, err := imageStore.GetRepositories() if err != nil { return err } for _, repo := range repos { // take just the first path err = os.RemoveAll(filepath.Join(imageStore.RootDir(), filepath.SplitList(repo)[0])) if err != nil { return err } } return nil }