From ca1c3288cfa7c557b1a2c55a127f57de6f37b314 Mon Sep 17 00:00:00 2001 From: Andrei Aaron Date: Tue, 3 Oct 2023 21:15:39 +0300 Subject: [PATCH] refactor(test): make sure cli tests are not internal unless they need to be (#1878) As part of this change searchConfig needed to be exported, as it was passed as a parameter to exported functions At this moment most of the tests remaining internal depend on the mock service. The interface it implements has unexported methods. Signed-off-by: Andrei Aaron --- pkg/cli/client/client.go | 40 +- .../{internal_test.go => client_test.go} | 31 +- ...md_internal_test.go => config_cmd_test.go} | 57 +- pkg/cli/client/cve_cmd_internal_test.go | 782 +--------- pkg/cli/client/cve_cmd_test.go | 787 ++++++++++ pkg/cli/client/discover.go | 16 +- ...ated_internal_test.go => elevated_test.go} | 5 +- ...s_internal_test.go => gql_queries_test.go} | 33 +- pkg/cli/client/image_cmd_internal_test.go | 1303 +---------------- pkg/cli/client/image_cmd_test.go | 1260 ++++++++++++++++ .../{repo_internal_test.go => repo_test.go} | 15 +- pkg/cli/client/search_cmd_internal_test.go | 774 ---------- pkg/cli/client/search_cmd_test.go | 796 ++++++++++ pkg/cli/client/search_functions.go | 110 +- .../client/search_functions_internal_test.go | 94 +- pkg/cli/client/service.go | 140 +- pkg/cli/client/utils.go | 86 +- pkg/cli/client/utils_internal_test.go | 16 +- 18 files changed, 3227 insertions(+), 3118 deletions(-) rename pkg/cli/client/{internal_test.go => client_test.go} (90%) rename pkg/cli/client/{config_cmd_internal_test.go => config_cmd_test.go} (92%) create mode 100644 pkg/cli/client/cve_cmd_test.go rename pkg/cli/client/{elevated_internal_test.go => elevated_test.go} (95%) rename pkg/cli/client/{gql_queries_internal_test.go => gql_queries_test.go} (60%) create mode 100644 pkg/cli/client/image_cmd_test.go rename pkg/cli/client/{repo_internal_test.go => repo_test.go} (85%) create mode 100644 pkg/cli/client/search_cmd_test.go diff --git a/pkg/cli/client/client.go b/pkg/cli/client/client.go index fc74d77c..da867ab2 100644 --- a/pkg/cli/client/client.go +++ b/pkg/cli/client/client.go @@ -179,7 +179,7 @@ type httpJob struct { password string imageName string tagName string - config searchConfig + config SearchConfig } const rateLimiterBuffer = 5000 @@ -218,8 +218,8 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) { defer p.wtgrp.Done() // Check manifest media type - header, err := makeHEADRequest(ctx, job.url, job.username, job.password, job.config.verifyTLS, - job.config.debug) + header, err := makeHEADRequest(ctx, job.url, job.username, job.password, job.config.VerifyTLS, + job.config.Debug) if err != nil { if isContextDone(ctx) { return @@ -227,7 +227,7 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) { p.outputCh <- stringResult{"", err} } - verbose := job.config.verbose + verbose := job.config.Verbose switch header.Get("Content-Type") { case ispec.MediaTypeImageManifest: @@ -242,7 +242,7 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) { } platformStr := getPlatformStr(image.Manifests[0].Platform) - str, err := image.string(job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose) + str, err := image.string(job.config.OutputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose) if err != nil { if isContextDone(ctx) { return @@ -270,7 +270,7 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) { platformStr := getPlatformStr(image.Manifests[0].Platform) - str, err := image.string(job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose) + str, err := image.string(job.config.OutputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose) if err != nil { if isContextDone(ctx) { return @@ -294,7 +294,7 @@ func fetchImageIndexStruct(ctx context.Context, job *httpJob) (*imageStruct, err var indexContent ispec.Index header, err := makeGETRequest(ctx, job.url, job.username, job.password, - job.config.verifyTLS, job.config.debug, &indexContent, job.config.resultWriter) + job.config.VerifyTLS, job.config.Debug, &indexContent, job.config.ResultWriter) if err != nil { if isContextDone(ctx) { return nil, context.Canceled @@ -376,16 +376,16 @@ func fetchImageManifestStruct(ctx context.Context, job *httpJob) (*imageStruct, }, nil } -func fetchManifestStruct(ctx context.Context, repo, manifestReference string, searchConf searchConfig, +func fetchManifestStruct(ctx context.Context, repo, manifestReference string, searchConf SearchConfig, username, password string, ) (common.ManifestSummary, error) { manifestResp := ispec.Manifest{} URL := fmt.Sprintf("%s/v2/%s/manifests/%s", - searchConf.servURL, repo, manifestReference) + searchConf.ServURL, repo, manifestReference) header, err := makeGETRequest(ctx, URL, username, password, - searchConf.verifyTLS, searchConf.debug, &manifestResp, searchConf.resultWriter) + searchConf.VerifyTLS, searchConf.Debug, &manifestResp, searchConf.ResultWriter) if err != nil { if isContextDone(ctx) { return common.ManifestSummary{}, context.Canceled @@ -465,16 +465,16 @@ func fetchManifestStruct(ctx context.Context, repo, manifestReference string, se }, nil } -func fetchConfig(ctx context.Context, repo, configDigest string, searchConf searchConfig, +func fetchConfig(ctx context.Context, repo, configDigest string, searchConf SearchConfig, username, password string, ) (ispec.Image, error) { configContent := ispec.Image{} URL := fmt.Sprintf("%s/v2/%s/blobs/%s", - searchConf.servURL, repo, configDigest) + searchConf.ServURL, repo, configDigest) _, err := makeGETRequest(ctx, URL, username, password, - searchConf.verifyTLS, searchConf.debug, &configContent, searchConf.resultWriter) + searchConf.VerifyTLS, searchConf.Debug, &configContent, searchConf.ResultWriter) if err != nil { if isContextDone(ctx) { return ispec.Image{}, context.Canceled @@ -486,16 +486,16 @@ func fetchConfig(ctx context.Context, repo, configDigest string, searchConf sear return configContent, nil } -func isNotationSigned(ctx context.Context, repo, digestStr string, searchConf searchConfig, +func isNotationSigned(ctx context.Context, repo, digestStr string, searchConf SearchConfig, username, password string, ) bool { var referrers ispec.Index URL := fmt.Sprintf("%s/v2/%s/referrers/%s?artifactType=%s", - searchConf.servURL, repo, digestStr, common.ArtifactTypeNotation) + searchConf.ServURL, repo, digestStr, common.ArtifactTypeNotation) _, err := makeGETRequest(ctx, URL, username, password, - searchConf.verifyTLS, searchConf.debug, &referrers, searchConf.resultWriter) + searchConf.VerifyTLS, searchConf.Debug, &referrers, searchConf.ResultWriter) if err != nil { return false } @@ -507,16 +507,16 @@ func isNotationSigned(ctx context.Context, repo, digestStr string, searchConf se return false } -func isCosignSigned(ctx context.Context, repo, digestStr string, searchConf searchConfig, +func isCosignSigned(ctx context.Context, repo, digestStr string, searchConf SearchConfig, username, password string, ) bool { var result interface{} cosignTag := strings.Replace(digestStr, ":", "-", 1) + "." + remote.SignatureTagSuffix - URL := fmt.Sprintf("%s/v2/%s/manifests/%s", searchConf.servURL, repo, cosignTag) + URL := fmt.Sprintf("%s/v2/%s/manifests/%s", searchConf.ServURL, repo, cosignTag) - _, err := makeGETRequest(ctx, URL, username, password, searchConf.verifyTLS, - searchConf.debug, &result, searchConf.resultWriter) + _, err := makeGETRequest(ctx, URL, username, password, searchConf.VerifyTLS, + searchConf.Debug, &result, searchConf.ResultWriter) return err == nil } diff --git a/pkg/cli/client/internal_test.go b/pkg/cli/client/client_test.go similarity index 90% rename from pkg/cli/client/internal_test.go rename to pkg/cli/client/client_test.go index 04f50ed2..6192d441 100644 --- a/pkg/cli/client/internal_test.go +++ b/pkg/cli/client/client_test.go @@ -1,7 +1,7 @@ //go:build search // +build search -package client +package client_test import ( "bytes" @@ -9,6 +9,7 @@ import ( "crypto/x509" "fmt" "os" + "path" "path/filepath" "testing" @@ -18,6 +19,7 @@ import ( "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/constants" + "zotregistry.io/zot/pkg/cli/client" extConf "zotregistry.io/zot/pkg/extensions/config" test "zotregistry.io/zot/pkg/test/common" ) @@ -91,7 +93,7 @@ func TestTLSWithAuth(t *testing.T) { defer os.RemoveAll(destCertsDir) args := []string{"name", "dummyImageName", "--url", HOST1} - imageCmd := NewImageCommand(new(searchService)) + imageCmd := client.NewImageCommand(client.NewSearchService()) imageBuff := bytes.NewBufferString("") imageCmd.SetOut(imageBuff) imageCmd.SetErr(imageBuff) @@ -105,7 +107,7 @@ func TestTLSWithAuth(t *testing.T) { fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`, BaseSecureURL1, constants.RoutePrefix, constants.ExtCatalogPrefix)) defer os.Remove(configPath) - imageCmd = NewImageCommand(new(searchService)) + imageCmd = client.NewImageCommand(client.NewSearchService()) imageBuff = bytes.NewBufferString("") imageCmd.SetOut(imageBuff) imageCmd.SetErr(imageBuff) @@ -120,7 +122,7 @@ func TestTLSWithAuth(t *testing.T) { fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`, BaseSecureURL1, constants.RoutePrefix, constants.ExtCatalogPrefix)) defer os.Remove(configPath) - imageCmd = NewImageCommand(new(searchService)) + imageCmd = client.NewImageCommand(client.NewSearchService()) imageBuff = bytes.NewBufferString("") imageCmd.SetOut(imageBuff) imageCmd.SetErr(imageBuff) @@ -174,7 +176,7 @@ func TestTLSWithoutAuth(t *testing.T) { defer os.RemoveAll(destCertsDir) args := []string{"list", "--config", "imagetest"} - imageCmd := NewImageCommand(new(searchService)) + imageCmd := client.NewImageCommand(client.NewSearchService()) imageBuff := bytes.NewBufferString("") imageCmd.SetOut(imageBuff) imageCmd.SetErr(imageBuff) @@ -215,7 +217,7 @@ func TestTLSBadCerts(t *testing.T) { defer os.Remove(configPath) args := []string{"list", "--config", "imagetest"} - imageCmd := NewImageCommand(new(searchService)) + imageCmd := client.NewImageCommand(client.NewSearchService()) imageBuff := bytes.NewBufferString("") imageCmd.SetOut(imageBuff) imageCmd.SetErr(imageBuff) @@ -226,3 +228,20 @@ func TestTLSBadCerts(t *testing.T) { }) }) } + +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 +} diff --git a/pkg/cli/client/config_cmd_internal_test.go b/pkg/cli/client/config_cmd_test.go similarity index 92% rename from pkg/cli/client/config_cmd_internal_test.go rename to pkg/cli/client/config_cmd_test.go index 7ba519d2..28e82592 100644 --- a/pkg/cli/client/config_cmd_internal_test.go +++ b/pkg/cli/client/config_cmd_test.go @@ -1,7 +1,7 @@ //go:build search // +build search -package client +package client_test import ( "bytes" @@ -14,6 +14,7 @@ import ( . "github.com/smartystreets/goconvey/convey" zerr "zotregistry.io/zot/errors" + "zotregistry.io/zot/pkg/cli/client" ) func TestConfigCmdBasics(t *testing.T) { @@ -21,7 +22,7 @@ func TestConfigCmdBasics(t *testing.T) { args := []string{"--help"} configPath := makeConfigFile("showspinner = false") defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -33,7 +34,7 @@ func TestConfigCmdBasics(t *testing.T) { args[0] = "-h" configPath := makeConfigFile("showspinner = false") defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -48,7 +49,7 @@ func TestConfigCmdBasics(t *testing.T) { args := []string{} configPath := makeConfigFile("showspinner = false") defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -64,7 +65,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"add", "configtest1", "https://test-url.com"} file := makeConfigFile("") defer os.Remove(file) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -90,7 +91,7 @@ func TestConfigCmdMain(t *testing.T) { panic(err) } - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -118,7 +119,7 @@ func TestConfigCmdMain(t *testing.T) { panic(err) } - cmd := NewConfigAddCommand() + cmd := client.NewConfigAddCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -140,7 +141,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"--list"} configPath := makeConfigFile(`{"configs":{"_name":"configtest","url":"https://test-url.com","showspinner":false}}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -153,7 +154,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"add", "configtest1", "test..com"} file := makeConfigFile("") defer os.Remove(file) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -167,7 +168,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"remove", "configtest"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -185,7 +186,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"remove", "configtest"} configPath := makeConfigFile(`{"configs":[]`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -199,7 +200,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"remove", "configtest"} configPath := makeConfigFile(`{"asdf":[]`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -213,7 +214,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"remove", "configtest"} configPath := makeConfigFile(`{"configs":[asdad]`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -232,7 +233,7 @@ func TestConfigCmdMain(t *testing.T) { }() err := os.Chmod(configPath, 0o400) // Read-only, so we fail only on updating the file, not reading So(err, ShouldBeNil) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -246,7 +247,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"--list"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -259,7 +260,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"-l"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -273,7 +274,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"-l"} configPath := makeConfigFile(``) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -288,7 +289,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "--list"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -302,7 +303,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "-l"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -317,7 +318,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "-l"} configPath := makeConfigFile(``) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -332,7 +333,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "url"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -345,7 +346,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "url"} configPath := makeConfigFile(``) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -360,7 +361,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "showspinner", "false"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com"}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -381,7 +382,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "showspinner", "false"} configPath := makeConfigFile(``) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -396,7 +397,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "url", "https://new-url.com"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -419,7 +420,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "showspinner", "--reset"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -441,7 +442,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"configtest", "url", "--reset"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -455,7 +456,7 @@ func TestConfigCmdMain(t *testing.T) { args := []string{"add", "configtest", "https://test-url.com/new"} configPath := makeConfigFile(`{"configs":[{"_name":"configtest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewConfigCommand() + cmd := client.NewConfigCommand() buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) diff --git a/pkg/cli/client/cve_cmd_internal_test.go b/pkg/cli/client/cve_cmd_internal_test.go index 554d6dda..5728cbd9 100644 --- a/pkg/cli/client/cve_cmd_internal_test.go +++ b/pkg/cli/client/cve_cmd_internal_test.go @@ -6,22 +6,14 @@ package client import ( "bytes" "context" - "encoding/json" "errors" "fmt" - "io" - "net/http" "os" - "path" "regexp" "strconv" "strings" "testing" - "time" - regTypes "github.com/google/go-containerregistry/pkg/v1/types" - godigest "github.com/opencontainers/go-digest" - ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" zerr "zotregistry.io/zot/errors" @@ -29,18 +21,7 @@ import ( "zotregistry.io/zot/pkg/api/config" zcommon "zotregistry.io/zot/pkg/common" extconf "zotregistry.io/zot/pkg/extensions/config" - "zotregistry.io/zot/pkg/extensions/monitoring" - cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" - cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model" - "zotregistry.io/zot/pkg/log" - mTypes "zotregistry.io/zot/pkg/meta/types" - "zotregistry.io/zot/pkg/storage" - "zotregistry.io/zot/pkg/storage/local" test "zotregistry.io/zot/pkg/test/common" - "zotregistry.io/zot/pkg/test/deprecated" - . "zotregistry.io/zot/pkg/test/image-utils" - "zotregistry.io/zot/pkg/test/mocks" - ociutils "zotregistry.io/zot/pkg/test/oci-utils" ) func TestSearchCVECmd(t *testing.T) { @@ -397,659 +378,6 @@ func TestSearchCVECmd(t *testing.T) { }) } -func TestNegativeServerResponse(t *testing.T) { - Convey("Test from real server without search endpoint", t, func() { - port := test.GetFreePort() - url := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - - dir := t.TempDir() - - srcStorageCtlr := ociutils.GetDefaultStoreController(dir, log.NewLogger("debug", "")) - err := WriteImageToFileSystem(CreateDefaultVulnerableImage(), "zot-cve-test", "0.0.1", srcStorageCtlr) - So(err, ShouldBeNil) - - conf.Storage.RootDirectory = dir - trivyConfig := &extconf.TrivyConfig{ - DBRepository: "ghcr.io/project-zot/trivy-db", - } - cveConfig := &extconf.CVEConfig{ - UpdateInterval: 2, - Trivy: trivyConfig, - } - defaultVal := false - searchConfig := &extconf.SearchConfig{ - BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, - CVE: cveConfig, - } - conf.Extensions = &extconf.ExtensionConfig{ - Search: searchConfig, - } - - logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") - if err != nil { - panic(err) - } - - logPath := logFile.Name() - defer os.Remove(logPath) - - writers := io.MultiWriter(os.Stdout, logFile) - - ctlr := api.NewController(conf) - ctlr.Log.Logger = ctlr.Log.Output(writers) - - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - _, err = test.ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second) - if err != nil { - panic(err) - } - - Convey("Status Code Not Found", func() { - args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - So(err, ShouldNotBeNil) - So(err.Error(), ShouldContainSubstring, zerr.ErrExtensionNotEnabled.Error()) - }) - }) - - Convey("Test non-existing manifest blob", t, func() { - port := test.GetFreePort() - url := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - - dir := t.TempDir() - - imageStore := local.NewImageStore(dir, false, false, - log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil) - - storeController := storage.StoreController{ - DefaultStore: imageStore, - } - - num := 10 - config, layers, manifest, err := deprecated.GetRandomImageComponents(num) //nolint:staticcheck - So(err, ShouldBeNil) - - err = WriteImageToFileSystem( - Image{ - Manifest: manifest, - Layers: layers, - Config: config, - }, "zot-cve-test", "0.0.1", storeController, - ) - So(err, ShouldBeNil) - - err = os.RemoveAll(path.Join(dir, "zot-cve-test/blobs")) - if err != nil { - panic(err) - } - - conf.Storage.RootDirectory = dir - trivyConfig := &extconf.TrivyConfig{ - DBRepository: "ghcr.io/project-zot/trivy-db", - } - cveConfig := &extconf.CVEConfig{ - UpdateInterval: 2, - Trivy: trivyConfig, - } - defaultVal := true - searchConfig := &extconf.SearchConfig{ - BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, - CVE: cveConfig, - } - conf.Extensions = &extconf.ExtensionConfig{ - Search: searchConfig, - } - - logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") - if err != nil { - panic(err) - } - - logPath := logFile.Name() - defer os.Remove(logPath) - - writers := io.MultiWriter(os.Stdout, logFile) - - ctlr := api.NewController(conf) - ctlr.Log.Logger = ctlr.Log.Output(writers) - - ctx := context.Background() - - if err := ctlr.Init(ctx); err != nil { - panic(err) - } - - ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) - - go func() { - if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { - panic(err) - } - }() - - defer ctlr.Shutdown() - - test.WaitTillServerReady(url) - - _, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second) - if err != nil { - panic(err) - } - - args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - So(err, ShouldNotBeNil) - }) -} - -//nolint:dupl -func TestServerCVEResponse(t *testing.T) { - port := test.GetFreePort() - url := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - - dir := t.TempDir() - - conf.Storage.RootDirectory = dir - trivyConfig := &extconf.TrivyConfig{ - DBRepository: "ghcr.io/project-zot/trivy-db", - } - cveConfig := &extconf.CVEConfig{ - UpdateInterval: 2, - Trivy: trivyConfig, - } - defaultVal := true - searchConfig := &extconf.SearchConfig{ - BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, - CVE: cveConfig, - } - conf.Extensions = &extconf.ExtensionConfig{ - Search: searchConfig, - } - - logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") - if err != nil { - panic(err) - } - - logPath := logFile.Name() - defer os.Remove(logPath) - - writers := io.MultiWriter(os.Stdout, logFile) - - ctlr := api.NewController(conf) - ctlr.Log.Logger = ctlr.Log.Output(writers) - - ctx := context.Background() - - if err := ctlr.Init(ctx); err != nil { - panic(err) - } - - ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) - - go func() { - if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { - panic(err) - } - }() - - defer ctlr.Shutdown() - - test.WaitTillServerReady(url) - - config, layers, manifest, err := deprecated.GetImageComponents(100) //nolint:staticcheck - if err != nil { - panic(err) - } - - err = UploadImage(Image{Manifest: manifest, Config: config, Layers: layers}, url, "zot-cve-test", "0.0.1") - if err != nil { - panic(err) - } - - _, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second) - if err != nil { - panic(err) - } - - Convey("Test CVE by image name - GQL - positive", t, func() { - args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(str, ShouldContainSubstring, "ID SEVERITY TITLE") - So(str, ShouldContainSubstring, "CVE") - }) - - Convey("Test CVE by image name - GQL - search CVE by title in results", t, func() { - args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-C1", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(str, ShouldContainSubstring, "ID SEVERITY TITLE") - So(str, ShouldContainSubstring, "CVE-C1") - So(str, ShouldNotContainSubstring, "CVE-2") - }) - - Convey("Test CVE by image name - GQL - search CVE by id in results", t, func() { - args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-2", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(str, ShouldContainSubstring, "ID SEVERITY TITLE") - So(str, ShouldContainSubstring, "CVE-2") - So(str, ShouldNotContainSubstring, "CVE-1") - }) - - Convey("Test CVE by image name - GQL - search nonexistent CVE", t, func() { - args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-100", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(str, ShouldContainSubstring, "No CVEs found for image") - }) - - Convey("Test CVE by image name - GQL - invalid image", t, func() { - args := []string{"list", "invalid:0.0.1", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("Test CVE by image name - GQL - invalid image name and tag", t, func() { - args := []string{"list", "invalid:", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("Test CVE by image name - GQL - invalid output format", t, func() { - args := []string{"list", "zot-cve-test:0.0.1", "-f", "random", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - So(err, ShouldNotBeNil) - So(buff.String(), ShouldContainSubstring, "invalid output format") - }) - - Convey("Test images by CVE ID - GQL - positive", t, func() { - args := []string{"affected", "CVE-2019-9923", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(str, ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 40d1f749 false 605B") - }) - - Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() { - args := []string{"affected", "CVE-invalid", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(str, ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - }) - - Convey("Test images by CVE ID - GQL - invalid output format", t, func() { - args := []string{"affected", "CVE-2019-9923", "-f", "random", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - So(err, ShouldNotBeNil) - So(buff.String(), ShouldContainSubstring, "invalid output format") - }) - - Convey("Test fixed tags by image name and CVE ID - GQL - positive", t, func() { - args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(str, ShouldEqual, "") - }) - - Convey("Test fixed tags by image name and CVE ID - GQL - random cve", t, func() { - args := []string{"fixed", "zot-cve-test", "random", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldBeNil) - So(strings.TrimSpace(str), ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - }) - - Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() { - args := []string{"fixed", "zot-cv-test", "CVE-2019-20807", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldNotBeNil) - So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - }) - - Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() { - args := []string{"fixed", "zot-cv-test:tag", "CVE-2019-20807", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - str = strings.TrimSpace(str) - So(err, ShouldNotBeNil) - So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - }) - - Convey("Test CVE by name and CVE ID - GQL - positive", t, func() { - args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - So(err, ShouldBeNil) - So(strings.TrimSpace(str), ShouldEqual, - "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 40d1f749 false 605B") - }) - - Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() { - args := []string{"affected", "CVE-20807", "--repo", "test", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err := cveCmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - So(err, ShouldBeNil) - So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH SIGNED SIZE") - }) - - Convey("Test CVE by name and CVE ID - GQL - invalid output format", t, func() { - args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "-f", "random", "--config", "cvetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cveCmd := NewCVECommand(new(searchService)) - buff := bytes.NewBufferString("") - cveCmd.SetOut(buff) - cveCmd.SetErr(buff) - cveCmd.SetArgs(args) - err = cveCmd.Execute() - So(err, ShouldNotBeNil) - So(buff.String(), ShouldContainSubstring, "invalid output format") - }) -} - -func TestCVESort(t *testing.T) { - rootDir := t.TempDir() - 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}, - CVE: &extconf.CVEConfig{ - UpdateInterval: 2, - Trivy: &extconf.TrivyConfig{ - DBRepository: "ghcr.io/project-zot/trivy-db", - }, - }, - }, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - - image1 := CreateRandomImage() - - storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log) - - err := WriteImageToFileSystem(image1, "repo", "tag", storeController) - if err != nil { - t.FailNow() - } - - ctx := context.Background() - - if err := ctlr.Init(ctx); err != nil { - panic(err) - } - - ctlr.CveScanner = mocks.CveScannerMock{ - ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) { - return map[string]cvemodel.CVE{ - "CVE-2023-1255": { - ID: "CVE-2023-1255", - Severity: "LOW", - Title: "Input buffer over-read in AES-XTS implementation and testing", - }, - "CVE-2023-2650": { - ID: "CVE-2023-2650", - Severity: "MEDIUM", - Title: "Possible DoS translating ASN.1 object identifier and executer", - }, - "CVE-2023-2975": { - ID: "CVE-2023-2975", - Severity: "HIGH", - Title: "AES-SIV cipher implementation contains a bug that can break", - }, - "CVE-2023-3446": { - ID: "CVE-2023-3446", - Severity: "CRITICAL", - Title: "Excessive time spent checking DH keys and parenthesis", - }, - "CVE-2023-3817": { - ID: "CVE-2023-3817", - Severity: "MEDIUM", - Title: "Excessive time spent checking DH q parameter and arguments", - }, - }, nil - }, - } - - go func() { - if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { - panic(err) - } - }() - - defer ctlr.Shutdown() - - test.WaitTillServerReady(baseURL) - - space := regexp.MustCompile(`\s+`) - - Convey("test sorting", t, func() { - args := []string{"list", "repo:tag", "--sort-by", "severity", "--url", baseURL} - cmd := NewCVECommand(new(searchService)) - 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, ShouldResemble, - "ID SEVERITY TITLE "+ - "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ - "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ - "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ - "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+ - "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...") - - args = []string{"list", "repo:tag", "--sort-by", "alpha-asc", "--url", baseURL} - cmd = NewCVECommand(new(searchService)) - 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, ShouldResemble, - "ID SEVERITY TITLE "+ - "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat... "+ - "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ - "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ - "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ - "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ...") - - args = []string{"list", "repo:tag", "--sort-by", "alpha-dsc", "--url", baseURL} - cmd = NewCVECommand(new(searchService)) - 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, ShouldResemble, - "ID SEVERITY TITLE "+ - "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+ - "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ - "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ - "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ - "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...") - }) -} - func TestCVECommandGQL(t *testing.T) { port := test.GetFreePort() baseURL := test.GetBaseURL(port) @@ -1096,7 +424,7 @@ func TestCVECommandGQL(t *testing.T) { args := []string{"affected", "CVE-12345", "--config", "cvetest"} defer os.Remove(configPath) cmd := NewCVECommand(mockService{ - getTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password, + getTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string) (*zcommon.ImagesForCve, error, ) { if count == 0 { @@ -1143,7 +471,7 @@ func TestCVECommandGQL(t *testing.T) { args := []string{"fixed", "repo", "CVE-2222", "--config", "cvetest"} defer os.Remove(configPath) cmd := NewCVECommand(mockService{ - getFixedTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password, + getFixedTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string) (*zcommon.ImageListWithCVEFixedResponse, error, ) { if count == 0 { @@ -1190,7 +518,7 @@ func TestCVECommandGQL(t *testing.T) { args := []string{"list", "repo:vuln", "--config", "cvetest"} defer os.Remove(configPath) cmd := NewCVECommand(mockService{ - getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username, password, + getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string) (*cveResult, error, ) { if count == 0 { @@ -1371,115 +699,13 @@ func TestCVECommandErrors(t *testing.T) { }) } -func getMockCveScanner(metaDB mTypes.MetaDB) cveinfo.Scanner { - // MetaDB loaded with initial data now mock the scanner - // Setup test CVE data in mock scanner - scanner := mocks.CveScannerMock{ - ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) { - if image == "zot-cve-test@sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495" || - image == "zot-cve-test:0.0.1" { - return map[string]cvemodel.CVE{ - "CVE-1": { - ID: "CVE-1", - Severity: "CRITICAL", - Title: "Title for CVE-C1", - Description: "Description of CVE-1", - }, - "CVE-2019-9923": { - ID: "CVE-2019-9923", - Severity: "HIGH", - Title: "Title for CVE-2", - Description: "Description of CVE-2", - }, - "CVE-3": { - ID: "CVE-3", - Severity: "MEDIUM", - Title: "Title for CVE-3", - Description: "Description of CVE-3", - }, - "CVE-4": { - ID: "CVE-4", - Severity: "LOW", - Title: "Title for CVE-4", - Description: "Description of CVE-4", - }, - "CVE-5": { - ID: "CVE-5", - Severity: "UNKNOWN", - Title: "Title for CVE-5", - Description: "Description of CVE-5", - }, - }, nil - } - - // By default the image has no vulnerabilities - return map[string]cvemodel.CVE{}, nil - }, - IsImageFormatScannableFn: func(repo string, reference string) (bool, error) { - // Almost same logic compared to actual Trivy specific implementation - imageDir := repo - inputTag := reference - - repoMeta, err := metaDB.GetRepoMeta(imageDir) - if err != nil { - return false, err - } - - manifestDigestStr := reference - - if zcommon.IsTag(reference) { - var ok bool - - descriptor, ok := repoMeta.Tags[inputTag] - if !ok { - return false, zerr.ErrTagMetaNotFound - } - - manifestDigestStr = descriptor.Digest - } - - manifestDigest, err := godigest.Parse(manifestDigestStr) - if err != nil { - return false, err - } - - manifestData, err := metaDB.GetManifestData(manifestDigest) - if err != nil { - return false, err - } - - var manifestContent ispec.Manifest - - err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent) - if err != nil { - return false, zerr.ErrScanNotSupported - } - - for _, imageLayer := range manifestContent.Layers { - switch imageLayer.MediaType { - case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer): - - return true, nil - default: - - return false, zerr.ErrScanNotSupported - } - } - - return false, nil - }, - } - - return &scanner -} - type mockServiceForRetry struct { mockService retryCounter int succeedOn int } -func (service *mockServiceForRetry) getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, repo, +func (service *mockServiceForRetry) getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, repo, cveID string, ) (*zcommon.ImagesForCve, error) { service.retryCounter += 1 diff --git a/pkg/cli/client/cve_cmd_test.go b/pkg/cli/client/cve_cmd_test.go new file mode 100644 index 00000000..ebb0c817 --- /dev/null +++ b/pkg/cli/client/cve_cmd_test.go @@ -0,0 +1,787 @@ +//go:build search +// +build search + +package client_test + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "regexp" + "strings" + "testing" + "time" + + regTypes "github.com/google/go-containerregistry/pkg/v1/types" + godigest "github.com/opencontainers/go-digest" + ispec "github.com/opencontainers/image-spec/specs-go/v1" + . "github.com/smartystreets/goconvey/convey" + + zerr "zotregistry.io/zot/errors" + "zotregistry.io/zot/pkg/api" + "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/cli/client" + zcommon "zotregistry.io/zot/pkg/common" + extconf "zotregistry.io/zot/pkg/extensions/config" + "zotregistry.io/zot/pkg/extensions/monitoring" + cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" + cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model" + "zotregistry.io/zot/pkg/log" + mTypes "zotregistry.io/zot/pkg/meta/types" + "zotregistry.io/zot/pkg/storage" + "zotregistry.io/zot/pkg/storage/local" + test "zotregistry.io/zot/pkg/test/common" + . "zotregistry.io/zot/pkg/test/image-utils" + "zotregistry.io/zot/pkg/test/mocks" + ociutils "zotregistry.io/zot/pkg/test/oci-utils" +) + +func TestNegativeServerResponse(t *testing.T) { + Convey("Test from real server without search endpoint", t, func() { + port := test.GetFreePort() + url := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + + dir := t.TempDir() + + srcStorageCtlr := ociutils.GetDefaultStoreController(dir, log.NewLogger("debug", "")) + err := WriteImageToFileSystem(CreateDefaultVulnerableImage(), "zot-cve-test", "0.0.1", srcStorageCtlr) + So(err, ShouldBeNil) + + conf.Storage.RootDirectory = dir + trivyConfig := &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + } + cveConfig := &extconf.CVEConfig{ + UpdateInterval: 2, + Trivy: trivyConfig, + } + defaultVal := false + searchConfig := &extconf.SearchConfig{ + BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, + CVE: cveConfig, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") + if err != nil { + panic(err) + } + + logPath := logFile.Name() + defer os.Remove(logPath) + + writers := io.MultiWriter(os.Stdout, logFile) + + ctlr := api.NewController(conf) + ctlr.Log.Logger = ctlr.Log.Output(writers) + + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + _, err = test.ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second) + if err != nil { + panic(err) + } + + Convey("Status Code Not Found", func() { + args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + So(err.Error(), ShouldContainSubstring, zerr.ErrExtensionNotEnabled.Error()) + }) + }) + + Convey("Test non-existing manifest blob", t, func() { + port := test.GetFreePort() + url := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + + dir := t.TempDir() + + imageStore := local.NewImageStore(dir, false, false, + log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil) + + storeController := storage.StoreController{ + DefaultStore: imageStore, + } + + image := CreateRandomImage() + + err := WriteImageToFileSystem(image, "zot-cve-test", "0.0.1", storeController) + So(err, ShouldBeNil) + + err = os.RemoveAll(path.Join(dir, "zot-cve-test/blobs")) + if err != nil { + panic(err) + } + + conf.Storage.RootDirectory = dir + trivyConfig := &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + } + cveConfig := &extconf.CVEConfig{ + UpdateInterval: 2, + Trivy: trivyConfig, + } + defaultVal := true + searchConfig := &extconf.SearchConfig{ + BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, + CVE: cveConfig, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") + if err != nil { + panic(err) + } + + logPath := logFile.Name() + defer os.Remove(logPath) + + writers := io.MultiWriter(os.Stdout, logFile) + + ctlr := api.NewController(conf) + ctlr.Log.Logger = ctlr.Log.Output(writers) + + ctx := context.Background() + + if err := ctlr.Init(ctx); err != nil { + panic(err) + } + + ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) + + go func() { + if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { + panic(err) + } + }() + + defer ctlr.Shutdown() + + test.WaitTillServerReady(url) + + _, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second) + if err != nil { + panic(err) + } + + args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + }) +} + +//nolint:dupl +func TestServerCVEResponse(t *testing.T) { + port := test.GetFreePort() + url := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + + dir := t.TempDir() + + conf.Storage.RootDirectory = dir + trivyConfig := &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + } + cveConfig := &extconf.CVEConfig{ + UpdateInterval: 2, + Trivy: trivyConfig, + } + defaultVal := true + searchConfig := &extconf.SearchConfig{ + BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, + CVE: cveConfig, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt") + if err != nil { + panic(err) + } + + logPath := logFile.Name() + defer os.Remove(logPath) + + writers := io.MultiWriter(os.Stdout, logFile) + + ctlr := api.NewController(conf) + ctlr.Log.Logger = ctlr.Log.Output(writers) + + ctx := context.Background() + + if err := ctlr.Init(ctx); err != nil { + panic(err) + } + + ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB) + + go func() { + if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { + panic(err) + } + }() + + defer ctlr.Shutdown() + + test.WaitTillServerReady(url) + + image := CreateDefaultImage() + + err = UploadImage(image, url, "zot-cve-test", "0.0.1") + if err != nil { + panic(err) + } + + _, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second) + if err != nil { + panic(err) + } + + Convey("Test CVE by image name - GQL - positive", t, func() { + args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(str, ShouldContainSubstring, "ID SEVERITY TITLE") + So(str, ShouldContainSubstring, "CVE") + }) + + Convey("Test CVE by image name - GQL - search CVE by title in results", t, func() { + args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-C1", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(str, ShouldContainSubstring, "ID SEVERITY TITLE") + So(str, ShouldContainSubstring, "CVE-C1") + So(str, ShouldNotContainSubstring, "CVE-2") + }) + + Convey("Test CVE by image name - GQL - search CVE by id in results", t, func() { + args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-2", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(str, ShouldContainSubstring, "ID SEVERITY TITLE") + So(str, ShouldContainSubstring, "CVE-2") + So(str, ShouldNotContainSubstring, "CVE-1") + }) + + Convey("Test CVE by image name - GQL - search nonexistent CVE", t, func() { + args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-100", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(str, ShouldContainSubstring, "No CVEs found for image") + }) + + Convey("Test CVE by image name - GQL - invalid image", t, func() { + args := []string{"list", "invalid:0.0.1", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("Test CVE by image name - GQL - invalid image name and tag", t, func() { + args := []string{"list", "invalid:", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("Test CVE by image name - GQL - invalid output format", t, func() { + args := []string{"list", "zot-cve-test:0.0.1", "-f", "random", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + So(buff.String(), ShouldContainSubstring, "invalid output format") + }) + + Convey("Test images by CVE ID - GQL - positive", t, func() { + args := []string{"affected", "CVE-2019-9923", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(str, ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B") + }) + + Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() { + args := []string{"affected", "CVE-invalid", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(str, ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + }) + + Convey("Test images by CVE ID - GQL - invalid output format", t, func() { + args := []string{"affected", "CVE-2019-9923", "-f", "random", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + So(buff.String(), ShouldContainSubstring, "invalid output format") + }) + + Convey("Test fixed tags by image name and CVE ID - GQL - positive", t, func() { + args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(str, ShouldEqual, "") + }) + + Convey("Test fixed tags by image name and CVE ID - GQL - random cve", t, func() { + args := []string{"fixed", "zot-cve-test", "random", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldBeNil) + So(strings.TrimSpace(str), ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + }) + + Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() { + args := []string{"fixed", "zot-cv-test", "CVE-2019-20807", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldNotBeNil) + So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + }) + + Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() { + args := []string{"fixed", "zot-cv-test:tag", "CVE-2019-20807", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + str = strings.TrimSpace(str) + So(err, ShouldNotBeNil) + So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + }) + + Convey("Test CVE by name and CVE ID - GQL - positive", t, func() { + args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + So(err, ShouldBeNil) + So(strings.TrimSpace(str), ShouldEqual, + "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B") + }) + + Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() { + args := []string{"affected", "CVE-20807", "--repo", "test", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + So(err, ShouldBeNil) + So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH SIGNED SIZE") + }) + + Convey("Test CVE by name and CVE ID - GQL - invalid output format", t, func() { + args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "-f", "random", "--config", "cvetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := client.NewCVECommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + So(buff.String(), ShouldContainSubstring, "invalid output format") + }) +} + +func TestCVESort(t *testing.T) { + rootDir := t.TempDir() + 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}, + CVE: &extconf.CVEConfig{ + UpdateInterval: 2, + Trivy: &extconf.TrivyConfig{ + DBRepository: "ghcr.io/project-zot/trivy-db", + }, + }, + }, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + + image1 := CreateRandomImage() + + storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log) + + err := WriteImageToFileSystem(image1, "repo", "tag", storeController) + if err != nil { + t.FailNow() + } + + ctx := context.Background() + + if err := ctlr.Init(ctx); err != nil { + panic(err) + } + + ctlr.CveScanner = mocks.CveScannerMock{ + ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) { + return map[string]cvemodel.CVE{ + "CVE-2023-1255": { + ID: "CVE-2023-1255", + Severity: "LOW", + Title: "Input buffer over-read in AES-XTS implementation and testing", + }, + "CVE-2023-2650": { + ID: "CVE-2023-2650", + Severity: "MEDIUM", + Title: "Possible DoS translating ASN.1 object identifier and executer", + }, + "CVE-2023-2975": { + ID: "CVE-2023-2975", + Severity: "HIGH", + Title: "AES-SIV cipher implementation contains a bug that can break", + }, + "CVE-2023-3446": { + ID: "CVE-2023-3446", + Severity: "CRITICAL", + Title: "Excessive time spent checking DH keys and parenthesis", + }, + "CVE-2023-3817": { + ID: "CVE-2023-3817", + Severity: "MEDIUM", + Title: "Excessive time spent checking DH q parameter and arguments", + }, + }, nil + }, + } + + go func() { + if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) { + panic(err) + } + }() + + defer ctlr.Shutdown() + + test.WaitTillServerReady(baseURL) + + space := regexp.MustCompile(`\s+`) + + Convey("test sorting", t, func() { + args := []string{"list", "repo:tag", "--sort-by", "severity", "--url", baseURL} + cmd := client.NewCVECommand(client.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, ShouldResemble, + "ID SEVERITY TITLE "+ + "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ + "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ + "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ + "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+ + "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...") + + args = []string{"list", "repo:tag", "--sort-by", "alpha-asc", "--url", baseURL} + cmd = client.NewCVECommand(client.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, ShouldResemble, + "ID SEVERITY TITLE "+ + "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat... "+ + "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ + "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ + "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ + "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ...") + + args = []string{"list", "repo:tag", "--sort-by", "alpha-dsc", "--url", baseURL} + cmd = client.NewCVECommand(client.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, ShouldResemble, + "ID SEVERITY TITLE "+ + "CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+ + "CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+ + "CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+ + "CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+ + "CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...") + }) +} + +func getMockCveScanner(metaDB mTypes.MetaDB) cveinfo.Scanner { + // MetaDB loaded with initial data now mock the scanner + // Setup test CVE data in mock scanner + scanner := mocks.CveScannerMock{ + ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) { + if strings.Contains(image, "zot-cve-test@sha256:db573b01") || + image == "zot-cve-test:0.0.1" { + return map[string]cvemodel.CVE{ + "CVE-1": { + ID: "CVE-1", + Severity: "CRITICAL", + Title: "Title for CVE-C1", + Description: "Description of CVE-1", + }, + "CVE-2019-9923": { + ID: "CVE-2019-9923", + Severity: "HIGH", + Title: "Title for CVE-2", + Description: "Description of CVE-2", + }, + "CVE-3": { + ID: "CVE-3", + Severity: "MEDIUM", + Title: "Title for CVE-3", + Description: "Description of CVE-3", + }, + "CVE-4": { + ID: "CVE-4", + Severity: "LOW", + Title: "Title for CVE-4", + Description: "Description of CVE-4", + }, + "CVE-5": { + ID: "CVE-5", + Severity: "UNKNOWN", + Title: "Title for CVE-5", + Description: "Description of CVE-5", + }, + }, nil + } + + // By default the image has no vulnerabilities + return map[string]cvemodel.CVE{}, nil + }, + IsImageFormatScannableFn: func(repo string, reference string) (bool, error) { + // Almost same logic compared to actual Trivy specific implementation + imageDir := repo + inputTag := reference + + repoMeta, err := metaDB.GetRepoMeta(imageDir) + if err != nil { + return false, err + } + + manifestDigestStr := reference + + if zcommon.IsTag(reference) { + var ok bool + + descriptor, ok := repoMeta.Tags[inputTag] + if !ok { + return false, zerr.ErrTagMetaNotFound + } + + manifestDigestStr = descriptor.Digest + } + + manifestDigest, err := godigest.Parse(manifestDigestStr) + if err != nil { + return false, err + } + + manifestData, err := metaDB.GetManifestData(manifestDigest) + if err != nil { + return false, err + } + + var manifestContent ispec.Manifest + + err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent) + if err != nil { + return false, zerr.ErrScanNotSupported + } + + for _, imageLayer := range manifestContent.Layers { + switch imageLayer.MediaType { + case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer): + + return true, nil + default: + + return false, zerr.ErrScanNotSupported + } + } + + return false, nil + }, + } + + return &scanner +} diff --git a/pkg/cli/client/discover.go b/pkg/cli/client/discover.go index 76979589..d5176c8b 100644 --- a/pkg/cli/client/discover.go +++ b/pkg/cli/client/discover.go @@ -90,11 +90,11 @@ func haveSameArgs(query field, reqQuery GQLQuery) bool { return true } -func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) error { - username, password := getUsernameAndPassword(config.user) +func CheckExtEndPointQuery(config SearchConfig, requiredQueries ...GQLQuery) error { + username, password := getUsernameAndPassword(config.User) ctx := context.Background() - discoverEndPoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("%s%s", + discoverEndPoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("%s%s", constants.RoutePrefix, constants.ExtOciDiscoverPrefix)) if err != nil { return err @@ -102,8 +102,8 @@ func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) err discoverResponse := &distext.ExtensionList{} - _, err = makeGETRequest(ctx, discoverEndPoint, username, password, config.verifyTLS, - config.debug, &discoverResponse, config.resultWriter) + _, err = makeGETRequest(ctx, discoverEndPoint, username, password, config.VerifyTLS, + config.Debug, &discoverResponse, config.ResultWriter) if err != nil { return err } @@ -124,7 +124,7 @@ func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) err return fmt.Errorf("%w: search extension gql endpoints not found", zerr.ErrExtensionNotEnabled) } - searchEndPoint, _ := combineServerAndEndpointURL(config.servURL, constants.FullSearchPrefix) + searchEndPoint, _ := combineServerAndEndpointURL(config.ServURL, constants.FullSearchPrefix) schemaQuery := ` { @@ -153,8 +153,8 @@ func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) err queryResponse := &schemaList{} - err = makeGraphQLRequest(ctx, searchEndPoint, schemaQuery, username, password, config.verifyTLS, - config.debug, queryResponse, config.resultWriter) + err = makeGraphQLRequest(ctx, searchEndPoint, schemaQuery, username, password, config.VerifyTLS, + config.Debug, queryResponse, config.ResultWriter) if err != nil { return fmt.Errorf("gql query failed: %w", err) } diff --git a/pkg/cli/client/elevated_internal_test.go b/pkg/cli/client/elevated_test.go similarity index 95% rename from pkg/cli/client/elevated_internal_test.go rename to pkg/cli/client/elevated_test.go index 98e8a0d4..ae9df260 100644 --- a/pkg/cli/client/elevated_internal_test.go +++ b/pkg/cli/client/elevated_test.go @@ -1,7 +1,7 @@ //go:build search && needprivileges // +build search,needprivileges -package client +package client_test import ( "bytes" @@ -19,6 +19,7 @@ import ( "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/constants" + "zotregistry.io/zot/pkg/cli/client" test "zotregistry.io/zot/pkg/test/common" ) @@ -94,7 +95,7 @@ func TestElevatedPrivilegesTLSNewControllerPrivilegedCert(t *testing.T) { defer os.Remove(configPath) args := []string{"list", "--config", "imagetest"} - imageCmd := NewImageCommand(new(searchService)) + imageCmd := client.NewImageCommand(client.NewSearchService()) imageBuff := bytes.NewBufferString("") imageCmd.SetOut(imageBuff) imageCmd.SetErr(imageBuff) diff --git a/pkg/cli/client/gql_queries_internal_test.go b/pkg/cli/client/gql_queries_test.go similarity index 60% rename from pkg/cli/client/gql_queries_internal_test.go rename to pkg/cli/client/gql_queries_test.go index f9937b41..a27a15a5 100644 --- a/pkg/cli/client/gql_queries_internal_test.go +++ b/pkg/cli/client/gql_queries_test.go @@ -1,7 +1,7 @@ //go:build search // +build search -package client +package client_test import ( "io" @@ -11,6 +11,7 @@ import ( "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/cli/client" extconf "zotregistry.io/zot/pkg/extensions/config" test "zotregistry.io/zot/pkg/test/common" ) @@ -36,57 +37,57 @@ func TestGQLQueries(t *testing.T) { defer cm.StopServer() - searchConfig := searchConfig{ - servURL: baseURL, - user: "", - verifyTLS: false, - debug: false, - resultWriter: io.Discard, + searchConfig := client.SearchConfig{ + ServURL: baseURL, + User: "", + VerifyTLS: false, + Debug: false, + ResultWriter: io.Discard, } Convey("Make sure the current CLI used the right queries in case they change", t, func() { Convey("ImageList", func() { - err := CheckExtEndPointQuery(searchConfig, ImageListQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.ImageListQuery()) So(err, ShouldBeNil) }) Convey("ImageListForDigest", func() { - err := CheckExtEndPointQuery(searchConfig, ImageListForDigestQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.ImageListForDigestQuery()) So(err, ShouldBeNil) }) Convey("BaseImageList", func() { - err := CheckExtEndPointQuery(searchConfig, BaseImageListQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.BaseImageListQuery()) So(err, ShouldBeNil) }) Convey("DerivedImageList", func() { - err := CheckExtEndPointQuery(searchConfig, DerivedImageListQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.DerivedImageListQuery()) So(err, ShouldBeNil) }) Convey("CVEListForImage", func() { - err := CheckExtEndPointQuery(searchConfig, CVEListForImageQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.CVEListForImageQuery()) So(err, ShouldBeNil) }) Convey("ImageListForCVE", func() { - err := CheckExtEndPointQuery(searchConfig, ImageListForCVEQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.ImageListForCVEQuery()) So(err, ShouldBeNil) }) Convey("ImageListWithCVEFixed", func() { - err := CheckExtEndPointQuery(searchConfig, ImageListWithCVEFixedQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.ImageListWithCVEFixedQuery()) So(err, ShouldBeNil) }) Convey("Referrers", func() { - err := CheckExtEndPointQuery(searchConfig, ReferrersQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.ReferrersQuery()) So(err, ShouldBeNil) }) Convey("GlobalSearch", func() { - err := CheckExtEndPointQuery(searchConfig, GlobalSearchQuery()) + err := client.CheckExtEndPointQuery(searchConfig, client.GlobalSearchQuery()) So(err, ShouldBeNil) }) }) diff --git a/pkg/cli/client/image_cmd_internal_test.go b/pkg/cli/client/image_cmd_internal_test.go index 9ef351fb..22430b64 100644 --- a/pkg/cli/client/image_cmd_internal_test.go +++ b/pkg/cli/client/image_cmd_internal_test.go @@ -6,7 +6,6 @@ package client import ( "bytes" "context" - "encoding/json" "errors" "fmt" "log" @@ -21,23 +20,16 @@ import ( godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" . "github.com/smartystreets/goconvey/convey" - "gopkg.in/resty.v1" zerr "zotregistry.io/zot/errors" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/common" extconf "zotregistry.io/zot/pkg/extensions/config" - zlog "zotregistry.io/zot/pkg/log" stypes "zotregistry.io/zot/pkg/storage/types" test "zotregistry.io/zot/pkg/test/common" . "zotregistry.io/zot/pkg/test/image-utils" - ociutils "zotregistry.io/zot/pkg/test/oci-utils" - "zotregistry.io/zot/pkg/test/signature" ) func TestSearchImageCmd(t *testing.T) { @@ -217,7 +209,7 @@ func TestSearchImageCmd(t *testing.T) { Convey("Test image by digest", t, func() { searchConfig := getTestSearchConfig("http://127.0.0.1:8080", new(mockService)) buff := &bytes.Buffer{} - searchConfig.resultWriter = buff + searchConfig.ResultWriter = buff err := SearchImagesByDigest(searchConfig, "6e2f80bf") So(err, ShouldBeNil) @@ -229,265 +221,12 @@ func TestSearchImageCmd(t *testing.T) { }) } -func TestSignature(t *testing.T) { - space := regexp.MustCompile(`\s+`) - - Convey("Test from real server", t, func() { - currentWorkingDir, err := os.Getwd() - So(err, ShouldBeNil) - - currentDir := t.TempDir() - err = os.Chdir(currentDir) - So(err, ShouldBeNil) - - port := test.GetFreePort() - url := 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 = currentDir - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - repoName := "repo7" - image := CreateDefaultImage() - err = UploadImage(image, url, repoName, "1.0") - So(err, ShouldBeNil) - - // generate a keypair - if _, err := os.Stat(path.Join(currentDir, "cosign.key")); err != nil { - os.Setenv("COSIGN_PASSWORD", "") - err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil) - So(err, ShouldBeNil) - } - - _, err = os.Stat(path.Join(currentDir, "cosign.key")) - So(err, ShouldBeNil) - - // sign the image - err = sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: 1 * time.Minute}, - options.KeyOpts{KeyRef: path.Join(currentDir, "cosign.key"), PassFunc: generate.GetPass}, - options.SignOptions{ - Registry: options.RegistryOptions{AllowInsecure: true}, - AnnotationOptions: options.AnnotationOptions{Annotations: []string{"tag=test:1.0"}}, - Upload: true, - }, - []string{fmt.Sprintf("localhost:%s/%s@%s", port, "repo7", image.DigestStr())}) - So(err, ShouldBeNil) - - searchConfig := getTestSearchConfig(url, new(searchService)) - - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err = SearchAllImagesGQL(searchConfig) - So(err, ShouldBeNil) - - actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 1.0 linux/amd64 db573b01 true 854B") - - t.Log("Test getting all images using rest calls to get catalog and individual manifests") - - buff = &bytes.Buffer{} - searchConfig.resultWriter = buff - err = SearchAllImages(searchConfig) - So(err, ShouldBeNil) - - actual = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 1.0 linux/amd64 db573b01 true 854B") - - err = os.Chdir(currentWorkingDir) - So(err, ShouldBeNil) - }) - - Convey("Test with notation signature", t, func() { - currentWorkingDir, err := os.Getwd() - So(err, ShouldBeNil) - - currentDir := t.TempDir() - err = os.Chdir(currentDir) - So(err, ShouldBeNil) - - port := test.GetFreePort() - url := 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 = currentDir - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - repoName := "repo7" - err = UploadImage(CreateDefaultImage(), url, repoName, "0.0.1") - So(err, ShouldBeNil) - - err = signature.SignImageUsingNotary("repo7:0.0.1", port) - So(err, ShouldBeNil) - - searchConfig := getTestSearchConfig(url, new(searchService)) - - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err = SearchAllImagesGQL(searchConfig) - So(err, ShouldBeNil) - - actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 0.0.1 linux/amd64 db573b01 true 854B") - - t.Log("Test getting all images using rest calls to get catalog and individual manifests") - buff = &bytes.Buffer{} - searchConfig.resultWriter = buff - err = SearchAllImages(searchConfig) - So(err, ShouldBeNil) - - actual = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 0.0.1 linux/amd64 db573b01 true 854B") - - err = os.Chdir(currentWorkingDir) - So(err, ShouldBeNil) - }) -} - -//nolint:dupl -func TestDerivedImageList(t *testing.T) { - port := test.GetFreePort() - url := 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() - - err := uploadManifestDerivedBase(url) - if err != nil { - panic(err) - } - - space := regexp.MustCompile(`\s+`) - searchConfig := getTestSearchConfig(url, new(searchService)) - - t.Logf("rootDir: %s", ctlr.Config.Storage.RootDirectory) - - Convey("Test from real server", t, func() { - Convey("Test derived images list working", func() { - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchDerivedImageListGQL(searchConfig, "repo7:test:2.0") - actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(err, ShouldBeNil) - - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 9d9461ed false 860B") - }) - - Convey("Test derived images list fails", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchDerivedImageListGQL(searchConfig, "repo7:test:missing") - So(err, ShouldNotBeNil) - }) - - Convey("Test derived images list cannot print", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - searchConfig.outputFormat = "random" - err := SearchDerivedImageListGQL(searchConfig, "repo7:test:2.0") - So(err, ShouldNotBeNil) - }) - }) -} - -//nolint:dupl -func TestBaseImageList(t *testing.T) { - port := test.GetFreePort() - url := 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() - - err := uploadManifestDerivedBase(url) - if err != nil { - panic(err) - } - - space := regexp.MustCompile(`\s+`) - searchConfig := getTestSearchConfig(url, new(searchService)) - - t.Logf("rootDir: %s", ctlr.Config.Storage.RootDirectory) - - Convey("Test from real server", t, func() { - Convey("Test base images list working", func() { - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchBaseImageListGQL(searchConfig, "repo7:test:1.0") - So(err, ShouldBeNil) - actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 214e4bed false 530B") - }) - - Convey("Test base images list fail", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchBaseImageListGQL(searchConfig, "repo7:test:missing") - So(err, ShouldNotBeNil) - }) - - Convey("Test base images list cannot print", func() { - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - buff := &bytes.Buffer{} - searchConfig.outputFormat = "random" - searchConfig.resultWriter = buff - err := SearchBaseImageListGQL(searchConfig, "repo7:test:missing") - So(err, ShouldNotBeNil) - }) - }) -} - 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 + searchConfig.ResultWriter = buff err := SearchRepos(searchConfig) So(err, ShouldBeNil) }) @@ -735,706 +474,6 @@ func TestOutputFormat(t *testing.T) { }) } -func TestOutputFormatGQL(t *testing.T) { - Convey("Test from real server", t, func() { - port := test.GetFreePort() - url := 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() - - err := uploadManifest(url) - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - So(err, ShouldBeNil) - - Convey("Test json", func() { - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - args := []string{"name", "repo7", "--config", "imagetest", "-f", "json"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - expectedStr := `{"repoName":"repo7","tag":"test:1.0",` + - `"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + - `"mediaType":"application/vnd.oci.image.manifest.v1+json",` + - `"manifests":[{"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + - `"configDigest":"sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e",` + - `"lastUpdated":"2023-01-01T12:00:00Z","size":"528","platform":{"os":"linux","arch":"amd64",` + - `"variant":""},"isSigned":false,"downloadCount":0,"layers":[{"size":"15","digest":` + - `"sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6","score":0}],` + - `"history":null,"vulnerabilities":{"maxSeverity":"","count":0},` + - `"referrers":null,"artifactType":"","signatureInfo":null}],` + - `"size":"528","downloadCount":0,"lastUpdated":"2023-01-01T12:00:00Z","description":"","isSigned":false,` + - `"licenses":"","labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",` + - `"vulnerabilities":{"maxSeverity":"","count":0},"referrers":null,"signatureInfo":null}` + "\n" + - `{"repoName":"repo7","tag":"test:2.0",` + - `"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + - `"mediaType":"application/vnd.oci.image.manifest.v1+json",` + - `"manifests":[{"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + - `"configDigest":"sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e",` + - `"lastUpdated":"2023-01-01T12:00:00Z","size":"528","platform":{"os":"linux","arch":"amd64",` + - `"variant":""},"isSigned":false,"downloadCount":0,"layers":[{"size":"15","digest":` + - `"sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6","score":0}],` + - `"history":null,"vulnerabilities":{"maxSeverity":"","count":0},` + - `"referrers":null,"artifactType":"","signatureInfo":null}],` + - `"size":"528","downloadCount":0,"lastUpdated":"2023-01-01T12:00:00Z","description":"","isSigned":false,` + - `"licenses":"","labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",` + - `"vulnerabilities":{"maxSeverity":"","count":0},"referrers":null,"signatureInfo":null}` + "\n" - // Output is supposed to be in json lines format, keep all spaces as is for verification - So(buff.String(), ShouldEqual, expectedStr) - So(err, ShouldBeNil) - }) - - Convey("Test yaml", func() { - args := []string{"name", "repo7", "--config", "imagetest", "-f", "yaml"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - expectedStr := `--- reponame: repo7 tag: test:1.0 ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + - `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + - `issigned: false downloadcount: 0 layers: - size: "15" ` + - `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + - `history: [] vulnerabilities: maxseverity: "" ` + - `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + - `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + - `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + - `authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: [] ` + - `--- reponame: repo7 tag: test:2.0 ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + - `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + - `issigned: false downloadcount: 0 layers: - size: "15" ` + - `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + - `history: [] vulnerabilities: maxseverity: "" ` + - `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + - `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + - `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + - `authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: []` - So(strings.TrimSpace(str), ShouldEqual, expectedStr) - So(err, ShouldBeNil) - }) - - Convey("Test yml", func() { - args := []string{"name", "repo7", "--config", "imagetest", "-f", "yml"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - expectedStr := `--- reponame: repo7 tag: test:1.0 ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + - `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + - `issigned: false downloadcount: 0 layers: - size: "15" ` + - `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + - `history: [] vulnerabilities: maxseverity: "" ` + - `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + - `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + - `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + - `authors: "" vendor: "" vulnerabilities: maxseverity: "" ` + - `count: 0 referrers: [] signatureinfo: [] ` + - `--- reponame: repo7 tag: test:2.0 ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + - `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + - `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + - `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + - `issigned: false downloadcount: 0 layers: - size: "15" ` + - `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + - `history: [] vulnerabilities: maxseverity: "" ` + - `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + - `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + - `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + - `authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: []` - So(strings.TrimSpace(str), ShouldEqual, expectedStr) - So(err, ShouldBeNil) - }) - - Convey("Test invalid", func() { - args := []string{"name", "repo7", "--config", "imagetest", "-f", "random"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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 output format") - }) - }) -} - -func TestServerResponseGQL(t *testing.T) { - Convey("Test from real server", t, func() { - port := test.GetFreePort() - url := 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() - - err := uploadManifest(url) - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - So(err, ShouldBeNil) - - Convey("Test all images config url", func() { - t.Logf("%s", ctlr.Config.Storage.RootDirectory) - args := []string{"list", "--config", "imagetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - cmd := NewImageCommand(new(searchService)) - buff := &bytes.Buffer{} - 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, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") - Convey("Test all images invalid output format", func() { - args := []string{"list", "--config", "imagetest", "-f", "random"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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 output format") - }) - }) - - Convey("Test all images verbose", func() { - args := []string{"list", "--config", "imagetest", "--verbose"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - // Actual cli output should be something similar to (order of images may differ): - // REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE - // repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B - // b8781e88 15B - // repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B - // b8781e88 15B - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") - }) - - Convey("Test all images with debug flag", func() { - args := []string{"list", "--config", "imagetest", "--debug"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - So(actual, ShouldContainSubstring, "GET") - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") - }) - - Convey("Test image by name config url", func() { - args := []string{"name", "repo7", "--config", "imagetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") - - Convey("invalid output format", func() { - args := []string{"name", "repo7", "--config", "imagetest", "-f", "random"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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 output format") - }) - }) - - Convey("Test image by digest", func() { - args := []string{"digest", "51e18f50", "--config", "imagetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - // Actual cli output should be something similar to (order of images may differ): - // REPOSITORY TAG OS/ARCH DIGEST SIZE - // repo7 test:2.0 a0ca253b 15B - // repo7 test:1.0 a0ca253b 15B - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") - - Convey("nonexistent digest", func() { - args := []string{"digest", "d1g35t", "--config", "imagetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - So(len(buff.String()), ShouldEqual, 0) - }) - - Convey("invalid output format", func() { - args := []string{"digest", "51e18f50", "--config", "imagetest", "-f", "random"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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 output format") - }) - }) - - Convey("Test image by name nonexistent name", func() { - args := []string{"name", "repo777", "--config", "imagetest"} - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) - 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, ShouldBeNil) - So(len(buff.String()), ShouldEqual, 0) - }) - - Convey("Test list repos error", func() { - args := []string{"list", "--config", "config-test"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"config-test", - "url":"%s","showspinner":false}]}`, url)) - defer os.Remove(configPath) - - cmd := NewRepoCommand(new(searchService)) - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - space := regexp.MustCompile(`\s+`) - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - - So(actual, ShouldContainSubstring, "REPOSITORY NAME") - So(actual, ShouldContainSubstring, "repo7") - }) - }) -} - -func TestServerResponse(t *testing.T) { - port := test.GetFreePort() - url := 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() - - err := uploadManifest(url) - if err != nil { - panic(err) - } - - space := regexp.MustCompile(`\s+`) - - Convey("Test from real server", t, func() { - searchConfig := getTestSearchConfig(url, new(searchService)) - - Convey("Test all images", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchAllImages(searchConfig) - So(err, ShouldBeNil) - - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") - }) - - Convey("Test all images verbose", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - searchConfig.verbose = true - defer func() { searchConfig.verbose = false }() - err := SearchAllImages(searchConfig) - So(err, ShouldBeNil) - - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - // Actual cli output should be something similar to (order of images may differ): - // REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE - // repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B - // b8781e88 15B - // repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B - // b8781e88 15B - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") - }) - - Convey("Test image by name", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchImageByName(searchConfig, "repo7") - So(err, ShouldBeNil) - - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") - }) - - Convey("Test image by digest", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchImagesByDigest(searchConfig, "51e18f50") - So(err, ShouldBeNil) - - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - // Actual cli output should be something similar to (order of images may differ): - // REPOSITORY TAG OS/ARCH DIGEST SIZE - // repo7 test:2.0 linux/amd64 51e18f50 528B - // repo7 test:1.0 linux/amd64 51e18f50 528B - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") - So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") - - Convey("nonexistent digest", func() { - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchImagesByDigest(searchConfig, "d1g35t") - So(err, ShouldBeNil) - - So(len(buff.String()), ShouldEqual, 0) - }) - }) - - Convey("Test image by name nonexistent name", func() { - err := SearchImageByName(searchConfig, "repo777") - So(err, ShouldNotBeNil) - - So(err.Error(), ShouldContainSubstring, "no repository found") - }) - }) -} - -func TestServerResponseGQLWithoutPermissions(t *testing.T) { - Convey("Test accessing a blobs folder without having permissions fails fast", t, func() { - port := test.GetFreePort() - conf := config.New() - conf.HTTP.Port = port - - dir := t.TempDir() - - srcStorageCtlr := ociutils.GetDefaultStoreController(dir, zlog.NewLogger("debug", "")) - err := WriteImageToFileSystem(CreateDefaultImage(), "zot-test", "0.0.1", srcStorageCtlr) - So(err, ShouldBeNil) - - err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o000) - if err != nil { - panic(err) - } - - defer func() { - err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o777) - if err != nil { - panic(err) - } - }() - - conf.Storage.RootDirectory = dir - defaultVal := true - searchConfig := &extconf.SearchConfig{ - BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, - } - conf.Extensions = &extconf.ExtensionConfig{ - Search: searchConfig, - } - - ctlr := api.NewController(conf) - if err := ctlr.Init(context.Background()); err != nil { - So(err, ShouldNotBeNil) - } - }) -} - -func TestDisplayIndex(t *testing.T) { - Convey("Init Basic Server, No GQL", t, func() { - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - - Convey("No GQL", func() { - defaultVal := false - 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() - - runDisplayIndexTests(baseURL) - }) - - Convey("With GQL", func() { - 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() - - runDisplayIndexTests(baseURL) - }) - }) -} - -func runDisplayIndexTests(baseURL string) { - space := regexp.MustCompile(`\s+`) - searchConfig := getTestSearchConfig(baseURL, new(searchService)) - - Convey("Test Image Index", func() { - uploadTestMultiarch(baseURL) - - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - err := SearchAllImages(searchConfig) - So(err, ShouldBeNil) - - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - // Actual cli output should be something similar to (order of images may differ): - // REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE - // repo multi-arch * 4780eafe false 1.5kB - // linux/amd64 02e0ac42 false 644B - // windows/arm64/v6 5e09b7f9 false 444B - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(actual, ShouldContainSubstring, "repo multi-arch * 4780eafe false 1.5kB ") - So(actual, ShouldContainSubstring, "linux/amd64 02e0ac42 false 644B ") - So(actual, ShouldContainSubstring, "windows/arm64/v6 5e09b7f9 false 506B") - }) - - Convey("Test Image Index Verbose", func() { - uploadTestMultiarch(baseURL) - - buff := &bytes.Buffer{} - searchConfig.resultWriter = buff - searchConfig.verbose = true - err := SearchAllImages(searchConfig) - So(err, ShouldBeNil) - - str := space.ReplaceAllString(buff.String(), " ") - actual := strings.TrimSpace(str) - // Actual cli output should be something similar to (order of images may differ): - // REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE - // repo multi-arch * 4780eafe false 1.5kB - // linux/amd64 02e0ac42 58cc9abe false 644B - // cbb5b121 4B - // a00291e8 4B - // windows/arm64/v6 5e09b7f9 5132a1cd false 506B - // 7d08ce29 4B - So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE") - So(actual, ShouldContainSubstring, "repo multi-arch * 4780eafe false 1.5kB") - So(actual, ShouldContainSubstring, "linux/amd64 02e0ac42 58cc9abe false 644B") - So(actual, ShouldContainSubstring, "cbb5b121 4B") - So(actual, ShouldContainSubstring, "a00291e8 4B") - So(actual, ShouldContainSubstring, "windows/arm64/v6 5e09b7f9 5132a1cd false 506B") - So(actual, ShouldContainSubstring, "7d08ce29 4B") - }) -} - -func TestImagesSortFlag(t *testing.T) { - rootDir := t.TempDir() - 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}, - CVE: nil, - }, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - - image1 := CreateImageWith().DefaultLayers(). - ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() - - image2 := CreateImageWith().DefaultLayers(). - ImageConfig(ispec.Image{Created: DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() - - storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log) - - err := WriteImageToFileSystem(image1, "a-repo", "tag1", storeController) - if err != nil { - t.FailNow() - } - - err = WriteImageToFileSystem(image2, "b-repo", "tag2", storeController) - if err != nil { - t.FailNow() - } - - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - - defer cm.StopServer() - - Convey("Sorting", t, func() { - args := []string{"list", "--sort-by", "alpha-asc", "--url", baseURL} - cmd := NewImageCommand(new(searchService)) - buff := bytes.NewBufferString("") - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldBeNil) - str := buff.String() - So(strings.Index(str, "a-repo"), ShouldBeLessThan, strings.Index(str, "b-repo")) - - args = []string{"list", "--sort-by", "alpha-dsc", "--url", baseURL} - buff = bytes.NewBufferString("") - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - str = buff.String() - So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-repo")) - - args = []string{"list", "--sort-by", "update-time", "--url", baseURL} - buff = bytes.NewBufferString("") - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - str = buff.String() - So(err, ShouldBeNil) - So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-repo")) - }) -} - func TestImagesCommandGQL(t *testing.T) { port := test.GetFreePort() baseURL := test.GetBaseURL(port) @@ -1754,7 +793,7 @@ func TestImagesCommandGQL(t *testing.T) { defer os.Remove(configPath) args := []string{"cve", "repo:vuln", "--config", "imagetest"} cmd := NewImageCommand(mockService{ - getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username, password, + getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string) (*cveResult, error, ) { if count == 0 { @@ -1980,321 +1019,71 @@ func TestImageCommandREST(t *testing.T) { }) } -func uploadTestMultiarch(baseURL string) { - // ------- Define Image1 - layer11 := []byte{11, 12, 13, 14} - layer12 := []byte{16, 17, 18, 19} - - image1 := CreateImageWith(). - LayerBlobs([][]byte{ - layer11, - layer12, - }). - ImageConfig( - ispec.Image{ - Platform: ispec.Platform{OS: "linux", Architecture: "amd64"}, - }, - ).Build() - - // ------ Define Image2 - layer21 := []byte{21, 22, 23, 24} - - image2 := CreateImageWith(). - LayerBlobs([][]byte{ - layer21, - }). - ImageConfig( - ispec.Image{ - Platform: ispec.Platform{OS: "windows", Architecture: "arm64", Variant: "v6"}, - }, - ).Build() - - // ------- Upload The multiarch image - - multiarch := CreateMultiarchWith().Images([]Image{image1, image2}).Build() - - err := UploadMultiarchImage(multiarch, baseURL, "repo", "multi-arch") - So(err, ShouldBeNil) -} - -func uploadManifest(url string) error { - // create and upload a blob/layer - resp, _ := resty.R().Post(url + "/v2/repo7/blobs/uploads/") - loc := test.Location(url, resp) - - content := []byte("this is a blob5") - digest := godigest.FromBytes(content) - _, _ = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) - - // create config - createdTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - - config := ispec.Image{ - Created: &createdTime, - Platform: ispec.Platform{ - Architecture: "amd64", - OS: "linux", - }, - RootFS: ispec.RootFS{ - Type: "layers", - DiffIDs: []godigest.Digest{}, - }, - Author: "some author", - } - - cblob, err := json.MarshalIndent(&config, "", "\t") - if err != nil { - return err - } - - cdigest := godigest.FromBytes(cblob) - - // upload image config blob - resp, _ = resty.R().Post(url + "/v2/repo7/blobs/uploads/") - loc = test.Location(url, resp) - - _, _ = resty.R(). - SetContentLength(true). - SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))). - SetHeader("Content-Type", "application/octet-stream"). - SetQueryParam("digest", cdigest.String()). - SetBody(cblob). - Put(loc) - - // create a manifest - manifest := ispec.Manifest{ - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: cdigest, - Size: int64(len(cblob)), - }, - Layers: []ispec.Descriptor{ - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: digest, - Size: int64(len(content)), - }, - }, - } - manifest.SchemaVersion = 2 - - content, err = json.Marshal(manifest) - if err != nil { - return err - } - - _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). - SetBody(content).Put(url + "/v2/repo7/manifests/test:1.0") - - content = []byte("this is a blob5") - digest = godigest.FromBytes(content) - // create a manifest with same blob but a different tag - manifest = ispec.Manifest{ - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: cdigest, - Size: int64(len(cblob)), - }, - Layers: []ispec.Descriptor{ - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: digest, - Size: int64(len(content)), - }, - }, - } - manifest.SchemaVersion = 2 - - content, err = json.Marshal(manifest) - if err != nil { - return err - } - _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). - SetBody(content).Put(url + "/v2/repo7/manifests/test:2.0") - - return nil -} - -func uploadManifestDerivedBase(url string) error { - // create a blob/layer - _, _ = resty.R().Post(url + "/v2/repo7/blobs/uploads/") - - content1 := []byte("this is a blob5.0") - content2 := []byte("this is a blob5.1") - content3 := []byte("this is a blob5.2") - digest1 := godigest.FromBytes(content1) - digest2 := godigest.FromBytes(content2) - digest3 := godigest.FromBytes(content3) - _, _ = resty.R().SetQueryParam("digest", digest1.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content1).Post(url + "/v2/repo7/blobs/uploads/") - _, _ = resty.R().SetQueryParam("digest", digest2.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content2).Post(url + "/v2/repo7/blobs/uploads/") - _, _ = resty.R().SetQueryParam("digest", digest3.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content3).Post(url + "/v2/repo7/blobs/uploads/") - - // create config - createdTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - - config := ispec.Image{ - Created: &createdTime, - Platform: ispec.Platform{ - Architecture: "amd64", - OS: "linux", - }, - RootFS: ispec.RootFS{ - Type: "layers", - DiffIDs: []godigest.Digest{}, - }, - Author: "some author", - } - - cblob, err := json.MarshalIndent(&config, "", "\t") - if err != nil { - return err - } - - cdigest := godigest.FromBytes(cblob) - - // upload image config blob - resp, _ := resty.R().Post(url + "/v2/repo7/blobs/uploads/") - loc := test.Location(url, resp) - - _, _ = resty.R(). - SetContentLength(true). - SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))). - SetHeader("Content-Type", "application/octet-stream"). - SetQueryParam("digest", cdigest.String()). - SetBody(cblob). - Put(loc) - - // create a manifest - manifest := ispec.Manifest{ - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: cdigest, - Size: int64(len(cblob)), - }, - Layers: []ispec.Descriptor{ - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: digest1, - Size: int64(len(content1)), - }, { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: digest2, - Size: int64(len(content2)), - }, { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: digest3, - Size: int64(len(content3)), - }, - }, - } - manifest.SchemaVersion = 2 - - content, err := json.Marshal(manifest) - if err != nil { - return err - } - - _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). - SetBody(content).Put(url + "/v2/repo7/manifests/test:1.0") - - content1 = []byte("this is a blob5.0") - digest1 = godigest.FromBytes(content1) - // create a manifest with one common layer blob - manifest = ispec.Manifest{ - Config: ispec.Descriptor{ - MediaType: "application/vnd.oci.image.config.v1+json", - Digest: cdigest, - Size: int64(len(cblob)), - }, - Layers: []ispec.Descriptor{ - { - MediaType: "application/vnd.oci.image.layer.v1.tar", - Digest: digest1, - Size: int64(len(content1)), - }, - }, - } - manifest.SchemaVersion = 2 - - content, err = json.Marshal(manifest) - if err != nil { - return err - } - _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). - SetBody(content).Put(url + "/v2/repo7/manifests/test:2.0") - - return nil -} - type mockService struct { - getAllImagesFn func(ctx context.Context, config searchConfig, username, password string, + 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, + getImagesGQLFn func(ctx context.Context, config SearchConfig, username, password string, imageName string) (*common.ImageListResponse, error) - getImageByNameFn func(ctx context.Context, config searchConfig, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + getImagesForDigestGQLFn func(ctx context.Context, config SearchConfig, username, password string, digest string, ) (*common.ImagesForDigest, error) - getCveByImageGQLFn func(ctx context.Context, config searchConfig, username, password, + getCveByImageGQLFn func(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string, ) (*cveResult, error) - getTagsForCVEGQLFn func(ctx context.Context, config searchConfig, username, password, + getTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImagesForCve, error) - getFixedTagsForCVEGQLFn func(ctx context.Context, config searchConfig, username, password, + getFixedTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImageListWithCVEFixedResponse, error) } -func (service mockService) getRepos(ctx context.Context, config searchConfig, username, +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, "\n\nREPOSITORY NAME") - fmt.Fprintln(config.resultWriter, "repo1") - fmt.Fprintln(config.resultWriter, "repo2") + fmt.Fprintln(config.ResultWriter, "repo1") + fmt.Fprintln(config.ResultWriter, "repo2") } -func (service mockService) getReferrers(ctx context.Context, config searchConfig, username, password string, +func (service mockService) getReferrers(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (referrersResult, error) { if service.getReferrersFn != nil { @@ -2311,7 +1100,7 @@ func (service mockService) getReferrers(ctx context.Context, config searchConfig }, nil } -func (service mockService) globalSearchGQL(ctx context.Context, config searchConfig, username, password string, +func (service mockService) globalSearchGQL(ctx context.Context, config SearchConfig, username, password string, query string, ) (*common.GlobalSearch, error) { if service.globalSearchGQLFn != nil { @@ -2343,7 +1132,7 @@ func (service mockService) globalSearchGQL(ctx context.Context, config searchCon }, nil } -func (service mockService) getReferrersGQL(ctx context.Context, config searchConfig, username, password string, +func (service mockService) getReferrersGQL(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (*common.ReferrersResp, error) { if service.getReferrersGQLFn != nil { @@ -2364,7 +1153,7 @@ func (service mockService) getReferrersGQL(ctx context.Context, config searchCon }, nil } -func (service mockService) getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string, +func (service mockService) getDerivedImageListGQL(ctx context.Context, config SearchConfig, username, password string, derivedImage string, ) (*common.DerivedImageListResponse, error) { if service.getDerivedImageListGQLFn != nil { @@ -2392,7 +1181,7 @@ func (service mockService) getDerivedImageListGQL(ctx context.Context, config se return imageListGQLResponse, nil } -func (service mockService) getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string, +func (service mockService) getBaseImageListGQL(ctx context.Context, config SearchConfig, username, password string, baseImage string, ) (*common.BaseImageListResponse, error) { if service.getBaseImageListGQLFn != nil { @@ -2420,7 +1209,7 @@ func (service mockService) getBaseImageListGQL(ctx context.Context, config searc return imageListGQLResponse, nil } -func (service mockService) getImagesGQL(ctx context.Context, config searchConfig, username, password string, +func (service mockService) getImagesGQL(ctx context.Context, config SearchConfig, username, password string, imageName string, ) (*common.ImageListResponse, error) { if service.getImagesGQLFn != nil { @@ -2450,7 +1239,7 @@ func (service mockService) getImagesGQL(ctx context.Context, config searchConfig return imageListGQLResponse, nil } -func (service mockService) getImagesForDigestGQL(ctx context.Context, config searchConfig, username, password string, +func (service mockService) getImagesForDigestGQL(ctx context.Context, config SearchConfig, username, password string, digest string, ) (*common.ImagesForDigest, error) { if service.getImagesForDigestGQLFn != nil { @@ -2480,7 +1269,7 @@ func (service mockService) getImagesForDigestGQL(ctx context.Context, config sea return imageListGQLResponse, nil } -func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, +func (service mockService) getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImagesForCve, error) { if service.getTagsForCVEGQLFn != nil { @@ -2506,7 +1295,7 @@ func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchCo return images, nil } -func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, +func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImageListWithCVEFixedResponse, error) { if service.getFixedTagsForCVEGQLFn != nil { @@ -2528,7 +1317,7 @@ func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config sea return fixedTags, nil } -func (service mockService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password, +func (service mockService) getCveByImageGQL(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string, ) (*cveResult, error) { if service.getCveByImageGQLFn != nil { @@ -2579,7 +1368,7 @@ func (service mockService) getMockedImageByName(imageName string) imageStruct { return image } -func (service mockService) getAllImages(ctx context.Context, config searchConfig, username, password string, +func (service mockService) getAllImages(ctx context.Context, config SearchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() @@ -2607,7 +1396,7 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig } image.Size = "123445" - str, err := image.string(config.outputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.verbose) + str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose) if err != nil { channel <- stringResult{"", err} @@ -2617,7 +1406,7 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig channel <- stringResult{str, nil} } -func (service mockService) getImageByName(ctx context.Context, config searchConfig, +func (service mockService) getImageByName(ctx context.Context, config SearchConfig, username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() @@ -2645,7 +1434,7 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf } image.Size = "123445" - str, err := image.string(config.outputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.verbose) + str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose) if err != nil { channel <- stringResult{"", err} @@ -2655,7 +1444,7 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf channel <- stringResult{str, nil} } -func (service mockService) getImagesByDigest(ctx context.Context, config searchConfig, username, +func (service mockService) getImagesByDigest(ctx context.Context, config SearchConfig, username, password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { if service.getImagesByDigestFn != nil { @@ -2687,7 +1476,7 @@ func makeConfigFile(content string) string { return configPath } -func getTestSearchConfig(url string, searchService SearchService) searchConfig { +func getTestSearchConfig(url string, searchService SearchService) SearchConfig { var ( user string outputFormat string @@ -2696,16 +1485,16 @@ func getTestSearchConfig(url string, searchService SearchService) searchConfig { verifyTLS bool ) - return searchConfig{ - searchService: searchService, - sortBy: "alpha-asc", - servURL: url, - user: user, - outputFormat: outputFormat, - verbose: verbose, - debug: debug, - verifyTLS: verifyTLS, - resultWriter: nil, + return SearchConfig{ + SearchService: searchService, + SortBy: "alpha-asc", + ServURL: url, + User: user, + OutputFormat: outputFormat, + Verbose: verbose, + Debug: debug, + VerifyTLS: verifyTLS, + ResultWriter: nil, } } diff --git a/pkg/cli/client/image_cmd_test.go b/pkg/cli/client/image_cmd_test.go new file mode 100644 index 00000000..90ba24a1 --- /dev/null +++ b/pkg/cli/client/image_cmd_test.go @@ -0,0 +1,1260 @@ +//go:build search +// +build search + +package client_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path" + "regexp" + "strings" + "testing" + "time" + + godigest "github.com/opencontainers/go-digest" + ispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" + . "github.com/smartystreets/goconvey/convey" + "gopkg.in/resty.v1" + + "zotregistry.io/zot/pkg/api" + "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/cli/client" + extconf "zotregistry.io/zot/pkg/extensions/config" + zlog "zotregistry.io/zot/pkg/log" + test "zotregistry.io/zot/pkg/test/common" + . "zotregistry.io/zot/pkg/test/image-utils" + ociutils "zotregistry.io/zot/pkg/test/oci-utils" + "zotregistry.io/zot/pkg/test/signature" +) + +func TestSignature(t *testing.T) { + space := regexp.MustCompile(`\s+`) + + Convey("Test from real server", t, func() { + currentWorkingDir, err := os.Getwd() + So(err, ShouldBeNil) + + currentDir := t.TempDir() + err = os.Chdir(currentDir) + So(err, ShouldBeNil) + + port := test.GetFreePort() + url := 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 = currentDir + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + repoName := "repo7" + image := CreateDefaultImage() + err = UploadImage(image, url, repoName, "1.0") + So(err, ShouldBeNil) + + // generate a keypair + if _, err := os.Stat(path.Join(currentDir, "cosign.key")); err != nil { + os.Setenv("COSIGN_PASSWORD", "") + err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil) + So(err, ShouldBeNil) + } + + _, err = os.Stat(path.Join(currentDir, "cosign.key")) + So(err, ShouldBeNil) + + // sign the image + err = sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: 1 * time.Minute}, + options.KeyOpts{KeyRef: path.Join(currentDir, "cosign.key"), PassFunc: generate.GetPass}, + options.SignOptions{ + Registry: options.RegistryOptions{AllowInsecure: true}, + AnnotationOptions: options.AnnotationOptions{Annotations: []string{"tag=test:1.0"}}, + Upload: true, + }, + []string{fmt.Sprintf("localhost:%s/%s@%s", port, "repo7", image.DigestStr())}) + So(err, ShouldBeNil) + + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + + searchConfig := getTestSearchConfig(url, client.NewSearchService()) + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + + err = client.SearchAllImagesGQL(searchConfig) + So(err, ShouldBeNil) + + actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 1.0 linux/amd64 db573b01 true 854B") + + t.Log("Test getting all images using rest calls to get catalog and individual manifests") + + buff = &bytes.Buffer{} + searchConfig.ResultWriter = buff + err = client.SearchAllImages(searchConfig) + So(err, ShouldBeNil) + + actual = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 1.0 linux/amd64 db573b01 true 854B") + + err = os.Chdir(currentWorkingDir) + So(err, ShouldBeNil) + }) + + Convey("Test with notation signature", t, func() { + currentWorkingDir, err := os.Getwd() + So(err, ShouldBeNil) + + currentDir := t.TempDir() + err = os.Chdir(currentDir) + So(err, ShouldBeNil) + + port := test.GetFreePort() + url := 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 = currentDir + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + repoName := "repo7" + err = UploadImage(CreateDefaultImage(), url, repoName, "0.0.1") + So(err, ShouldBeNil) + + err = signature.SignImageUsingNotary("repo7:0.0.1", port) + So(err, ShouldBeNil) + + searchConfig := getTestSearchConfig(url, client.NewSearchService()) + + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err = client.SearchAllImagesGQL(searchConfig) + So(err, ShouldBeNil) + + actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 0.0.1 linux/amd64 db573b01 true 854B") + + t.Log("Test getting all images using rest calls to get catalog and individual manifests") + buff = &bytes.Buffer{} + searchConfig.ResultWriter = buff + err = client.SearchAllImages(searchConfig) + So(err, ShouldBeNil) + + actual = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 0.0.1 linux/amd64 db573b01 true 854B") + + err = os.Chdir(currentWorkingDir) + So(err, ShouldBeNil) + }) +} + +//nolint:dupl +func TestDerivedImageList(t *testing.T) { + port := test.GetFreePort() + url := 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() + + err := uploadManifestDerivedBase(url) + if err != nil { + panic(err) + } + + space := regexp.MustCompile(`\s+`) + searchConfig := getTestSearchConfig(url, client.NewSearchService()) + + t.Logf("rootDir: %s", ctlr.Config.Storage.RootDirectory) + + Convey("Test from real server", t, func() { + Convey("Test derived images list working", func() { + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchDerivedImageListGQL(searchConfig, "repo7:test:2.0") + actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(err, ShouldBeNil) + + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 9d9461ed false 860B") + }) + + Convey("Test derived images list fails", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchDerivedImageListGQL(searchConfig, "repo7:test:missing") + So(err, ShouldNotBeNil) + }) + + Convey("Test derived images list cannot print", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + searchConfig.OutputFormat = "random" + err := client.SearchDerivedImageListGQL(searchConfig, "repo7:test:2.0") + So(err, ShouldNotBeNil) + }) + }) +} + +//nolint:dupl +func TestBaseImageList(t *testing.T) { + port := test.GetFreePort() + url := 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() + + err := uploadManifestDerivedBase(url) + if err != nil { + panic(err) + } + + space := regexp.MustCompile(`\s+`) + searchConfig := getTestSearchConfig(url, client.NewSearchService()) + + t.Logf("rootDir: %s", ctlr.Config.Storage.RootDirectory) + + Convey("Test from real server", t, func() { + Convey("Test base images list working", func() { + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchBaseImageListGQL(searchConfig, "repo7:test:1.0") + So(err, ShouldBeNil) + actual := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 214e4bed false 530B") + }) + + Convey("Test base images list fail", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchBaseImageListGQL(searchConfig, "repo7:test:missing") + So(err, ShouldNotBeNil) + }) + + Convey("Test base images list cannot print", func() { + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + buff := &bytes.Buffer{} + searchConfig.OutputFormat = "random" + searchConfig.ResultWriter = buff + err := client.SearchBaseImageListGQL(searchConfig, "repo7:test:missing") + So(err, ShouldNotBeNil) + }) + }) +} + +func TestOutputFormatGQL(t *testing.T) { + Convey("Test from real server", t, func() { + port := test.GetFreePort() + url := 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() + + err := uploadManifest(url) + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + So(err, ShouldBeNil) + + Convey("Test json", func() { + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + args := []string{"name", "repo7", "--config", "imagetest", "-f", "json"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldBeNil) + expectedStr := `{"repoName":"repo7","tag":"test:1.0",` + + `"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + + `"mediaType":"application/vnd.oci.image.manifest.v1+json",` + + `"manifests":[{"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + + `"configDigest":"sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e",` + + `"lastUpdated":"2023-01-01T12:00:00Z","size":"528","platform":{"os":"linux","arch":"amd64",` + + `"variant":""},"isSigned":false,"downloadCount":0,"layers":[{"size":"15","digest":` + + `"sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6","score":0}],` + + `"history":null,"vulnerabilities":{"maxSeverity":"","count":0},` + + `"referrers":null,"artifactType":"","signatureInfo":null}],` + + `"size":"528","downloadCount":0,"lastUpdated":"2023-01-01T12:00:00Z","description":"","isSigned":false,` + + `"licenses":"","labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",` + + `"vulnerabilities":{"maxSeverity":"","count":0},"referrers":null,"signatureInfo":null}` + "\n" + + `{"repoName":"repo7","tag":"test:2.0",` + + `"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + + `"mediaType":"application/vnd.oci.image.manifest.v1+json",` + + `"manifests":[{"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` + + `"configDigest":"sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e",` + + `"lastUpdated":"2023-01-01T12:00:00Z","size":"528","platform":{"os":"linux","arch":"amd64",` + + `"variant":""},"isSigned":false,"downloadCount":0,"layers":[{"size":"15","digest":` + + `"sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6","score":0}],` + + `"history":null,"vulnerabilities":{"maxSeverity":"","count":0},` + + `"referrers":null,"artifactType":"","signatureInfo":null}],` + + `"size":"528","downloadCount":0,"lastUpdated":"2023-01-01T12:00:00Z","description":"","isSigned":false,` + + `"licenses":"","labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",` + + `"vulnerabilities":{"maxSeverity":"","count":0},"referrers":null,"signatureInfo":null}` + "\n" + // Output is supposed to be in json lines format, keep all spaces as is for verification + So(buff.String(), ShouldEqual, expectedStr) + So(err, ShouldBeNil) + }) + + Convey("Test yaml", func() { + args := []string{"name", "repo7", "--config", "imagetest", "-f", "yaml"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.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(), " ") + expectedStr := `--- reponame: repo7 tag: test:1.0 ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + + `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + + `issigned: false downloadcount: 0 layers: - size: "15" ` + + `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + + `history: [] vulnerabilities: maxseverity: "" ` + + `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + + `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + + `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + + `authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: [] ` + + `--- reponame: repo7 tag: test:2.0 ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + + `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + + `issigned: false downloadcount: 0 layers: - size: "15" ` + + `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + + `history: [] vulnerabilities: maxseverity: "" ` + + `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + + `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + + `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + + `authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: []` + So(strings.TrimSpace(str), ShouldEqual, expectedStr) + So(err, ShouldBeNil) + }) + + Convey("Test yml", func() { + args := []string{"name", "repo7", "--config", "imagetest", "-f", "yml"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.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(), " ") + expectedStr := `--- reponame: repo7 tag: test:1.0 ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + + `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + + `issigned: false downloadcount: 0 layers: - size: "15" ` + + `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + + `history: [] vulnerabilities: maxseverity: "" ` + + `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + + `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + + `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + + `authors: "" vendor: "" vulnerabilities: maxseverity: "" ` + + `count: 0 referrers: [] signatureinfo: [] ` + + `--- reponame: repo7 tag: test:2.0 ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` + + `digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` + + `configdigest: sha256:d14faead7d60053bad0d62e5ceb0031df28037d8c636d7911179b2f874ee004e ` + + `lastupdated: 2023-01-01T12:00:00Z size: "528" platform: os: linux arch: amd64 variant: "" ` + + `issigned: false downloadcount: 0 layers: - size: "15" ` + + `digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` + + `history: [] vulnerabilities: maxseverity: "" ` + + `count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` + + `size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` + + `issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` + + `authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: []` + So(strings.TrimSpace(str), ShouldEqual, expectedStr) + So(err, ShouldBeNil) + }) + + Convey("Test invalid", func() { + args := []string{"name", "repo7", "--config", "imagetest", "-f", "random"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + So(buff.String(), ShouldContainSubstring, "invalid output format") + }) + }) +} + +func TestServerResponseGQL(t *testing.T) { + Convey("Test from real server", t, func() { + port := test.GetFreePort() + url := 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() + + err := uploadManifest(url) + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + So(err, ShouldBeNil) + + Convey("Test all images config url", func() { + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + args := []string{"list", "--config", "imagetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := &bytes.Buffer{} + 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, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") + Convey("Test all images invalid output format", func() { + args := []string{"list", "--config", "imagetest", "-f", "random"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + So(buff.String(), ShouldContainSubstring, "invalid output format") + }) + }) + + Convey("Test all images verbose", func() { + args := []string{"list", "--config", "imagetest", "--verbose"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.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) + // Actual cli output should be something similar to (order of images may differ): + // REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE + // repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B + // b8781e88 15B + // repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B + // b8781e88 15B + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") + }) + + Convey("Test all images with debug flag", func() { + args := []string{"list", "--config", "imagetest", "--debug"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.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, "GET") + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") + }) + + Convey("Test image by name config url", func() { + args := []string{"name", "repo7", "--config", "imagetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.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, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") + + Convey("invalid output format", func() { + args := []string{"name", "repo7", "--config", "imagetest", "-f", "random"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + So(buff.String(), ShouldContainSubstring, "invalid output format") + }) + }) + + Convey("Test image by digest", func() { + args := []string{"digest", "51e18f50", "--config", "imagetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.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) + // Actual cli output should be something similar to (order of images may differ): + // REPOSITORY TAG OS/ARCH DIGEST SIZE + // repo7 test:2.0 a0ca253b 15B + // repo7 test:1.0 a0ca253b 15B + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") + + Convey("nonexistent digest", func() { + args := []string{"digest", "d1g35t", "--config", "imagetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldBeNil) + So(len(buff.String()), ShouldEqual, 0) + }) + + Convey("invalid output format", func() { + args := []string{"digest", "51e18f50", "--config", "imagetest", "-f", "random"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + So(buff.String(), ShouldContainSubstring, "invalid output format") + }) + }) + + Convey("Test image by name nonexistent name", func() { + args := []string{"name", "repo777", "--config", "imagetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldBeNil) + So(len(buff.String()), ShouldEqual, 0) + }) + + Convey("Test list repos error", func() { + args := []string{"list", "--config", "config-test"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"config-test", + "url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + + cmd := client.NewRepoCommand(client.NewSearchService()) + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + actual := strings.TrimSpace(str) + + So(actual, ShouldContainSubstring, "REPOSITORY NAME") + So(actual, ShouldContainSubstring, "repo7") + }) + }) +} + +func TestServerResponse(t *testing.T) { + port := test.GetFreePort() + url := 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() + + err := uploadManifest(url) + if err != nil { + panic(err) + } + + space := regexp.MustCompile(`\s+`) + + Convey("Test from real server", t, func() { + searchConfig := getTestSearchConfig(url, client.NewSearchService()) + + Convey("Test all images", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchAllImages(searchConfig) + So(err, ShouldBeNil) + + str := space.ReplaceAllString(buff.String(), " ") + actual := strings.TrimSpace(str) + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") + }) + + Convey("Test all images verbose", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + searchConfig.Verbose = true + defer func() { searchConfig.Verbose = false }() + err := client.SearchAllImages(searchConfig) + So(err, ShouldBeNil) + + str := space.ReplaceAllString(buff.String(), " ") + actual := strings.TrimSpace(str) + // Actual cli output should be something similar to (order of images may differ): + // REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE + // repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B + // b8781e88 15B + // repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B + // b8781e88 15B + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 d14faead false 528B b8781e88 15B") + }) + + Convey("Test image by name", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchImageByName(searchConfig, "repo7") + So(err, ShouldBeNil) + + str := space.ReplaceAllString(buff.String(), " ") + actual := strings.TrimSpace(str) + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") + }) + + Convey("Test image by digest", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchImagesByDigest(searchConfig, "51e18f50") + So(err, ShouldBeNil) + + str := space.ReplaceAllString(buff.String(), " ") + actual := strings.TrimSpace(str) + // Actual cli output should be something similar to (order of images may differ): + // REPOSITORY TAG OS/ARCH DIGEST SIZE + // repo7 test:2.0 linux/amd64 51e18f50 528B + // repo7 test:1.0 linux/amd64 51e18f50 528B + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 51e18f50 false 528B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 51e18f50 false 528B") + + Convey("nonexistent digest", func() { + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchImagesByDigest(searchConfig, "d1g35t") + So(err, ShouldBeNil) + + So(len(buff.String()), ShouldEqual, 0) + }) + }) + + Convey("Test image by name nonexistent name", func() { + err := client.SearchImageByName(searchConfig, "repo777") + So(err, ShouldNotBeNil) + + So(err.Error(), ShouldContainSubstring, "no repository found") + }) + }) +} + +func TestServerResponseGQLWithoutPermissions(t *testing.T) { + Convey("Test accessing a blobs folder without having permissions fails fast", t, func() { + port := test.GetFreePort() + conf := config.New() + conf.HTTP.Port = port + + dir := t.TempDir() + + srcStorageCtlr := ociutils.GetDefaultStoreController(dir, zlog.NewLogger("debug", "")) + err := WriteImageToFileSystem(CreateDefaultImage(), "zot-test", "0.0.1", srcStorageCtlr) + So(err, ShouldBeNil) + + err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o000) + if err != nil { + panic(err) + } + + defer func() { + err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o777) + if err != nil { + panic(err) + } + }() + + conf.Storage.RootDirectory = dir + defaultVal := true + searchConfig := &extconf.SearchConfig{ + BaseConfig: extconf.BaseConfig{Enable: &defaultVal}, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + ctlr := api.NewController(conf) + if err := ctlr.Init(context.Background()); err != nil { + So(err, ShouldNotBeNil) + } + }) +} + +func TestDisplayIndex(t *testing.T) { + Convey("Init Basic Server, No GQL", t, func() { + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + + Convey("No GQL", func() { + defaultVal := false + 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() + + runDisplayIndexTests(baseURL) + }) + + Convey("With GQL", func() { + 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() + + runDisplayIndexTests(baseURL) + }) + }) +} + +func runDisplayIndexTests(baseURL string) { + space := regexp.MustCompile(`\s+`) + searchConfig := getTestSearchConfig(baseURL, client.NewSearchService()) + + Convey("Test Image Index", func() { + uploadTestMultiarch(baseURL) + + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + err := client.SearchAllImages(searchConfig) + So(err, ShouldBeNil) + + str := space.ReplaceAllString(buff.String(), " ") + actual := strings.TrimSpace(str) + // Actual cli output should be something similar to (order of images may differ): + // REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE + // repo multi-arch * 4780eafe false 1.5kB + // linux/amd64 02e0ac42 false 644B + // windows/arm64/v6 5e09b7f9 false 444B + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(actual, ShouldContainSubstring, "repo multi-arch * 4780eafe false 1.5kB ") + So(actual, ShouldContainSubstring, "linux/amd64 02e0ac42 false 644B ") + So(actual, ShouldContainSubstring, "windows/arm64/v6 5e09b7f9 false 506B") + }) + + Convey("Test Image Index Verbose", func() { + uploadTestMultiarch(baseURL) + + buff := &bytes.Buffer{} + searchConfig.ResultWriter = buff + searchConfig.Verbose = true + err := client.SearchAllImages(searchConfig) + So(err, ShouldBeNil) + + str := space.ReplaceAllString(buff.String(), " ") + actual := strings.TrimSpace(str) + // Actual cli output should be something similar to (order of images may differ): + // REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE + // repo multi-arch * 4780eafe false 1.5kB + // linux/amd64 02e0ac42 58cc9abe false 644B + // cbb5b121 4B + // a00291e8 4B + // windows/arm64/v6 5e09b7f9 5132a1cd false 506B + // 7d08ce29 4B + So(actual, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE") + So(actual, ShouldContainSubstring, "repo multi-arch * 4780eafe false 1.5kB") + So(actual, ShouldContainSubstring, "linux/amd64 02e0ac42 58cc9abe false 644B") + So(actual, ShouldContainSubstring, "cbb5b121 4B") + So(actual, ShouldContainSubstring, "a00291e8 4B") + So(actual, ShouldContainSubstring, "windows/arm64/v6 5e09b7f9 5132a1cd false 506B") + So(actual, ShouldContainSubstring, "7d08ce29 4B") + }) +} + +func TestImagesSortFlag(t *testing.T) { + rootDir := t.TempDir() + 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}, + CVE: nil, + }, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + + image1 := CreateImageWith().DefaultLayers(). + ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() + + image2 := CreateImageWith().DefaultLayers(). + ImageConfig(ispec.Image{Created: DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}).Build() + + storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log) + + err := WriteImageToFileSystem(image1, "a-repo", "tag1", storeController) + if err != nil { + t.FailNow() + } + + err = WriteImageToFileSystem(image2, "b-repo", "tag2", storeController) + if err != nil { + t.FailNow() + } + + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + + defer cm.StopServer() + + Convey("Sorting", t, func() { + args := []string{"list", "--sort-by", "alpha-asc", "--url", baseURL} + cmd := client.NewImageCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldBeNil) + str := buff.String() + So(strings.Index(str, "a-repo"), ShouldBeLessThan, strings.Index(str, "b-repo")) + + args = []string{"list", "--sort-by", "alpha-dsc", "--url", baseURL} + buff = bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + str = buff.String() + So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-repo")) + + args = []string{"list", "--sort-by", "update-time", "--url", baseURL} + buff = bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + str = buff.String() + So(err, ShouldBeNil) + So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-repo")) + }) +} + +func uploadTestMultiarch(baseURL string) { + // ------- Define Image1 + layer11 := []byte{11, 12, 13, 14} + layer12 := []byte{16, 17, 18, 19} + + image1 := CreateImageWith(). + LayerBlobs([][]byte{ + layer11, + layer12, + }). + ImageConfig( + ispec.Image{ + Platform: ispec.Platform{OS: "linux", Architecture: "amd64"}, + }, + ).Build() + + // ------ Define Image2 + layer21 := []byte{21, 22, 23, 24} + + image2 := CreateImageWith(). + LayerBlobs([][]byte{ + layer21, + }). + ImageConfig( + ispec.Image{ + Platform: ispec.Platform{OS: "windows", Architecture: "arm64", Variant: "v6"}, + }, + ).Build() + + // ------- Upload The multiarch image + + multiarch := CreateMultiarchWith().Images([]Image{image1, image2}).Build() + + err := UploadMultiarchImage(multiarch, baseURL, "repo", "multi-arch") + So(err, ShouldBeNil) +} + +func uploadManifest(url string) error { + // create and upload a blob/layer + resp, _ := resty.R().Post(url + "/v2/repo7/blobs/uploads/") + loc := test.Location(url, resp) + + content := []byte("this is a blob5") + digest := godigest.FromBytes(content) + _, _ = resty.R().SetQueryParam("digest", digest.String()). + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) + + // create config + createdTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + + config := ispec.Image{ + Created: &createdTime, + Platform: ispec.Platform{ + Architecture: "amd64", + OS: "linux", + }, + RootFS: ispec.RootFS{ + Type: "layers", + DiffIDs: []godigest.Digest{}, + }, + Author: "some author", + } + + cblob, err := json.MarshalIndent(&config, "", "\t") + if err != nil { + return err + } + + cdigest := godigest.FromBytes(cblob) + + // upload image config blob + resp, _ = resty.R().Post(url + "/v2/repo7/blobs/uploads/") + loc = test.Location(url, resp) + + _, _ = resty.R(). + SetContentLength(true). + SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))). + SetHeader("Content-Type", "application/octet-stream"). + SetQueryParam("digest", cdigest.String()). + SetBody(cblob). + Put(loc) + + // create a manifest + manifest := ispec.Manifest{ + Config: ispec.Descriptor{ + MediaType: "application/vnd.oci.image.config.v1+json", + Digest: cdigest, + Size: int64(len(cblob)), + }, + Layers: []ispec.Descriptor{ + { + MediaType: "application/vnd.oci.image.layer.v1.tar", + Digest: digest, + Size: int64(len(content)), + }, + }, + } + manifest.SchemaVersion = 2 + + content, err = json.Marshal(manifest) + if err != nil { + return err + } + + _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(content).Put(url + "/v2/repo7/manifests/test:1.0") + + content = []byte("this is a blob5") + digest = godigest.FromBytes(content) + // create a manifest with same blob but a different tag + manifest = ispec.Manifest{ + Config: ispec.Descriptor{ + MediaType: "application/vnd.oci.image.config.v1+json", + Digest: cdigest, + Size: int64(len(cblob)), + }, + Layers: []ispec.Descriptor{ + { + MediaType: "application/vnd.oci.image.layer.v1.tar", + Digest: digest, + Size: int64(len(content)), + }, + }, + } + manifest.SchemaVersion = 2 + + content, err = json.Marshal(manifest) + if err != nil { + return err + } + _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(content).Put(url + "/v2/repo7/manifests/test:2.0") + + return nil +} + +func uploadManifestDerivedBase(url string) error { + // create a blob/layer + _, _ = resty.R().Post(url + "/v2/repo7/blobs/uploads/") + + content1 := []byte("this is a blob5.0") + content2 := []byte("this is a blob5.1") + content3 := []byte("this is a blob5.2") + digest1 := godigest.FromBytes(content1) + digest2 := godigest.FromBytes(content2) + digest3 := godigest.FromBytes(content3) + _, _ = resty.R().SetQueryParam("digest", digest1.String()). + SetHeader("Content-Type", "application/octet-stream").SetBody(content1).Post(url + "/v2/repo7/blobs/uploads/") + _, _ = resty.R().SetQueryParam("digest", digest2.String()). + SetHeader("Content-Type", "application/octet-stream").SetBody(content2).Post(url + "/v2/repo7/blobs/uploads/") + _, _ = resty.R().SetQueryParam("digest", digest3.String()). + SetHeader("Content-Type", "application/octet-stream").SetBody(content3).Post(url + "/v2/repo7/blobs/uploads/") + + // create config + createdTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + + config := ispec.Image{ + Created: &createdTime, + Platform: ispec.Platform{ + Architecture: "amd64", + OS: "linux", + }, + RootFS: ispec.RootFS{ + Type: "layers", + DiffIDs: []godigest.Digest{}, + }, + Author: "some author", + } + + cblob, err := json.MarshalIndent(&config, "", "\t") + if err != nil { + return err + } + + cdigest := godigest.FromBytes(cblob) + + // upload image config blob + resp, _ := resty.R().Post(url + "/v2/repo7/blobs/uploads/") + loc := test.Location(url, resp) + + _, _ = resty.R(). + SetContentLength(true). + SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))). + SetHeader("Content-Type", "application/octet-stream"). + SetQueryParam("digest", cdigest.String()). + SetBody(cblob). + Put(loc) + + // create a manifest + manifest := ispec.Manifest{ + Config: ispec.Descriptor{ + MediaType: "application/vnd.oci.image.config.v1+json", + Digest: cdigest, + Size: int64(len(cblob)), + }, + Layers: []ispec.Descriptor{ + { + MediaType: "application/vnd.oci.image.layer.v1.tar", + Digest: digest1, + Size: int64(len(content1)), + }, { + MediaType: "application/vnd.oci.image.layer.v1.tar", + Digest: digest2, + Size: int64(len(content2)), + }, { + MediaType: "application/vnd.oci.image.layer.v1.tar", + Digest: digest3, + Size: int64(len(content3)), + }, + }, + } + manifest.SchemaVersion = 2 + + content, err := json.Marshal(manifest) + if err != nil { + return err + } + + _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(content).Put(url + "/v2/repo7/manifests/test:1.0") + + content1 = []byte("this is a blob5.0") + digest1 = godigest.FromBytes(content1) + // create a manifest with one common layer blob + manifest = ispec.Manifest{ + Config: ispec.Descriptor{ + MediaType: "application/vnd.oci.image.config.v1+json", + Digest: cdigest, + Size: int64(len(cblob)), + }, + Layers: []ispec.Descriptor{ + { + MediaType: "application/vnd.oci.image.layer.v1.tar", + Digest: digest1, + Size: int64(len(content1)), + }, + }, + } + manifest.SchemaVersion = 2 + + content, err = json.Marshal(manifest) + if err != nil { + return err + } + _, _ = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(content).Put(url + "/v2/repo7/manifests/test:2.0") + + return nil +} + +func getTestSearchConfig(url string, searchService client.SearchService) client.SearchConfig { + var ( + user string + outputFormat string + verbose bool + debug bool + verifyTLS bool + ) + + return client.SearchConfig{ + SearchService: searchService, + SortBy: "alpha-asc", + ServURL: url, + User: user, + OutputFormat: outputFormat, + Verbose: verbose, + Debug: debug, + VerifyTLS: verifyTLS, + ResultWriter: nil, + } +} diff --git a/pkg/cli/client/repo_internal_test.go b/pkg/cli/client/repo_test.go similarity index 85% rename from pkg/cli/client/repo_internal_test.go rename to pkg/cli/client/repo_test.go index 0a067b61..b5ff7b04 100644 --- a/pkg/cli/client/repo_internal_test.go +++ b/pkg/cli/client/repo_test.go @@ -1,7 +1,7 @@ //go:build search // +build search -package client +package client_test import ( "bytes" @@ -15,6 +15,7 @@ import ( "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/cli/client" test "zotregistry.io/zot/pkg/test/common" . "zotregistry.io/zot/pkg/test/image-utils" ) @@ -43,7 +44,7 @@ func TestReposCommand(t *testing.T) { defer os.Remove(configPath) args := []string{"list", "--config", "repostest"} - cmd := NewRepoCommand(mockService{}) + cmd := client.NewRepoCommand(client.NewSearchService()) buff := bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -57,7 +58,7 @@ func TestReposCommand(t *testing.T) { So(actual, ShouldContainSubstring, "repo2") args = []string{"list", "--sort-by", "alpha-dsc", "--config", "repostest"} - cmd = NewRepoCommand(new(searchService)) + cmd = client.NewRepoCommand(client.NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -72,7 +73,7 @@ func TestReposCommand(t *testing.T) { So(strings.Index(actual, "repo2"), ShouldBeLessThan, strings.Index(actual, "repo1")) args = []string{"list", "--sort-by", "alpha-asc", "--config", "repostest"} - cmd = NewRepoCommand(new(searchService)) + cmd = client.NewRepoCommand(client.NewSearchService()) buff = bytes.NewBufferString("") cmd.SetOut(buff) cmd.SetErr(buff) @@ -91,11 +92,13 @@ func TestReposCommand(t *testing.T) { func TestSuggestions(t *testing.T) { Convey("Suggestions", t, func() { space := regexp.MustCompile(`\s+`) - suggestion := ShowSuggestionsIfUnknownCommand(NewRepoCommand(mockService{}), []string{"bad-command"}) + suggestion := client.ShowSuggestionsIfUnknownCommand( + client.NewRepoCommand(client.NewSearchService()), []string{"bad-command"}) str := space.ReplaceAllString(suggestion.Error(), " ") So(str, ShouldContainSubstring, "unknown subcommand") - suggestion = ShowSuggestionsIfUnknownCommand(NewRepoCommand(mockService{}), []string{"listt"}) + suggestion = client.ShowSuggestionsIfUnknownCommand( + client.NewRepoCommand(client.NewSearchService()), []string{"listt"}) str = space.ReplaceAllString(suggestion.Error(), " ") So(str, ShouldContainSubstring, "Did you mean this? list") }) diff --git a/pkg/cli/client/search_cmd_internal_test.go b/pkg/cli/client/search_cmd_internal_test.go index c13ddb49..bfffcbd1 100644 --- a/pkg/cli/client/search_cmd_internal_test.go +++ b/pkg/cli/client/search_cmd_internal_test.go @@ -10,9 +10,7 @@ import ( "regexp" "strings" "testing" - "time" - ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" "zotregistry.io/zot/pkg/api" @@ -20,713 +18,8 @@ import ( extconf "zotregistry.io/zot/pkg/extensions/config" test "zotregistry.io/zot/pkg/test/common" . "zotregistry.io/zot/pkg/test/image-utils" - ociutils "zotregistry.io/zot/pkg/test/oci-utils" ) -const ( - customArtTypeV1 = "application/custom.art.type.v1" - customArtTypeV2 = "application/custom.art.type.v2" - repoName = "repo" -) - -func TestReferrerCLI(t *testing.T) { - Convey("Test GQL", t, func() { - rootDir := t.TempDir() - - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - conf.Storage.GC = false - defaultVal := true - conf.Extensions = &extconf.ExtensionConfig{ - Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - repo := repoName - image := CreateRandomImage() - - err := UploadImage(image, baseURL, repo, "tag") - So(err, ShouldBeNil) - - ref1 := CreateImageWith(). - RandomLayers(1, 10). - RandomConfig(). - Subject(image.DescriptorRef()).Build() - - ref2 := CreateImageWith(). - RandomLayers(1, 10). - ArtifactConfig(customArtTypeV1). - Subject(image.DescriptorRef()).Build() - - ref3 := CreateImageWith(). - RandomLayers(1, 10). - RandomConfig(). - ArtifactType(customArtTypeV2). - Subject(image.DescriptorRef()).Build() - - err = UploadImage(ref1, baseURL, repo, ref1.DigestStr()) - So(err, ShouldBeNil) - - err = UploadImage(ref2, baseURL, repo, ref2.DigestStr()) - So(err, ShouldBeNil) - - err = UploadImage(ref3, baseURL, repo, ref3.DigestStr()) - So(err, ShouldBeNil) - - args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, - baseURL)) - defer os.Remove(configPath) - - cmd := NewSearchCommand(new(searchService)) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") - So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) - - fmt.Println(buff.String()) - - os.Remove(configPath) - - args = []string{"subject", repo + ":" + "tag", "--config", "reftest"} - - configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, - baseURL)) - defer os.Remove(configPath) - - cmd = NewSearchCommand(new(searchService)) - - buff = &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") - So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) - - fmt.Println(buff.String()) - }) - - Convey("Test REST", t, func() { - rootDir := t.TempDir() - - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - conf.Storage.GC = false - defaultVal := false - conf.Extensions = &extconf.ExtensionConfig{ - Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - repo := repoName - image := CreateRandomImage() - - err := UploadImage(image, baseURL, repo, "tag") - So(err, ShouldBeNil) - - ref1 := CreateImageWith(). - RandomLayers(1, 10). - RandomConfig(). - Subject(image.DescriptorRef()).Build() - - ref2 := CreateImageWith(). - RandomLayers(1, 10). - ArtifactConfig(customArtTypeV1). - Subject(image.DescriptorRef()).Build() - - ref3 := CreateImageWith(). - RandomLayers(1, 10). - RandomConfig(). - ArtifactType(customArtTypeV2). - Subject(image.DescriptorRef()).Build() - - err = UploadImage(ref1, baseURL, repo, ref1.DigestStr()) - So(err, ShouldBeNil) - - err = UploadImage(ref2, baseURL, repo, ref2.DigestStr()) - So(err, ShouldBeNil) - - err = UploadImage(ref3, baseURL, repo, ref3.DigestStr()) - So(err, ShouldBeNil) - - // get referrers by digest - args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, - baseURL)) - - cmd := NewSearchCommand(new(searchService)) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") - So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) - fmt.Println(buff.String()) - - os.Remove(configPath) - - args = []string{"subject", repo + ":" + "tag", "--config", "reftest"} - - configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, - baseURL)) - defer os.Remove(configPath) - - buff = &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") - So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) - So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) - fmt.Println(buff.String()) - }) -} - -func TestFormatsReferrersCLI(t *testing.T) { - Convey("Create server", t, func() { - rootDir := t.TempDir() - - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - conf.Storage.GC = false - defaultVal := false - conf.Extensions = &extconf.ExtensionConfig{ - Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - repo := repoName - image := CreateRandomImage() - - err := UploadImage(image, baseURL, repo, "tag") - So(err, ShouldBeNil) - - // add referrers - ref1 := CreateImageWith(). - RandomLayers(1, 10). - RandomConfig(). - Subject(image.DescriptorRef()).Build() - - ref2 := CreateImageWith(). - RandomLayers(1, 10). - ArtifactConfig(customArtTypeV1). - Subject(image.DescriptorRef()).Build() - - ref3 := CreateImageWith(). - RandomLayers(1, 10). - RandomConfig(). - ArtifactType(customArtTypeV2). - Subject(image.DescriptorRef()).Build() - - err = UploadImage(ref1, baseURL, repo, ref1.DigestStr()) - So(err, ShouldBeNil) - - err = UploadImage(ref2, baseURL, repo, ref2.DigestStr()) - So(err, ShouldBeNil) - - err = UploadImage(ref3, baseURL, repo, ref3.DigestStr()) - So(err, ShouldBeNil) - - Convey("JSON format", func() { - args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "json", "--config", "reftest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, - baseURL)) - - defer os.Remove(configPath) - - cmd := NewSearchCommand(new(searchService)) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - fmt.Println(buff.String()) - }) - Convey("YAML format", func() { - args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "yaml", "--config", "reftest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, - baseURL)) - - defer os.Remove(configPath) - - cmd := NewSearchCommand(new(searchService)) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - fmt.Println(buff.String()) - }) - Convey("Invalid format", func() { - args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "invalid_format", "--config", "reftest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, - baseURL)) - - defer os.Remove(configPath) - - cmd := NewSearchCommand(new(searchService)) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldNotBeNil) - }) - }) -} - -func TestReferrersCLIErrors(t *testing.T) { - Convey("Errors", t, func() { - cmd := NewSearchCommand(new(searchService)) - - Convey("no url provided", func() { - args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "reftest"} - - configPath := makeConfigFile(`{"configs":[{"_name":"reftest","showspinner":false}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("getConfigValue", func() { - args := []string{"subject", "repo/alpine", "--config", "reftest"} - - configPath := makeConfigFile(`bad-json`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("bad showspinnerConfig ", func() { - args := []string{"query", "repo", "--config", "reftest"} - - configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("bad verifyTLSConfig ", func() { - args := []string{"query", "repo", "reftest"} - - configPath := makeConfigFile( - `{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("url from config is empty", func() { - args := []string{"subject", "repo/alpine", "--config", "reftest"} - - configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"", "showspinner":false}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("bad params combination", func() { - args := []string{"query", "repo", "reftest"} - - configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - }) -} - -func TestSearchCLI(t *testing.T) { - Convey("Test GQL", t, func() { - rootDir := t.TempDir() - - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - conf.Storage.GC = false - defaultVal := true - conf.Extensions = &extconf.ExtensionConfig{ - Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - const ( - repo1 = "repo" - r1tag1 = "repo1tag1" - r1tag2 = "repo1tag2" - - repo2 = "repo/alpine" - r2tag1 = "repo2tag1" - r2tag2 = "repo2tag2" - - repo3 = "repo/test/alpine" - r3tag1 = "repo3tag1" - r3tag2 = "repo3tag2" - ) - - image1 := CreateImageWith(). - RandomLayers(1, 10). - ImageConfig(ispec.Image{ - Created: DefaultTimeRef(), - Platform: ispec.Platform{OS: "Os", Architecture: "Arch"}, - }). - Build() - formatterDigest1 := image1.Digest().Encoded()[:8] - - image2 := CreateImageWith(). - RandomLayers(1, 10). - DefaultConfig(). - Build() - formatterDigest2 := image2.Digest().Encoded()[:8] - - err := UploadImage(image1, baseURL, repo1, r1tag1) - So(err, ShouldBeNil) - err = UploadImage(image2, baseURL, repo1, r1tag2) - So(err, ShouldBeNil) - - err = UploadImage(image1, baseURL, repo2, r2tag1) - So(err, ShouldBeNil) - err = UploadImage(image2, baseURL, repo2, r2tag2) - So(err, ShouldBeNil) - - err = UploadImage(image1, baseURL, repo3, r3tag1) - So(err, ShouldBeNil) - err = UploadImage(image2, baseURL, repo3, r3tag2) - So(err, ShouldBeNil) - - // search by repos - - args := []string{"query", "test/alpin", "--verbose", "--config", "searchtest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, - baseURL)) - defer os.Remove(configPath) - - cmd := NewSearchCommand(new(searchService)) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - space := regexp.MustCompile(`\s+`) - str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(str, ShouldContainSubstring, "NAME SIZE LAST UPDATED DOWNLOADS STARS PLATFORMS") - So(str, ShouldContainSubstring, "repo/test/alpine 1.1kB 2010-01-01 01:01:01 +0000 UTC 0 0") - So(str, ShouldContainSubstring, "Os/Arch") - So(str, ShouldContainSubstring, "linux/amd64") - - fmt.Println("\n", buff.String()) - - os.Remove(configPath) - - cmd = NewSearchCommand(new(searchService)) - - args = []string{"query", "repo/alpine:", "--config", "searchtest"} - - configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, - baseURL)) - - defer os.Remove(configPath) - - buff = &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) - So(str, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") - So(str, ShouldContainSubstring, "repo/alpine repo2tag1 Os/Arch "+formatterDigest1+" false 525B") - So(str, ShouldContainSubstring, "repo/alpine repo2tag2 linux/amd64 "+formatterDigest2+" false 552B") - - fmt.Println("\n", buff.String()) - }) -} - -func TestFormatsSearchCLI(t *testing.T) { - Convey("", t, func() { - rootDir := t.TempDir() - - port := test.GetFreePort() - baseURL := test.GetBaseURL(port) - conf := config.New() - conf.HTTP.Port = port - conf.Storage.GC = false - defaultVal := true - conf.Extensions = &extconf.ExtensionConfig{ - Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - defer cm.StopServer() - - const ( - repo1 = "repo" - r1tag1 = "repo1tag1" - r1tag2 = "repo1tag2" - - repo2 = "repo/alpine" - r2tag1 = "repo2tag1" - r2tag2 = "repo2tag2" - - repo3 = "repo/test/alpine" - r3tag1 = "repo3tag1" - r3tag2 = "repo3tag2" - ) - - image1 := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() - image2 := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() - - err := UploadImage(image1, baseURL, repo1, r1tag1) - So(err, ShouldBeNil) - err = UploadImage(image2, baseURL, repo1, r1tag2) - So(err, ShouldBeNil) - - err = UploadImage(image1, baseURL, repo2, r2tag1) - So(err, ShouldBeNil) - err = UploadImage(image2, baseURL, repo2, r2tag2) - So(err, ShouldBeNil) - - err = UploadImage(image1, baseURL, repo3, r3tag1) - So(err, ShouldBeNil) - err = UploadImage(image2, baseURL, repo3, r3tag2) - So(err, ShouldBeNil) - - cmd := NewSearchCommand(new(searchService)) - - Convey("JSON format", func() { - args := []string{"query", "repo/alpine", "--format", "json", "--config", "searchtest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, - baseURL)) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - fmt.Println(buff.String()) - }) - - Convey("YAML format", func() { - args := []string{"query", "repo/alpine", "--format", "yaml", "--config", "searchtest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, - baseURL)) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - fmt.Println(buff.String()) - }) - - Convey("Invalid format", func() { - args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} - - configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, - baseURL)) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldNotBeNil) - }) - }) -} - -func TestSearchCLIErrors(t *testing.T) { - Convey("Errors", t, func() { - cmd := NewSearchCommand(new(searchService)) - - Convey("no url provided", func() { - args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} - - configPath := makeConfigFile(`{"configs":[{"_name":"searchtest","showspinner":false}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("getConfigValue", func() { - args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} - - configPath := makeConfigFile(`bad-json`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("bad showspinnerConfig ", func() { - args := []string{"query", "repo/alpine", "--config", "searchtest"} - - configPath := makeConfigFile( - `{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("bad verifyTLSConfig ", func() { - args := []string{"query", "repo/alpine", "--config", "searchtest"} - - configPath := makeConfigFile( - `{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - - Convey("url from config is empty", func() { - args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} - - configPath := makeConfigFile(`{"configs":[{"_name":"searchtest", "url":"", "showspinner":false}]}`) - - defer os.Remove(configPath) - - buff := &bytes.Buffer{} - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldNotBeNil) - }) - }) -} - func TestSearchCommandGQL(t *testing.T) { port := test.GetFreePort() baseURL := test.GetBaseURL(port) @@ -865,70 +158,3 @@ func TestSearchCommandREST(t *testing.T) { }) }) } - -func TestSearchSort(t *testing.T) { - rootDir := t.TempDir() - 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}, - CVE: nil, - }, - } - ctlr := api.NewController(conf) - ctlr.Config.Storage.RootDirectory = rootDir - - image1 := CreateImageWith().DefaultLayers(). - ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}). - Build() - - image2 := CreateImageWith().DefaultLayers(). - ImageConfig(ispec.Image{Created: DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}). - Build() - - storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log) - - err := WriteImageToFileSystem(image1, "b-repo", "tag2", storeController) - if err != nil { - t.FailNow() - } - - err = WriteImageToFileSystem(image2, "a-test-repo", "tag2", storeController) - if err != nil { - t.FailNow() - } - - cm := test.NewControllerManager(ctlr) - cm.StartAndWait(conf.HTTP.Port) - - defer cm.StopServer() - - Convey("test sorting", t, func() { - args := []string{"query", "repo", "--sort-by", "relevance", "--url", baseURL} - cmd := NewSearchCommand(new(searchService)) - buff := bytes.NewBufferString("") - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err := cmd.Execute() - So(err, ShouldBeNil) - str := buff.String() - So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-test-repo")) - - args = []string{"query", "repo", "--sort-by", "alpha-asc", "--url", baseURL} - cmd = NewSearchCommand(new(searchService)) - buff = bytes.NewBufferString("") - cmd.SetOut(buff) - cmd.SetErr(buff) - cmd.SetArgs(args) - err = cmd.Execute() - So(err, ShouldBeNil) - str = buff.String() - So(strings.Index(str, "a-test-repo"), ShouldBeLessThan, strings.Index(str, "b-repo")) - }) -} diff --git a/pkg/cli/client/search_cmd_test.go b/pkg/cli/client/search_cmd_test.go new file mode 100644 index 00000000..b16a5a83 --- /dev/null +++ b/pkg/cli/client/search_cmd_test.go @@ -0,0 +1,796 @@ +//go:build search +// +build search + +package client_test + +import ( + "bytes" + "fmt" + "os" + "regexp" + "strings" + "testing" + "time" + + ispec "github.com/opencontainers/image-spec/specs-go/v1" + . "github.com/smartystreets/goconvey/convey" + + "zotregistry.io/zot/pkg/api" + "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/cli/client" + extconf "zotregistry.io/zot/pkg/extensions/config" + test "zotregistry.io/zot/pkg/test/common" + . "zotregistry.io/zot/pkg/test/image-utils" + ociutils "zotregistry.io/zot/pkg/test/oci-utils" +) + +const ( + customArtTypeV1 = "application/custom.art.type.v1" + customArtTypeV2 = "application/custom.art.type.v2" + repoName = "repo" +) + +func TestReferrerCLI(t *testing.T) { + Convey("Test GQL", t, func() { + rootDir := t.TempDir() + + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.GC = false + defaultVal := true + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + repo := repoName + image := CreateRandomImage() + + err := UploadImage(image, baseURL, repo, "tag") + So(err, ShouldBeNil) + + ref1 := CreateImageWith(). + RandomLayers(1, 10). + RandomConfig(). + Subject(image.DescriptorRef()).Build() + + ref2 := CreateImageWith(). + RandomLayers(1, 10). + ArtifactConfig(customArtTypeV1). + Subject(image.DescriptorRef()).Build() + + ref3 := CreateImageWith(). + RandomLayers(1, 10). + RandomConfig(). + ArtifactType(customArtTypeV2). + Subject(image.DescriptorRef()).Build() + + err = UploadImage(ref1, baseURL, repo, ref1.DigestStr()) + So(err, ShouldBeNil) + + err = UploadImage(ref2, baseURL, repo, ref2.DigestStr()) + So(err, ShouldBeNil) + + err = UploadImage(ref3, baseURL, repo, ref3.DigestStr()) + So(err, ShouldBeNil) + + args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, + baseURL)) + defer os.Remove(configPath) + + cmd := client.NewSearchCommand(client.NewSearchService()) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + space := regexp.MustCompile(`\s+`) + str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") + So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) + + fmt.Println(buff.String()) + + os.Remove(configPath) + + args = []string{"subject", repo + ":" + "tag", "--config", "reftest"} + + configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, + baseURL)) + defer os.Remove(configPath) + + cmd = client.NewSearchCommand(client.NewSearchService()) + + buff = &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") + So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) + + fmt.Println(buff.String()) + }) + + Convey("Test REST", t, func() { + rootDir := t.TempDir() + + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.GC = false + defaultVal := false + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + repo := repoName + image := CreateRandomImage() + + err := UploadImage(image, baseURL, repo, "tag") + So(err, ShouldBeNil) + + ref1 := CreateImageWith(). + RandomLayers(1, 10). + RandomConfig(). + Subject(image.DescriptorRef()).Build() + + ref2 := CreateImageWith(). + RandomLayers(1, 10). + ArtifactConfig(customArtTypeV1). + Subject(image.DescriptorRef()).Build() + + ref3 := CreateImageWith(). + RandomLayers(1, 10). + RandomConfig(). + ArtifactType(customArtTypeV2). + Subject(image.DescriptorRef()).Build() + + err = UploadImage(ref1, baseURL, repo, ref1.DigestStr()) + So(err, ShouldBeNil) + + err = UploadImage(ref2, baseURL, repo, ref2.DigestStr()) + So(err, ShouldBeNil) + + err = UploadImage(ref3, baseURL, repo, ref3.DigestStr()) + So(err, ShouldBeNil) + + // get referrers by digest + args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, + baseURL)) + + cmd := client.NewSearchCommand(client.NewSearchService()) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + space := regexp.MustCompile(`\s+`) + str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") + So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) + fmt.Println(buff.String()) + + os.Remove(configPath) + + args = []string{"subject", repo + ":" + "tag", "--config", "reftest"} + + configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, + baseURL)) + defer os.Remove(configPath) + + buff = &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST") + So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr()) + So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr()) + fmt.Println(buff.String()) + }) +} + +func TestFormatsReferrersCLI(t *testing.T) { + Convey("Create server", t, func() { + rootDir := t.TempDir() + + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.GC = false + defaultVal := false + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + repo := repoName + image := CreateRandomImage() + + err := UploadImage(image, baseURL, repo, "tag") + So(err, ShouldBeNil) + + // add referrers + ref1 := CreateImageWith(). + RandomLayers(1, 10). + RandomConfig(). + Subject(image.DescriptorRef()).Build() + + ref2 := CreateImageWith(). + RandomLayers(1, 10). + ArtifactConfig(customArtTypeV1). + Subject(image.DescriptorRef()).Build() + + ref3 := CreateImageWith(). + RandomLayers(1, 10). + RandomConfig(). + ArtifactType(customArtTypeV2). + Subject(image.DescriptorRef()).Build() + + err = UploadImage(ref1, baseURL, repo, ref1.DigestStr()) + So(err, ShouldBeNil) + + err = UploadImage(ref2, baseURL, repo, ref2.DigestStr()) + So(err, ShouldBeNil) + + err = UploadImage(ref3, baseURL, repo, ref3.DigestStr()) + So(err, ShouldBeNil) + + Convey("JSON format", func() { + args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "json", "--config", "reftest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, + baseURL)) + + defer os.Remove(configPath) + + cmd := client.NewSearchCommand(client.NewSearchService()) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + fmt.Println(buff.String()) + }) + Convey("YAML format", func() { + args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "yaml", "--config", "reftest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, + baseURL)) + + defer os.Remove(configPath) + + cmd := client.NewSearchCommand(client.NewSearchService()) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + fmt.Println(buff.String()) + }) + Convey("Invalid format", func() { + args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "invalid_format", "--config", "reftest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`, + baseURL)) + + defer os.Remove(configPath) + + cmd := client.NewSearchCommand(client.NewSearchService()) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldNotBeNil) + }) + }) +} + +func TestReferrersCLIErrors(t *testing.T) { + Convey("Errors", t, func() { + cmd := client.NewSearchCommand(client.NewSearchService()) + + Convey("no url provided", func() { + args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "reftest"} + + configPath := makeConfigFile(`{"configs":[{"_name":"reftest","showspinner":false}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("getConfigValue", func() { + args := []string{"subject", "repo/alpine", "--config", "reftest"} + + configPath := makeConfigFile(`bad-json`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("bad showspinnerConfig ", func() { + args := []string{"query", "repo", "--config", "reftest"} + + configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("bad verifyTLSConfig ", func() { + args := []string{"query", "repo", "reftest"} + + configPath := makeConfigFile( + `{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("url from config is empty", func() { + args := []string{"subject", "repo/alpine", "--config", "reftest"} + + configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"", "showspinner":false}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("bad params combination", func() { + args := []string{"query", "repo", "reftest"} + + configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + }) +} + +func TestSearchCLI(t *testing.T) { + Convey("Test GQL", t, func() { + rootDir := t.TempDir() + + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.GC = false + defaultVal := true + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + const ( + repo1 = "repo" + r1tag1 = "repo1tag1" + r1tag2 = "repo1tag2" + + repo2 = "repo/alpine" + r2tag1 = "repo2tag1" + r2tag2 = "repo2tag2" + + repo3 = "repo/test/alpine" + r3tag1 = "repo3tag1" + r3tag2 = "repo3tag2" + ) + + image1 := CreateImageWith(). + RandomLayers(1, 10). + ImageConfig(ispec.Image{ + Created: DefaultTimeRef(), + Platform: ispec.Platform{OS: "Os", Architecture: "Arch"}, + }). + Build() + formatterDigest1 := image1.Digest().Encoded()[:8] + + image2 := CreateImageWith(). + RandomLayers(1, 10). + DefaultConfig(). + Build() + formatterDigest2 := image2.Digest().Encoded()[:8] + + err := UploadImage(image1, baseURL, repo1, r1tag1) + So(err, ShouldBeNil) + err = UploadImage(image2, baseURL, repo1, r1tag2) + So(err, ShouldBeNil) + + err = UploadImage(image1, baseURL, repo2, r2tag1) + So(err, ShouldBeNil) + err = UploadImage(image2, baseURL, repo2, r2tag2) + So(err, ShouldBeNil) + + err = UploadImage(image1, baseURL, repo3, r3tag1) + So(err, ShouldBeNil) + err = UploadImage(image2, baseURL, repo3, r3tag2) + So(err, ShouldBeNil) + + // search by repos + + args := []string{"query", "test/alpin", "--verbose", "--config", "searchtest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, + baseURL)) + defer os.Remove(configPath) + + cmd := client.NewSearchCommand(client.NewSearchService()) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + space := regexp.MustCompile(`\s+`) + str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(str, ShouldContainSubstring, "NAME SIZE LAST UPDATED DOWNLOADS STARS PLATFORMS") + So(str, ShouldContainSubstring, "repo/test/alpine 1.1kB 2010-01-01 01:01:01 +0000 UTC 0 0") + So(str, ShouldContainSubstring, "Os/Arch") + So(str, ShouldContainSubstring, "linux/amd64") + + fmt.Println("\n", buff.String()) + + os.Remove(configPath) + + cmd = client.NewSearchCommand(client.NewSearchService()) + + args = []string{"query", "repo/alpine:", "--config", "searchtest"} + + configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, + baseURL)) + + defer os.Remove(configPath) + + buff = &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " ")) + So(str, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE") + So(str, ShouldContainSubstring, "repo/alpine repo2tag1 Os/Arch "+formatterDigest1+" false 525B") + So(str, ShouldContainSubstring, "repo/alpine repo2tag2 linux/amd64 "+formatterDigest2+" false 552B") + + fmt.Println("\n", buff.String()) + }) +} + +func TestFormatsSearchCLI(t *testing.T) { + Convey("", t, func() { + rootDir := t.TempDir() + + port := test.GetFreePort() + baseURL := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.GC = false + defaultVal := true + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + defer cm.StopServer() + + const ( + repo1 = "repo" + r1tag1 = "repo1tag1" + r1tag2 = "repo1tag2" + + repo2 = "repo/alpine" + r2tag1 = "repo2tag1" + r2tag2 = "repo2tag2" + + repo3 = "repo/test/alpine" + r3tag1 = "repo3tag1" + r3tag2 = "repo3tag2" + ) + + image1 := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() + image2 := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build() + + err := UploadImage(image1, baseURL, repo1, r1tag1) + So(err, ShouldBeNil) + err = UploadImage(image2, baseURL, repo1, r1tag2) + So(err, ShouldBeNil) + + err = UploadImage(image1, baseURL, repo2, r2tag1) + So(err, ShouldBeNil) + err = UploadImage(image2, baseURL, repo2, r2tag2) + So(err, ShouldBeNil) + + err = UploadImage(image1, baseURL, repo3, r3tag1) + So(err, ShouldBeNil) + err = UploadImage(image2, baseURL, repo3, r3tag2) + So(err, ShouldBeNil) + + cmd := client.NewSearchCommand(client.NewSearchService()) + + Convey("JSON format", func() { + args := []string{"query", "repo/alpine", "--format", "json", "--config", "searchtest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, + baseURL)) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + fmt.Println(buff.String()) + }) + + Convey("YAML format", func() { + args := []string{"query", "repo/alpine", "--format", "yaml", "--config", "searchtest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, + baseURL)) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + fmt.Println(buff.String()) + }) + + Convey("Invalid format", func() { + args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} + + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`, + baseURL)) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldNotBeNil) + }) + }) +} + +func TestSearchCLIErrors(t *testing.T) { + Convey("Errors", t, func() { + cmd := client.NewSearchCommand(client.NewSearchService()) + + Convey("no url provided", func() { + args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} + + configPath := makeConfigFile(`{"configs":[{"_name":"searchtest","showspinner":false}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("getConfigValue", func() { + args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} + + configPath := makeConfigFile(`bad-json`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("bad showspinnerConfig ", func() { + args := []string{"query", "repo/alpine", "--config", "searchtest"} + + configPath := makeConfigFile( + `{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("bad verifyTLSConfig ", func() { + args := []string{"query", "repo/alpine", "--config", "searchtest"} + + configPath := makeConfigFile( + `{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("url from config is empty", func() { + args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"} + + configPath := makeConfigFile(`{"configs":[{"_name":"searchtest", "url":"", "showspinner":false}]}`) + + defer os.Remove(configPath) + + buff := &bytes.Buffer{} + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + }) + }) +} + +func TestSearchSort(t *testing.T) { + rootDir := t.TempDir() + 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}, + CVE: nil, + }, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = rootDir + + image1 := CreateImageWith().DefaultLayers(). + ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}). + Build() + + image2 := CreateImageWith().DefaultLayers(). + ImageConfig(ispec.Image{Created: DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}). + Build() + + storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log) + + err := WriteImageToFileSystem(image1, "b-repo", "tag2", storeController) + if err != nil { + t.FailNow() + } + + err = WriteImageToFileSystem(image2, "a-test-repo", "tag2", storeController) + if err != nil { + t.FailNow() + } + + cm := test.NewControllerManager(ctlr) + cm.StartAndWait(conf.HTTP.Port) + + defer cm.StopServer() + + Convey("test sorting", t, func() { + args := []string{"query", "repo", "--sort-by", "relevance", "--url", baseURL} + cmd := client.NewSearchCommand(client.NewSearchService()) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldBeNil) + str := buff.String() + So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-test-repo")) + + args = []string{"query", "repo", "--sort-by", "alpha-asc", "--url", baseURL} + cmd = client.NewSearchCommand(client.NewSearchService()) + buff = bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err = cmd.Execute() + So(err, ShouldBeNil) + str = buff.String() + So(strings.Index(str, "a-test-repo"), ShouldBeLessThan, strings.Index(str, "b-repo")) + }) +} diff --git a/pkg/cli/client/search_functions.go b/pkg/cli/client/search_functions.go index e400742f..9cf5c34f 100644 --- a/pkg/cli/client/search_functions.go +++ b/pkg/cli/client/search_functions.go @@ -17,8 +17,8 @@ import ( const CveDBRetryInterval = 3 -func SearchAllImages(config searchConfig) error { - username, password := getUsernameAndPassword(config.user) +func SearchAllImages(config SearchConfig) error { + username, password := getUsernameAndPassword(config.User) imageErr := make(chan stringResult) ctx, cancel := context.WithCancel(context.Background()) @@ -26,7 +26,7 @@ func SearchAllImages(config searchConfig) error { wg.Add(1) - go config.searchService.getAllImages(ctx, config, username, password, imageErr, &wg) + go config.SearchService.getAllImages(ctx, config, username, password, imageErr, &wg) wg.Add(1) errCh := make(chan error, 1) @@ -41,13 +41,13 @@ func SearchAllImages(config searchConfig) error { } } -func SearchAllImagesGQL(config searchConfig) error { - username, password := getUsernameAndPassword(config.user) +func SearchAllImagesGQL(config SearchConfig) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - imageList, err := config.searchService.getImagesGQL(ctx, config, username, password, "") + imageList, err := config.SearchService.getImagesGQL(ctx, config, username, password, "") if err != nil { return err } @@ -61,8 +61,8 @@ func SearchAllImagesGQL(config searchConfig) error { return printImageResult(config, imageListData) } -func SearchImageByName(config searchConfig, image string) error { - username, password := getUsernameAndPassword(config.user) +func SearchImageByName(config SearchConfig, image string) error { + username, password := getUsernameAndPassword(config.User) imageErr := make(chan stringResult) ctx, cancel := context.WithCancel(context.Background()) @@ -70,7 +70,7 @@ func SearchImageByName(config searchConfig, image string) error { wg.Add(1) - go config.searchService.getImageByName(ctx, config, username, password, + go config.SearchService.getImageByName(ctx, config, username, password, image, imageErr, &wg) wg.Add(1) @@ -91,15 +91,15 @@ func SearchImageByName(config searchConfig, image string) error { } } -func SearchImageByNameGQL(config searchConfig, imageName string) error { - username, password := getUsernameAndPassword(config.user) +func SearchImageByNameGQL(config SearchConfig, imageName string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() repo, tag := zcommon.GetImageDirAndTag(imageName) - imageList, err := config.searchService.getImagesGQL(ctx, config, username, password, repo) + imageList, err := config.SearchService.getImagesGQL(ctx, config, username, password, repo) if err != nil { return err } @@ -115,8 +115,8 @@ func SearchImageByNameGQL(config searchConfig, imageName string) error { return printImageResult(config, imageListData) } -func SearchImagesByDigest(config searchConfig, digest string) error { - username, password := getUsernameAndPassword(config.user) +func SearchImagesByDigest(config SearchConfig, digest string) error { + username, password := getUsernameAndPassword(config.User) imageErr := make(chan stringResult) ctx, cancel := context.WithCancel(context.Background()) @@ -124,7 +124,7 @@ func SearchImagesByDigest(config searchConfig, digest string) error { wg.Add(1) - go config.searchService.getImagesByDigest(ctx, config, username, password, + go config.SearchService.getImagesByDigest(ctx, config, username, password, digest, imageErr, &wg) wg.Add(1) @@ -141,13 +141,13 @@ func SearchImagesByDigest(config searchConfig, digest string) error { } } -func SearchDerivedImageListGQL(config searchConfig, derivedImage string) error { - username, password := getUsernameAndPassword(config.user) +func SearchDerivedImageListGQL(config SearchConfig, derivedImage string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - imageList, err := config.searchService.getDerivedImageListGQL(ctx, config, username, + imageList, err := config.SearchService.getDerivedImageListGQL(ctx, config, username, password, derivedImage) if err != nil { return err @@ -162,13 +162,13 @@ func SearchDerivedImageListGQL(config searchConfig, derivedImage string) error { return printImageResult(config, imageListData) } -func SearchBaseImageListGQL(config searchConfig, baseImage string) error { - username, password := getUsernameAndPassword(config.user) +func SearchBaseImageListGQL(config SearchConfig, baseImage string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - imageList, err := config.searchService.getBaseImageListGQL(ctx, config, username, + imageList, err := config.SearchService.getBaseImageListGQL(ctx, config, username, password, baseImage) if err != nil { return err @@ -183,13 +183,13 @@ func SearchBaseImageListGQL(config searchConfig, baseImage string) error { return printImageResult(config, imageListData) } -func SearchImagesForDigestGQL(config searchConfig, digest string) error { - username, password := getUsernameAndPassword(config.user) +func SearchImagesForDigestGQL(config SearchConfig, digest string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - imageList, err := config.searchService.getImagesForDigestGQL(ctx, config, username, password, digest) + imageList, err := config.SearchService.getImagesForDigestGQL(ctx, config, username, password, digest) if err != nil { return err } @@ -207,8 +207,8 @@ func SearchImagesForDigestGQL(config searchConfig, digest string) error { return nil } -func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) error { - username, password := getUsernameAndPassword(config.user) +func SearchCVEForImageGQL(config SearchConfig, image, searchedCveID string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -218,7 +218,7 @@ func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) erro err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error { var err error - cveList, err = config.searchService.getCveByImageGQL(ctx, config, username, password, image, searchedCveID) + cveList, err = config.SearchService.getCveByImageGQL(ctx, config, username, password, image, searchedCveID) if err != nil { if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) { cancel() @@ -226,7 +226,7 @@ func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) erro return err } - fmt.Fprintf(config.resultWriter, + fmt.Fprintf(config.ResultWriter, "[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds())) } @@ -237,30 +237,30 @@ func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) erro } if len(cveList.Data.CVEListForImage.CVEList) == 0 { - fmt.Fprint(config.resultWriter, "No CVEs found for image\n") + fmt.Fprint(config.ResultWriter, "No CVEs found for image\n") return nil } var builder strings.Builder - if config.outputFormat == defaultOutputFormat || config.outputFormat == "" { + if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" { printCVETableHeader(&builder) - fmt.Fprint(config.resultWriter, builder.String()) + fmt.Fprint(config.ResultWriter, builder.String()) } - out, err := cveList.string(config.outputFormat) + out, err := cveList.string(config.OutputFormat) if err != nil { return err } - fmt.Fprint(config.resultWriter, out) + fmt.Fprint(config.ResultWriter, out) return nil } -func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error { - username, password := getUsernameAndPassword(config.user) +func SearchImagesByCVEIDGQL(config SearchConfig, repo, cveid string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -270,7 +270,7 @@ func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error { err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error { var err error - imageList, err = config.searchService.getTagsForCVEGQL(ctx, config, username, password, + imageList, err = config.SearchService.getTagsForCVEGQL(ctx, config, username, password, repo, cveid) if err != nil { if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) { @@ -279,7 +279,7 @@ func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error { return err } - fmt.Fprintf(config.resultWriter, + fmt.Fprintf(config.ResultWriter, "[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds())) } @@ -298,8 +298,8 @@ func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error { return printImageResult(config, imageListData) } -func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error { - username, password := getUsernameAndPassword(config.user) +func SearchFixedTagsGQL(config SearchConfig, repo, cveid string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -309,7 +309,7 @@ func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error { err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error { var err error - fixedTags, err = config.searchService.getFixedTagsForCVEGQL(ctx, config, username, password, + fixedTags, err = config.SearchService.getFixedTagsForCVEGQL(ctx, config, username, password, repo, cveid) if err != nil { if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) { @@ -318,7 +318,7 @@ func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error { return err } - fmt.Fprintf(config.resultWriter, + fmt.Fprintf(config.ResultWriter, "[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds())) } @@ -337,13 +337,13 @@ func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error { return printImageResult(config, imageList) } -func GlobalSearchGQL(config searchConfig, query string) error { - username, password := getUsernameAndPassword(config.user) +func GlobalSearchGQL(config SearchConfig, query string) error { + username, password := getUsernameAndPassword(config.User) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - globalSearchResult, err := config.searchService.globalSearchGQL(ctx, config, username, password, query) + globalSearchResult, err := config.SearchService.globalSearchGQL(ctx, config, username, password, query) if err != nil { return err } @@ -367,8 +367,8 @@ func GlobalSearchGQL(config searchConfig, query string) error { return printRepoResults(config, reposList) } -func SearchReferrersGQL(config searchConfig, subject string) error { - username, password := getUsernameAndPassword(config.user) +func SearchReferrersGQL(config SearchConfig, subject string) error { + username, password := getUsernameAndPassword(config.User) repo, ref, refIsTag, err := zcommon.GetRepoReference(subject) if err != nil { @@ -384,7 +384,7 @@ func SearchReferrersGQL(config searchConfig, subject string) error { } } - response, err := config.searchService.getReferrersGQL(context.Background(), config, username, password, repo, digest) + response, err := config.SearchService.getReferrersGQL(context.Background(), config, username, password, repo, digest) if err != nil { return err } @@ -399,13 +399,13 @@ func SearchReferrersGQL(config searchConfig, subject string) error { } } - printReferrersTableHeader(config, config.resultWriter, maxArtifactTypeLen) + printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen) return printReferrersResult(config, referrersList, maxArtifactTypeLen) } -func SearchReferrers(config searchConfig, subject string) error { - username, password := getUsernameAndPassword(config.user) +func SearchReferrers(config SearchConfig, subject string) error { + username, password := getUsernameAndPassword(config.User) repo, ref, refIsTag, err := zcommon.GetRepoReference(subject) if err != nil { @@ -421,7 +421,7 @@ func SearchReferrers(config searchConfig, subject string) error { } } - referrersList, err := config.searchService.getReferrers(context.Background(), config, username, password, + referrersList, err := config.SearchService.getReferrers(context.Background(), config, username, password, repo, digest) if err != nil { return err @@ -435,13 +435,13 @@ func SearchReferrers(config searchConfig, subject string) error { } } - printReferrersTableHeader(config, config.resultWriter, maxArtifactTypeLen) + printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen) return printReferrersResult(config, referrersList, maxArtifactTypeLen) } -func SearchRepos(config searchConfig) error { - username, password := getUsernameAndPassword(config.user) +func SearchRepos(config SearchConfig) error { + username, password := getUsernameAndPassword(config.User) repoErr := make(chan stringResult) ctx, cancel := context.WithCancel(context.Background()) @@ -449,7 +449,7 @@ func SearchRepos(config searchConfig) error { wg.Add(1) - go config.searchService.getRepos(ctx, config, username, password, repoErr, &wg) + go config.SearchService.getRepos(ctx, config, username, password, repoErr, &wg) wg.Add(1) errCh := make(chan error, 1) diff --git a/pkg/cli/client/search_functions_internal_test.go b/pkg/cli/client/search_functions_internal_test.go index 238615d6..582665e5 100644 --- a/pkg/cli/client/search_functions_internal_test.go +++ b/pkg/cli/client/search_functions_internal_test.go @@ -29,7 +29,7 @@ func TestSearchAllImages(t *testing.T) { Convey("SearchAllImages", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getAllImagesFn: func(ctx context.Context, config searchConfig, username, password string, + getAllImagesFn: func(ctx context.Context, config SearchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { str, err := getMockImageStruct().stringPlainText(10, 10, 10, false) @@ -51,7 +51,7 @@ func TestSearchAllImagesGQL(t *testing.T) { Convey("SearchAllImagesGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string, + getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string, ) (*common.ImageListResponse, error) { return &common.ImageListResponse{ImageList: common.ImageList{ PaginatedImagesResult: common.PaginatedImagesResult{ @@ -72,7 +72,7 @@ func TestSearchAllImagesGQL(t *testing.T) { Convey("SearchAllImagesGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string, + getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string, ) (*common.ImageListResponse, error) { return &common.ImageListResponse{ImageList: common.ImageList{ PaginatedImagesResult: common.PaginatedImagesResult{ @@ -91,7 +91,7 @@ func TestSearchImageByName(t *testing.T) { Convey("SearchImageByName", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImageByNameFn: func(ctx context.Context, config searchConfig, username string, password string, imageName string, + getImageByNameFn: func(ctx context.Context, config SearchConfig, username string, password string, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { str, err := getMockImageStruct().stringPlainText(10, 10, 10, false) @@ -111,7 +111,7 @@ func TestSearchImageByName(t *testing.T) { Convey("SearchImageByName error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImageByNameFn: func(ctx context.Context, config searchConfig, username string, password string, imageName string, + getImageByNameFn: func(ctx context.Context, config SearchConfig, username string, password string, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { channel <- stringResult{StrValue: "", Err: zerr.ErrInjected} @@ -127,7 +127,7 @@ func TestSearchImageByNameGQL(t *testing.T) { Convey("SearchImageByNameGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string, + getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string, ) (*common.ImageListResponse, error) { return &common.ImageListResponse{ImageList: common.ImageList{ PaginatedImagesResult: common.PaginatedImagesResult{ @@ -148,7 +148,7 @@ func TestSearchImageByNameGQL(t *testing.T) { Convey("SearchImageByNameGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string, + getImagesGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName string, ) (*common.ImageListResponse, error) { return &common.ImageListResponse{ImageList: common.ImageList{ PaginatedImagesResult: common.PaginatedImagesResult{ @@ -167,7 +167,7 @@ func TestSearchImagesByDigest(t *testing.T) { Convey("SearchImagesByDigest", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesByDigestFn: func(ctx context.Context, config searchConfig, username string, password string, digest string, + getImagesByDigestFn: func(ctx context.Context, config SearchConfig, username string, password string, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { str, err := getMockImageStruct().stringPlainText(10, 10, 10, false) @@ -187,7 +187,7 @@ func TestSearchImagesByDigest(t *testing.T) { Convey("SearchImagesByDigest error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesByDigestFn: func(ctx context.Context, config searchConfig, username string, password string, digest string, + getImagesByDigestFn: func(ctx context.Context, config SearchConfig, username string, password string, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { rch <- stringResult{StrValue: "", Err: zerr.ErrInjected} @@ -203,7 +203,7 @@ func TestSearchDerivedImageListGQL(t *testing.T) { Convey("SearchDerivedImageListGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getDerivedImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string, + getDerivedImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, derivedImage string) (*common.DerivedImageListResponse, error, ) { return &common.DerivedImageListResponse{DerivedImageList: common.DerivedImageList{ @@ -227,7 +227,7 @@ func TestSearchDerivedImageListGQL(t *testing.T) { Convey("SearchDerivedImageListGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getDerivedImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string, + getDerivedImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, derivedImage string) (*common.DerivedImageListResponse, error, ) { return &common.DerivedImageListResponse{DerivedImageList: common.DerivedImageList{ @@ -245,7 +245,7 @@ func TestSearchBaseImageListGQL(t *testing.T) { Convey("SearchBaseImageListGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getBaseImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string, + getBaseImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, derivedImage string) (*common.BaseImageListResponse, error, ) { return &common.BaseImageListResponse{BaseImageList: common.BaseImageList{ @@ -267,7 +267,7 @@ func TestSearchBaseImageListGQL(t *testing.T) { Convey("SearchBaseImageListGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getBaseImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string, + getBaseImageListGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, derivedImage string) (*common.BaseImageListResponse, error, ) { return &common.BaseImageListResponse{BaseImageList: common.BaseImageList{ @@ -285,7 +285,7 @@ func TestSearchImagesForDigestGQL(t *testing.T) { Convey("SearchImagesForDigestGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesForDigestGQLFn: func(ctx context.Context, config searchConfig, username string, + getImagesForDigestGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, digest string) (*common.ImagesForDigest, error, ) { return &common.ImagesForDigest{ImagesForDigestList: common.ImagesForDigestList{ @@ -307,7 +307,7 @@ func TestSearchImagesForDigestGQL(t *testing.T) { Convey("SearchImagesForDigestGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getImagesForDigestGQLFn: func(ctx context.Context, config searchConfig, username string, + getImagesForDigestGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, digest string) (*common.ImagesForDigest, error, ) { return &common.ImagesForDigest{ImagesForDigestList: common.ImagesForDigestList{ @@ -325,7 +325,7 @@ func TestSearchCVEForImageGQL(t *testing.T) { Convey("SearchCVEForImageGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username string, password string, + getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, imageName string, searchedCVE string) (*cveResult, error, ) { return &cveResult{ @@ -363,7 +363,7 @@ func TestSearchCVEForImageGQL(t *testing.T) { Convey("SearchCVEForImageGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username string, password string, + getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string, imageName string, searchedCVE string) (*cveResult, error, ) { return &cveResult{}, zerr.ErrInjected @@ -379,7 +379,7 @@ func TestSearchImagesByCVEIDGQL(t *testing.T) { Convey("SearchImagesByCVEIDGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password, + getTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string) (*common.ImagesForCve, error, ) { return &common.ImagesForCve{ @@ -405,7 +405,7 @@ func TestSearchImagesByCVEIDGQL(t *testing.T) { Convey("SearchImagesByCVEIDGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password, + getTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string) (*common.ImagesForCve, error, ) { return &common.ImagesForCve{ @@ -425,7 +425,7 @@ func TestSearchFixedTagsGQL(t *testing.T) { Convey("SearchFixedTagsGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getFixedTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password, + getFixedTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string) (*common.ImageListWithCVEFixedResponse, error, ) { return &common.ImageListWithCVEFixedResponse{ @@ -449,7 +449,7 @@ func TestSearchFixedTagsGQL(t *testing.T) { Convey("SearchFixedTagsGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getFixedTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password, + getFixedTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password, imageName, cveID string) (*common.ImageListWithCVEFixedResponse, error, ) { return &common.ImageListWithCVEFixedResponse{ @@ -469,7 +469,7 @@ func TestSearchReferrersGQL(t *testing.T) { Convey("SearchReferrersGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getReferrersGQLFn: func(ctx context.Context, config searchConfig, username, password, + getReferrersGQLFn: func(ctx context.Context, config SearchConfig, username, password, repo, digest string) (*common.ReferrersResp, error, ) { return &common.ReferrersResp{ @@ -497,7 +497,7 @@ func TestSearchReferrersGQL(t *testing.T) { Convey("SearchReferrersGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getReferrersGQLFn: func(ctx context.Context, config searchConfig, username, password, + getReferrersGQLFn: func(ctx context.Context, config SearchConfig, username, password, repo, digest string) (*common.ReferrersResp, error, ) { return &common.ReferrersResp{}, zerr.ErrInjected @@ -513,7 +513,7 @@ func TestGlobalSearchGQL(t *testing.T) { Convey("GlobalSearchGQL", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - globalSearchGQLFn: func(ctx context.Context, config searchConfig, username, password, + globalSearchGQLFn: func(ctx context.Context, config SearchConfig, username, password, query string) (*common.GlobalSearch, error, ) { return &common.GlobalSearch{ @@ -538,7 +538,7 @@ func TestGlobalSearchGQL(t *testing.T) { Convey("GlobalSearchGQL error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - globalSearchGQLFn: func(ctx context.Context, config searchConfig, username, password, + globalSearchGQLFn: func(ctx context.Context, config SearchConfig, username, password, query string) (*common.GlobalSearch, error, ) { return &common.GlobalSearch{}, zerr.ErrInjected @@ -554,7 +554,7 @@ func TestSearchReferrers(t *testing.T) { Convey("SearchReferrers", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getReferrersFn: func(ctx context.Context, config searchConfig, username string, password string, + getReferrersFn: func(ctx context.Context, config SearchConfig, username string, password string, repo string, digest string) (referrersResult, error, ) { return referrersResult([]common.Referrer{ @@ -580,7 +580,7 @@ func TestSearchReferrers(t *testing.T) { Convey("SearchReferrers error", t, func() { buff := bytes.NewBufferString("") searchConfig := getMockSearchConfig(buff, mockService{ - getReferrersFn: func(ctx context.Context, config searchConfig, username string, password string, + getReferrersFn: func(ctx context.Context, config SearchConfig, username string, password string, repo string, digest string) (referrersResult, error, ) { return referrersResult{}, zerr.ErrInjected @@ -607,17 +607,17 @@ func TestSearchRepos(t *testing.T) { }) } -func getMockSearchConfig(buff *bytes.Buffer, mockService mockService) searchConfig { - return searchConfig{ - resultWriter: buff, - user: "", - searchService: mockService, - servURL: "http://127.0.0.1:8000", - outputFormat: "", - verifyTLS: false, - fixedFlag: false, - verbose: false, - debug: false, +func getMockSearchConfig(buff *bytes.Buffer, mockService mockService) SearchConfig { + return SearchConfig{ + ResultWriter: buff, + User: "", + SearchService: mockService, + ServURL: "http://127.0.0.1:8000", + OutputFormat: "", + VerifyTLS: false, + FixedFlag: false, + Verbose: false, + Debug: false, } } @@ -744,19 +744,19 @@ func TestUtils(t *testing.T) { Convey("CheckExtEndPointQuery", t, func() { // invalid url - err := CheckExtEndPointQuery(searchConfig{ - user: "", - servURL: "bad-url", + err := CheckExtEndPointQuery(SearchConfig{ + User: "", + ServURL: "bad-url", }) So(err, ShouldNotBeNil) // good url but no connection - err = CheckExtEndPointQuery(searchConfig{ - user: "", - servURL: "http://127.0.0.1:5000", - verifyTLS: false, - debug: false, - resultWriter: io.Discard, + err = CheckExtEndPointQuery(SearchConfig{ + User: "", + ServURL: "http://127.0.0.1:5000", + VerifyTLS: false, + Debug: false, + ResultWriter: io.Discard, }) So(err, ShouldNotBeNil) }) diff --git a/pkg/cli/client/service.go b/pkg/cli/client/service.go index 016a1e69..82089d7f 100644 --- a/pkg/cli/client/service.go +++ b/pkg/cli/client/service.go @@ -32,49 +32,49 @@ const ( ) type SearchService interface { //nolint:interfacebloat - getImagesGQL(ctx context.Context, config searchConfig, username, password string, + getImagesGQL(ctx context.Context, config SearchConfig, username, password string, imageName string) (*common.ImageListResponse, error) - getImagesForDigestGQL(ctx context.Context, config searchConfig, username, password string, + getImagesForDigestGQL(ctx context.Context, config SearchConfig, username, password string, digest string) (*common.ImagesForDigest, error) - getCveByImageGQL(ctx context.Context, config searchConfig, username, password, + getCveByImageGQL(ctx context.Context, config SearchConfig, username, password, imageName string, searchedCVE string) (*cveResult, error) - getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, repo, + getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, repo, cveID string) (*common.ImagesForCve, error) - getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName, + getFixedTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, imageName, cveID string) (*common.ImageListWithCVEFixedResponse, error) - getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string, + getDerivedImageListGQL(ctx context.Context, config SearchConfig, username, password string, derivedImage string) (*common.DerivedImageListResponse, error) - getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string, + getBaseImageListGQL(ctx context.Context, config SearchConfig, username, password string, baseImage string) (*common.BaseImageListResponse, error) - getReferrersGQL(ctx context.Context, config searchConfig, username, password string, + getReferrersGQL(ctx context.Context, config SearchConfig, username, password string, repo, digest string) (*common.ReferrersResp, error) - globalSearchGQL(ctx context.Context, config searchConfig, username, password string, + globalSearchGQL(ctx context.Context, config SearchConfig, username, password string, query string) (*common.GlobalSearch, error) - getAllImages(ctx context.Context, config searchConfig, username, password string, + getAllImages(ctx context.Context, config SearchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup) - getImagesByDigest(ctx context.Context, config searchConfig, username, password, digest string, + getImagesByDigest(ctx context.Context, config SearchConfig, username, password, digest string, channel chan stringResult, wtgrp *sync.WaitGroup) - getRepos(ctx context.Context, config searchConfig, username, password string, + getRepos(ctx context.Context, config SearchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup) - getImageByName(ctx context.Context, config searchConfig, username, password, imageName string, + getImageByName(ctx context.Context, config SearchConfig, username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup) - getReferrers(ctx context.Context, config searchConfig, username, password string, repo, digest string, + getReferrers(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (referrersResult, error) } -type searchConfig struct { - searchService SearchService - servURL string - user string - outputFormat string - sortBy string - verifyTLS bool - fixedFlag bool - verbose bool - debug bool - resultWriter io.Writer - spinner spinnerState +type SearchConfig struct { + SearchService SearchService + ServURL string + User string + OutputFormat string + SortBy string + VerifyTLS bool + FixedFlag bool + Verbose bool + Debug bool + ResultWriter io.Writer + Spinner spinnerState } type searchService struct{} @@ -83,7 +83,7 @@ func NewSearchService() SearchService { return searchService{} } -func (service searchService) getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getDerivedImageListGQL(ctx context.Context, config SearchConfig, username, password string, derivedImage string, ) (*common.DerivedImageListResponse, error) { query := fmt.Sprintf(` @@ -107,7 +107,7 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config IsSigned } } - }`, derivedImage, Flag2SortCriteria(config.sortBy)) + }`, derivedImage, Flag2SortCriteria(config.SortBy)) result := &common.DerivedImageListResponse{} err := service.makeGraphQLQuery(ctx, config, username, password, query, result) @@ -119,7 +119,7 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config return result, nil } -func (service searchService) getReferrersGQL(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getReferrersGQL(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (*common.ReferrersResp, error) { query := fmt.Sprintf(` @@ -146,7 +146,7 @@ func (service searchService) getReferrersGQL(ctx context.Context, config searchC return result, nil } -func (service searchService) globalSearchGQL(ctx context.Context, config searchConfig, username, password string, +func (service searchService) globalSearchGQL(ctx context.Context, config SearchConfig, username, password string, query string, ) (*common.GlobalSearch, error) { GQLQuery := fmt.Sprintf(` @@ -179,7 +179,7 @@ func (service searchService) globalSearchGQL(ctx context.Context, config searchC StarCount } } - }`, query, Flag2SortCriteria(config.sortBy)) + }`, query, Flag2SortCriteria(config.SortBy)) result := &common.GlobalSearchResultResp{} @@ -191,7 +191,7 @@ func (service searchService) globalSearchGQL(ctx context.Context, config searchC return &result.GlobalSearch, nil } -func (service searchService) getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getBaseImageListGQL(ctx context.Context, config SearchConfig, username, password string, baseImage string, ) (*common.BaseImageListResponse, error) { query := fmt.Sprintf(` @@ -215,7 +215,7 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea IsSigned } } - }`, baseImage, Flag2SortCriteria(config.sortBy)) + }`, baseImage, Flag2SortCriteria(config.SortBy)) result := &common.BaseImageListResponse{} err := service.makeGraphQLQuery(ctx, config, username, password, query, result) @@ -227,7 +227,7 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea return result, nil } -func (service searchService) getImagesGQL(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getImagesGQL(ctx context.Context, config SearchConfig, username, password string, imageName string, ) (*common.ImageListResponse, error) { query := fmt.Sprintf(` @@ -251,7 +251,7 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf IsSigned } } - }`, imageName, Flag2SortCriteria(config.sortBy)) + }`, imageName, Flag2SortCriteria(config.SortBy)) result := &common.ImageListResponse{} err := service.makeGraphQLQuery(ctx, config, username, password, query, result) @@ -263,7 +263,7 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf return result, nil } -func (service searchService) getImagesForDigestGQL(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getImagesForDigestGQL(ctx context.Context, config SearchConfig, username, password string, digest string, ) (*common.ImagesForDigest, error) { query := fmt.Sprintf(` @@ -287,7 +287,7 @@ func (service searchService) getImagesForDigestGQL(ctx context.Context, config s IsSigned } } - }`, digest, Flag2SortCriteria(config.sortBy)) + }`, digest, Flag2SortCriteria(config.SortBy)) result := &common.ImagesForDigest{} err := service.makeGraphQLQuery(ctx, config, username, password, query, result) @@ -299,7 +299,7 @@ func (service searchService) getImagesForDigestGQL(ctx context.Context, config s return result, nil } -func (service searchService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password, +func (service searchService) getCveByImageGQL(ctx context.Context, config SearchConfig, username, password, imageName, searchedCVE string, ) (*cveResult, error) { query := fmt.Sprintf(` @@ -310,7 +310,7 @@ func (service searchService) getCveByImageGQL(ctx context.Context, config search PackageList {Name InstalledVersion FixedVersion} } } - }`, imageName, searchedCVE, Flag2SortCriteria(config.sortBy)) + }`, imageName, searchedCVE, Flag2SortCriteria(config.SortBy)) result := &cveResult{} err := service.makeGraphQLQuery(ctx, config, username, password, query, result) @@ -322,7 +322,7 @@ func (service searchService) getCveByImageGQL(ctx context.Context, config search return result, nil } -func (service searchService) getTagsForCVEGQL(ctx context.Context, config searchConfig, +func (service searchService) getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, repo, cveID string, ) (*common.ImagesForCve, error) { query := fmt.Sprintf(` @@ -347,7 +347,7 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search } } }`, - cveID, Flag2SortCriteria(config.sortBy)) + cveID, Flag2SortCriteria(config.SortBy)) result := &common.ImagesForCve{} err := service.makeGraphQLQuery(ctx, config, username, password, query, result) @@ -371,7 +371,7 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search return filteredResults, nil } -func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, +func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, imageName, cveID string, ) (*common.ImageListWithCVEFixedResponse, error) { query := fmt.Sprintf(` @@ -409,10 +409,10 @@ func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config s return result, nil } -func (service searchService) getReferrers(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getReferrers(ctx context.Context, config SearchConfig, username, password string, repo, digest string, ) (referrersResult, error) { - referrersEndpoint, err := combineServerAndEndpointURL(config.servURL, + referrersEndpoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("/v2/%s/referrers/%s", repo, digest)) if err != nil { if isContextDone(ctx) { @@ -423,8 +423,8 @@ func (service searchService) getReferrers(ctx context.Context, config searchConf } referrerResp := &ispec.Index{} - _, err = makeGETRequest(ctx, referrersEndpoint, username, password, config.verifyTLS, - config.debug, &referrerResp, config.resultWriter) + _, err = makeGETRequest(ctx, referrersEndpoint, username, password, config.VerifyTLS, + config.Debug, &referrerResp, config.ResultWriter) if err != nil { if isContextDone(ctx) { @@ -447,7 +447,7 @@ func (service searchService) getReferrers(ctx context.Context, config searchConf return referrersList, nil } -func (service searchService) getImageByName(ctx context.Context, config searchConfig, +func (service searchService) getImageByName(ctx context.Context, config SearchConfig, username, password, imageName string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() @@ -466,7 +466,7 @@ func (service searchService) getImageByName(ctx context.Context, config searchCo localWg.Wait() } -func (service searchService) getAllImages(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getAllImages(ctx context.Context, config SearchConfig, username, password string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() @@ -474,7 +474,7 @@ func (service searchService) getAllImages(ctx context.Context, config searchConf catalog := &catalogResponse{} - catalogEndPoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("%s%s", + catalogEndPoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("%s%s", constants.RoutePrefix, constants.ExtCatalogPrefix)) if err != nil { if isContextDone(ctx) { @@ -485,8 +485,8 @@ func (service searchService) getAllImages(ctx context.Context, config searchConf return } - _, err = makeGETRequest(ctx, catalogEndPoint, username, password, config.verifyTLS, - config.debug, catalog, config.resultWriter) + _, err = makeGETRequest(ctx, catalogEndPoint, username, password, config.VerifyTLS, + config.Debug, catalog, config.ResultWriter) if err != nil { if isContextDone(ctx) { return @@ -513,14 +513,14 @@ func (service searchService) getAllImages(ctx context.Context, config searchConf localWg.Wait() } -func getImage(ctx context.Context, config searchConfig, username, password, imageName string, +func getImage(ctx context.Context, config SearchConfig, username, password, imageName string, rch chan stringResult, wtgrp *sync.WaitGroup, pool *requestsPool, ) { defer wtgrp.Done() repo, imageTag := common.GetImageDirAndTag(imageName) - tagListEndpoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("/v2/%s/tags/list", repo)) + tagListEndpoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("/v2/%s/tags/list", repo)) if err != nil { if isContextDone(ctx) { return @@ -531,8 +531,8 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag } tagList := &tagListResp{} - _, err = makeGETRequest(ctx, tagListEndpoint, username, password, config.verifyTLS, - config.debug, &tagList, config.resultWriter) + _, err = makeGETRequest(ctx, tagListEndpoint, username, password, config.VerifyTLS, + config.Debug, &tagList, config.ResultWriter) if err != nil { if isContextDone(ctx) { @@ -567,7 +567,7 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag } } -func (service searchService) getImagesByDigest(ctx context.Context, config searchConfig, username, +func (service searchService) getImagesByDigest(ctx context.Context, config SearchConfig, username, password string, digest string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() @@ -652,16 +652,16 @@ func isContextDone(ctx context.Context) bool { // Query using GQL, the query string is passed as a parameter // errors are returned in the stringResult channel, the unmarshalled payload is in resultPtr. func (service searchService) makeGraphQLQuery(ctx context.Context, - config searchConfig, username, password, query string, + config SearchConfig, username, password, query string, resultPtr interface{}, ) error { - endPoint, err := combineServerAndEndpointURL(config.servURL, constants.FullSearchPrefix) + endPoint, err := combineServerAndEndpointURL(config.ServURL, constants.FullSearchPrefix) if err != nil { return err } - err = makeGraphQLRequest(ctx, endPoint, query, username, password, config.verifyTLS, - config.debug, resultPtr, config.resultWriter) + err = makeGraphQLRequest(ctx, endPoint, query, username, password, config.VerifyTLS, + config.Debug, resultPtr, config.ResultWriter) if err != nil { return err } @@ -697,12 +697,12 @@ func checkResultGraphQLQuery(ctx context.Context, err error, resultErrors []comm return nil } -func addManifestCallToPool(ctx context.Context, config searchConfig, pool *requestsPool, +func addManifestCallToPool(ctx context.Context, config SearchConfig, pool *requestsPool, username, password, imageName, tagName string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() - manifestEndpoint, err := combineServerAndEndpointURL(config.servURL, + manifestEndpoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("/v2/%s/manifests/%s", imageName, tagName)) if err != nil { if isContextDone(ctx) { @@ -1304,7 +1304,7 @@ func getRepoTableWriter(writer io.Writer) *tablewriter.Table { return table } -func (service searchService) getRepos(ctx context.Context, config searchConfig, username, password string, +func (service searchService) getRepos(ctx context.Context, config SearchConfig, username, password string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { defer wtgrp.Done() @@ -1312,7 +1312,7 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig, catalog := &catalogResponse{} - catalogEndPoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("%s%s", + catalogEndPoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("%s%s", constants.RoutePrefix, constants.ExtCatalogPrefix)) if err != nil { if isContextDone(ctx) { @@ -1323,8 +1323,8 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig, return } - _, err = makeGETRequest(ctx, catalogEndPoint, username, password, config.verifyTLS, - config.debug, catalog, config.resultWriter) + _, err = makeGETRequest(ctx, catalogEndPoint, username, password, config.VerifyTLS, + config.Debug, catalog, config.ResultWriter) if err != nil { if isContextDone(ctx) { return @@ -1334,15 +1334,15 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig, return } - fmt.Fprintln(config.resultWriter, "\nREPOSITORY NAME") + fmt.Fprintln(config.ResultWriter, "\nREPOSITORY NAME") - if config.sortBy == SortByAlphabeticAsc { + if config.SortBy == SortByAlphabeticAsc { for i := 0; i < len(catalog.Repositories); i++ { - fmt.Fprintln(config.resultWriter, catalog.Repositories[i]) + fmt.Fprintln(config.ResultWriter, catalog.Repositories[i]) } } else { for i := len(catalog.Repositories) - 1; i >= 0; i-- { - fmt.Fprintln(config.resultWriter, catalog.Repositories[i]) + fmt.Fprintln(config.ResultWriter, catalog.Repositories[i]) } } } diff --git a/pkg/cli/client/utils.go b/pkg/cli/client/utils.go index 458cd940..91f482a9 100644 --- a/pkg/cli/client/utils.go +++ b/pkg/cli/client/utils.go @@ -30,31 +30,31 @@ func ref[T any](input T) *T { return &ref } -func fetchImageDigest(repo, ref, username, password string, config searchConfig) (string, error) { - url, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("/v2/%s/manifests/%s", repo, ref)) +func fetchImageDigest(repo, ref, username, password string, config SearchConfig) (string, error) { + url, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("/v2/%s/manifests/%s", repo, ref)) if err != nil { return "", err } - res, err := makeHEADRequest(context.Background(), url, username, password, config.verifyTLS, false) + res, err := makeHEADRequest(context.Background(), url, username, password, config.VerifyTLS, false) digestStr := res.Get(constants.DistContentDigestKey) return digestStr, err } -func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan stringResult, +func collectResults(config SearchConfig, wg *sync.WaitGroup, imageErr chan stringResult, cancel context.CancelFunc, printHeader printHeader, errCh chan error, ) { var foundResult bool defer wg.Done() - config.spinner.startSpinner() + config.Spinner.startSpinner() for { select { case result, ok := <-imageErr: - config.spinner.stopSpinner() + config.Spinner.stopSpinner() if !ok { cancel() @@ -69,18 +69,18 @@ func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan strin return } - if !foundResult && (config.outputFormat == defaultOutputFormat || config.outputFormat == "") { + if !foundResult && (config.OutputFormat == defaultOutputFormat || config.OutputFormat == "") { var builder strings.Builder - printHeader(&builder, config.verbose, 0, 0, 0) - fmt.Fprint(config.resultWriter, builder.String()) + printHeader(&builder, config.Verbose, 0, 0, 0) + fmt.Fprint(config.ResultWriter, builder.String()) } foundResult = true - fmt.Fprint(config.resultWriter, result.StrValue) + fmt.Fprint(config.ResultWriter, result.StrValue) case <-time.After(waitTimeout): - config.spinner.stopSpinner() + config.Spinner.stopSpinner() cancel() errCh <- zerr.ErrCLITimeout @@ -193,8 +193,8 @@ func printCVETableHeader(writer io.Writer) { table.Render() } -func printReferrersTableHeader(config searchConfig, writer io.Writer, maxArtifactTypeLen int) { - if config.outputFormat != "" && config.outputFormat != defaultOutputFormat { +func printReferrersTableHeader(config SearchConfig, writer io.Writer, maxArtifactTypeLen int) { + if config.OutputFormat != "" && config.OutputFormat != defaultOutputFormat { return } @@ -269,18 +269,18 @@ func printRepoTableHeader(writer io.Writer, repoMaxLen, maxTimeLen int, verbose table.Render() } -func printReferrersResult(config searchConfig, referrersList referrersResult, maxArtifactTypeLen int) error { - out, err := referrersList.string(config.outputFormat, maxArtifactTypeLen) +func printReferrersResult(config SearchConfig, referrersList referrersResult, maxArtifactTypeLen int) error { + out, err := referrersList.string(config.OutputFormat, maxArtifactTypeLen) if err != nil { return err } - fmt.Fprint(config.resultWriter, out) + fmt.Fprint(config.ResultWriter, out) return nil } -func printImageResult(config searchConfig, imageList []imageStruct) error { +func printImageResult(config SearchConfig, imageList []imageStruct) error { var builder strings.Builder maxImgNameLen := 0 maxTagLen := 0 @@ -305,29 +305,29 @@ func printImageResult(config searchConfig, imageList []imageStruct) error { } } - if config.outputFormat == defaultOutputFormat || config.outputFormat == "" { - printImageTableHeader(&builder, config.verbose, maxImgNameLen, maxTagLen, maxPlatformLen) + if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" { + printImageTableHeader(&builder, config.Verbose, maxImgNameLen, maxTagLen, maxPlatformLen) } - fmt.Fprint(config.resultWriter, builder.String()) + fmt.Fprint(config.ResultWriter, builder.String()) } for i := range imageList { img := imageList[i] - verbose := config.verbose + verbose := config.Verbose - out, err := img.string(config.outputFormat, maxImgNameLen, maxTagLen, maxPlatformLen, verbose) + out, err := img.string(config.OutputFormat, maxImgNameLen, maxTagLen, maxPlatformLen, verbose) if err != nil { return err } - fmt.Fprint(config.resultWriter, out) + fmt.Fprint(config.ResultWriter, out) } return nil } -func printRepoResults(config searchConfig, repoList []repoStruct) error { +func printRepoResults(config SearchConfig, repoList []repoStruct) error { maxRepoNameLen := 0 maxTimeLen := 0 @@ -341,31 +341,31 @@ func printRepoResults(config searchConfig, repoList []repoStruct) error { } } - if len(repoList) > 0 && (config.outputFormat == defaultOutputFormat || config.outputFormat == "") { - printRepoTableHeader(config.resultWriter, maxRepoNameLen, maxTimeLen, config.verbose) + if len(repoList) > 0 && (config.OutputFormat == defaultOutputFormat || config.OutputFormat == "") { + printRepoTableHeader(config.ResultWriter, maxRepoNameLen, maxTimeLen, config.Verbose) } for _, repo := range repoList { - out, err := repo.string(config.outputFormat, maxRepoNameLen, maxTimeLen, config.verbose) + out, err := repo.string(config.OutputFormat, maxRepoNameLen, maxTimeLen, config.Verbose) if err != nil { return err } - fmt.Fprint(config.resultWriter, out) + fmt.Fprint(config.ResultWriter, out) } return nil } -func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (searchConfig, error) { +func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (SearchConfig, error) { serverURL, err := GetServerURLFromFlags(cmd) if err != nil { - return searchConfig{}, err + return SearchConfig{}, err } isSpinner, verifyTLS, err := GetCliConfigOptions(cmd) if err != nil { - return searchConfig{}, err + return SearchConfig{}, err } flags := cmd.Flags() @@ -379,18 +379,18 @@ func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) ( spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr())) spin.Prefix = prefix - return searchConfig{ - searchService: searchService, - servURL: serverURL, - user: user, - outputFormat: outputFormat, - verifyTLS: verifyTLS, - fixedFlag: fixed, - verbose: verbose, - debug: debug, - sortBy: sortBy, - spinner: spinnerState{spin, isSpinner}, - resultWriter: cmd.OutOrStdout(), + return SearchConfig{ + SearchService: searchService, + ServURL: serverURL, + User: user, + OutputFormat: outputFormat, + VerifyTLS: verifyTLS, + FixedFlag: fixed, + Verbose: verbose, + Debug: debug, + SortBy: sortBy, + Spinner: spinnerState{spin, isSpinner}, + ResultWriter: cmd.OutOrStdout(), }, nil } diff --git a/pkg/cli/client/utils_internal_test.go b/pkg/cli/client/utils_internal_test.go index 6b46a0fe..21cd1a39 100644 --- a/pkg/cli/client/utils_internal_test.go +++ b/pkg/cli/client/utils_internal_test.go @@ -20,19 +20,19 @@ import ( test "zotregistry.io/zot/pkg/test/common" ) -func getDefaultSearchConf(baseURL string) searchConfig { +func getDefaultSearchConf(baseURL string) SearchConfig { verifyTLS := false debug := false verbose := true outputFormat := "text" - return searchConfig{ - servURL: baseURL, - resultWriter: io.Discard, - verifyTLS: verifyTLS, - debug: debug, - verbose: verbose, - outputFormat: outputFormat, + return SearchConfig{ + ServURL: baseURL, + ResultWriter: io.Discard, + VerifyTLS: verifyTLS, + Debug: debug, + Verbose: verbose, + OutputFormat: outputFormat, } }