diff --git a/golangcilint.yaml b/golangcilint.yaml index 5cab14f7..7380c09a 100644 --- a/golangcilint.yaml +++ b/golangcilint.yaml @@ -50,6 +50,7 @@ linters-settings: - github.com/containers/image/v5 - github.com/opencontainers/image-spec - github.com/open-policy-agent/opa + - github.com/vektah/gqlparser/v2 - go.opentelemetry.io/otel - go.opentelemetry.io/otel/exporters/otlp - go.opentelemetry.io/otel/metric diff --git a/pkg/cli/client.go b/pkg/cli/client.go index 7b87f7b9..e03ef622 100644 --- a/pkg/cli/client.go +++ b/pkg/cli/client.go @@ -15,6 +15,7 @@ import ( "net/url" "os" "path/filepath" + "strconv" "strings" "sync" "time" @@ -283,16 +284,12 @@ func (p *requestsPool) doJob(ctx context.Context, job *manifestJob) { image := &imageStruct{} image.verbose = *job.config.verbose - image.Name = job.imageName - image.Tags = []tags{ - { - Name: job.tagName, - Digest: digest, - Size: size, - ConfigDigest: configDigest, - Layers: layers, - }, - } + image.RepoName = job.imageName + image.Tag = job.tagName + image.Digest = digest + image.Size = strconv.Itoa(int(size)) + image.ConfigDigest = configDigest + image.Layers = layers str, err := image.string(*job.config.outputFormat) if err != nil { diff --git a/pkg/cli/client_test.go b/pkg/cli/client_test.go index b639b8b0..b7e7c9eb 100644 --- a/pkg/cli/client_test.go +++ b/pkg/cli/client_test.go @@ -20,6 +20,7 @@ import ( "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" "zotregistry.io/zot/pkg/api/constants" + extConf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/test" ) @@ -69,6 +70,11 @@ func TestTLSWithAuth(t *testing.T) { CACert: CACert, } + enable := true + conf.Extensions = &extConf.ExtensionConfig{ + Search: &extConf.SearchConfig{Enable: &enable}, + } + ctlr := api.NewController(conf) ctlr.Config.Storage.RootDirectory = t.TempDir() go func() { @@ -161,6 +167,11 @@ func TestTLSWithoutAuth(t *testing.T) { CACert: CACert, } + enable := true + conf.Extensions = &extConf.ExtensionConfig{ + Search: &extConf.SearchConfig{Enable: &enable}, + } + ctlr := api.NewController(conf) ctlr.Config.Storage.RootDirectory = t.TempDir() go func() { diff --git a/pkg/cli/cve_cmd.go b/pkg/cli/cve_cmd.go index 7548a070..fdc72e2a 100644 --- a/pkg/cli/cve_cmd.go +++ b/pkg/cli/cve_cmd.go @@ -4,13 +4,18 @@ package cli import ( + "encoding/json" "fmt" + "net/http" + "net/url" "os" "path" "github.com/briandowns/spinner" "github.com/spf13/cobra" + "gopkg.in/resty.v1" zotErrors "zotregistry.io/zot/errors" + "zotregistry.io/zot/pkg/api/constants" ) func NewCveCommand(searchService SearchService) *cobra.Command { @@ -68,7 +73,8 @@ func NewCveCommand(searchService SearchService) *cobra.Command { } spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr())) - spin.Prefix = fmt.Sprintf("Fetching from %s.. ", servURL) + spin.Prefix = fmt.Sprintf("Fetching from %s..", servURL) + spin.Suffix = "\n\b" verbose = false @@ -112,7 +118,7 @@ func NewCveCommand(searchService SearchService) *cobra.Command { func setupCveFlags(cveCmd *cobra.Command, variables cveFlagVariables) { variables.searchCveParams["imageName"] = cveCmd.Flags().StringP("image", "I", "", "List CVEs by IMAGENAME[:TAG]") - variables.searchCveParams["cvid"] = cveCmd.Flags().StringP("cve-id", "i", "", "List images affected by a CVE") + variables.searchCveParams["cveID"] = cveCmd.Flags().StringP("cve-id", "i", "", "List images affected by a CVE") cveCmd.Flags().StringVar(variables.servURL, "url", "", "Specify zot server URL if config-name is not mentioned") cveCmd.Flags().StringVarP(variables.user, "user", "u", "", `User Credentials of `+ @@ -131,8 +137,80 @@ type cveFlagVariables struct { fixedFlag *bool } +type field struct { + Name string `json:"name"` +} + +type schemaList struct { + Data struct { + Schema struct { + QueryType struct { + Fields []field `json:"fields"` + } `json:"queryType"` //nolint:tagliatelle // graphQL schema + } `json:"__schema"` //nolint:tagliatelle // graphQL schema + } `json:"data"` +} + +func containsGQLQuery(queryList []field, query string) bool { + for _, q := range queryList { + if q.Name == query { + return true + } + } + + return false +} + +func checkExtEndPoint(serverURL string) bool { + client := resty.New() + + extEndPoint, err := combineServerAndEndpointURL(serverURL, fmt.Sprintf("%s%s", + constants.RoutePrefix, constants.ExtOciDiscoverPrefix)) + if err != nil { + return false + } + + // nolint: gosec + resp, err := client.R().Get(extEndPoint) + if err != nil || resp.StatusCode() != http.StatusOK { + return false + } + + searchEndPoint, _ := combineServerAndEndpointURL(serverURL, constants.ExtSearchPrefix) + + query := ` + { + __schema() { + queryType { + fields { + name + } + } + } + }` + + resp, err = client.R().Get(searchEndPoint + "?query=" + url.QueryEscape(query)) + if err != nil || resp.StatusCode() != http.StatusOK { + return false + } + + queryList := &schemaList{} + + _ = json.Unmarshal(resp.Body(), queryList) + + return containsGQLQuery(queryList.Data.Schema.QueryType.Fields, "ImageList") +} + func searchCve(searchConfig searchConfig) error { - for _, searcher := range getCveSearchers() { + var searchers []searcher + + if checkExtEndPoint(*searchConfig.servURL) { + searchers = getCveSearchersGQL() + } else { + searchers = getCveSearchers() + } + + for _, searcher := range searchers { found, err := searcher.search(searchConfig) if found { if err != nil { diff --git a/pkg/cli/cve_cmd_test.go b/pkg/cli/cve_cmd_test.go index d6ce8ebd..8f277810 100644 --- a/pkg/cli/cve_cmd_test.go +++ b/pkg/cli/cve_cmd_test.go @@ -15,6 +15,7 @@ import ( "time" . "github.com/smartystreets/goconvey/convey" + "github.com/spf13/cobra" "gopkg.in/resty.v1" zotErrors "zotregistry.io/zot/errors" "zotregistry.io/zot/pkg/api" @@ -51,6 +52,7 @@ func TestSearchCVECmd(t *testing.T) { So(err, ShouldBeNil) }) }) + Convey("Test CVE no url", t, func() { args := []string{"cvetest", "-i", "cveIdRandom"} configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`) @@ -136,10 +138,8 @@ func TestSearchCVECmd(t *testing.T) { Convey("Test CVE url from config", t, func() { args := []string{"cvetest", "--image", "dummyImageName:tag"} - configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewCveCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -162,10 +162,10 @@ func TestSearchCVECmd(t *testing.T) { cveCmd.SetErr(buff) cveCmd.SetArgs(args) err := cveCmd.Execute() + So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE dummyImageName tag DigestsA 123kB") - So(err, ShouldBeNil) Convey("using shorthand", func() { args := []string{"cvetest", "-I", "dummyImageName", "--cve-id", "aCVEID", "--url", "someURL"} buff := bytes.NewBufferString("") @@ -176,11 +176,10 @@ func TestSearchCVECmd(t *testing.T) { cveCmd.SetErr(buff) cveCmd.SetArgs(args) err := cveCmd.Execute() - + So(err, ShouldBeNil) space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE dummyImageName tag DigestsA 123kB") - So(err, ShouldBeNil) }) }) @@ -266,6 +265,34 @@ func TestSearchCVECmd(t *testing.T) { str := space.ReplaceAllString(buff.String(), " ") So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE anImage tag DigestsA 123kB") So(err, ShouldBeNil) + + Convey("invalid CVE ID", func() { + args := []string{"cvetest", "--cve-id", "invalidCVEID"} + configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`) + defer os.Remove(configPath) + cveCmd := NewCveCommand(new(mockService)) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("invalid url", func() { + args := []string{"cvetest", "--cve-id", "aCVEID", "--url", "invalidURL"} + configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`) + defer os.Remove(configPath) + cveCmd := NewCveCommand(NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + So(err, ShouldNotBeNil) + So(err, ShouldEqual, zotErrors.ErrInvalidURL) + So(buff.String(), ShouldContainSubstring, "invalid URL format") + }) }) Convey("Test fixed tags by and image name CVE ID", t, func() { @@ -282,10 +309,24 @@ func TestSearchCVECmd(t *testing.T) { str := space.ReplaceAllString(buff.String(), " ") So(err, ShouldBeNil) So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE fixedImage tag DigestsA 123kB") + + Convey("invalid image name", func() { + args := []string{"cvetest", "--cve-id", "aCVEID", "--image", "invalidImageName"} + configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`) + defer os.Remove(configPath) + cveCmd := NewCveCommand(NewSearchService()) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err := cveCmd.Execute() + So(err, ShouldNotBeNil) + }) }) } -func TestServerCVEResponse(t *testing.T) { +// nolint: dupl // GQL +func TestServerCVEResponseGQL(t *testing.T) { port := test.GetFreePort() url := test.GetBaseURL(port) conf := config.New() @@ -351,6 +392,7 @@ func TestServerCVEResponse(t *testing.T) { So(err, ShouldBeNil) So(str, ShouldContainSubstring, "ID SEVERITY TITLE") So(str, ShouldContainSubstring, "CVE") + Convey("invalid image", func() { args := []string{"cvetest", "--image", "invalid:0.0.1"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) @@ -363,6 +405,33 @@ func TestServerCVEResponse(t *testing.T) { err = cveCmd.Execute() So(err, ShouldNotBeNil) }) + + Convey("invalid image name and tag", func() { + args := []string{"cvetest", "--image", "invalid:"} + 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("invalid output format", func() { + args := []string{"cvetest", "--image", "zot-cve-test:0.0.1", "-o", "random"} + 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", t, func() { @@ -380,6 +449,7 @@ func TestServerCVEResponse(t *testing.T) { str = strings.TrimSpace(str) So(err, ShouldBeNil) So(str, ShouldEqual, "IMAGE NAME TAG DIGEST SIZE zot-cve-test 0.0.1 63a795ca 75MB") + Convey("invalid CVE ID", func() { args := []string{"cvetest", "--cve-id", "invalid"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) @@ -396,6 +466,20 @@ func TestServerCVEResponse(t *testing.T) { So(err, ShouldBeNil) So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIZE") }) + + Convey("invalid output format", func() { + args := []string{"cvetest", "--cve-id", "CVE-2019-9923", "-o", "random"} + 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 and image name CVE ID", t, func() { @@ -413,6 +497,7 @@ func TestServerCVEResponse(t *testing.T) { str = strings.TrimSpace(str) So(err, ShouldBeNil) So(str, ShouldEqual, "") + Convey("random cve", func() { args := []string{"cvetest", "--cve-id", "random", "--image", "zot-cve-test", "--fixed"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) @@ -430,7 +515,7 @@ func TestServerCVEResponse(t *testing.T) { So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIZE") }) - Convey("invalid image", func() { + Convey("random image", func() { args := []string{"cvetest", "--cve-id", "CVE-2019-20807", "--image", "zot-cv-test", "--fixed"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) defer os.Remove(configPath) @@ -446,6 +531,23 @@ func TestServerCVEResponse(t *testing.T) { So(err, ShouldNotBeNil) So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIZE") }) + + Convey("invalid image", func() { + args := []string{"cvetest", "--cve-id", "CVE-2019-20807", "--image", "zot-cv-test:tag", "--fixed"} + 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, "IMAGE NAME TAG DIGEST SIZE") + }) }) Convey("Test CVE by name and CVE ID", t, func() { @@ -462,7 +564,8 @@ func TestServerCVEResponse(t *testing.T) { str := space.ReplaceAllString(buff.String(), " ") So(err, ShouldBeNil) So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE zot-cve-test 0.0.1 63a795ca 75MB") - Convey("invalidname and CVE ID", func() { + + Convey("invalid name and CVE ID", func() { args := []string{"cvetest", "--image", "test", "--cve-id", "CVE-20807"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) defer os.Remove(configPath) @@ -477,5 +580,451 @@ func TestServerCVEResponse(t *testing.T) { So(err, ShouldBeNil) So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIZE") }) + + Convey("invalid output format", func() { + args := []string{"cvetest", "--image", "zot-cve-test", "--cve-id", "CVE-2019-9923", "-o", "random"} + 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 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() + + err := test.CopyFiles("../../test/data/zot-cve-test", path.Join(dir, "zot-cve-test")) + if err != nil { + panic(err) + } + + conf.Storage.RootDirectory = dir + cveConfig := &extconf.CVEConfig{ + UpdateInterval: 2, + } + defaultVal := false + searchConfig := &extconf.SearchConfig{ + CVE: cveConfig, + Enable: &defaultVal, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + ctlr := api.NewController(conf) + + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(context.Background()); err != nil { + return + } + }(ctlr) + // wait till ready + for { + res, err := resty.R().Get(url) + if err == nil && res.StatusCode() == 404 { + break + } + + time.Sleep(100 * time.Millisecond) + } + time.Sleep(90 * time.Second) + + defer func(controller *api.Controller) { + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(ctlr) + + Convey("Status Code Not Found", func() { + args := []string{"cvetest", "--image", "zot-cve-test:0.0.1"} + 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(str, ShouldContainSubstring, "404 page not found") + }) + }) + + 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() + + err := test.CopyFiles("../../test/data/zot-cve-test", path.Join(dir, "zot-cve-test")) + if err != nil { + panic(err) + } + + err = os.RemoveAll(path.Join(dir, "zot-cve-test/blobs")) + if err != nil { + panic(err) + } + + conf.Storage.RootDirectory = dir + cveConfig := &extconf.CVEConfig{ + UpdateInterval: 2, + } + defaultVal := true + searchConfig := &extconf.SearchConfig{ + CVE: cveConfig, + Enable: &defaultVal, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + ctlr := api.NewController(conf) + + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(context.Background()); err != nil { + return + } + }(ctlr) + // wait till ready + for { + res, err := resty.R().Get(url) + if err == nil && res.StatusCode() == 404 { + break + } + + time.Sleep(100 * time.Millisecond) + } + time.Sleep(90 * time.Second) + + defer func(controller *api.Controller) { + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(ctlr) + + args := []string{"cvetest", "--cve-id", "CVE-2019-9923", "--image", "zot-cve-test", "--fixed"} + 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() + + err := test.CopyFiles("../../test/data/zot-cve-test", path.Join(dir, "zot-cve-test")) + if err != nil { + panic(err) + } + + conf.Storage.RootDirectory = dir + cveConfig := &extconf.CVEConfig{ + UpdateInterval: 2, + } + defaultVal := true + searchConfig := &extconf.SearchConfig{ + CVE: cveConfig, + Enable: &defaultVal, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + ctlr := api.NewController(conf) + + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(context.Background()); err != nil { + return + } + }(ctlr) + // wait till ready + for { + res, err := resty.R().Get(url + constants.ExtSearchPrefix) + if err == nil && res.StatusCode() == 422 { + break + } + + time.Sleep(100 * time.Millisecond) + } + time.Sleep(90 * time.Second) + + defer func(controller *api.Controller) { + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(ctlr) + + Convey("Test CVE by image name", t, func() { + args := []string{"cvetest", "--image", "zot-cve-test:0.0.1"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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("invalid image", func() { + args := []string{"cvetest", "--image", "invalid:0.0.1"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(new(searchService)) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + }) + }) + + Convey("Test images by CVE ID", t, func() { + args := []string{"cvetest", "--cve-id", "CVE-2019-9923"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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, "IMAGE NAME TAG DIGEST SIZE zot-cve-test 0.0.1 63a795ca 75MB") + Convey("invalid CVE ID", func() { + args := []string{"cvetest", "--cve-id", "invalid"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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, "IMAGE NAME TAG DIGEST SIZE") + }) + }) + + Convey("Test fixed tags by and image name CVE ID", t, func() { + args := []string{"cvetest", "--cve-id", "CVE-2019-9923", "--image", "zot-cve-test", "--fixed"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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("random cve", func() { + args := []string{"cvetest", "--cve-id", "random", "--image", "zot-cve-test", "--fixed"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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, "IMAGE NAME TAG DIGEST SIZE") + }) + + Convey("invalid image", func() { + args := []string{"cvetest", "--cve-id", "CVE-2019-20807", "--image", "zot-cv-test", "--fixed"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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, "IMAGE NAME TAG DIGEST SIZE") + }) + }) + + Convey("Test CVE by name and CVE ID", t, func() { + args := []string{"cvetest", "--image", "zot-cve-test", "--cve-id", "CVE-2019-9923"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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, "IMAGE NAME TAG DIGEST SIZE zot-cve-test 0.0.1 63a795ca 75MB") + Convey("invalid name and CVE ID", func() { + args := []string{"cvetest", "--image", "test", "--cve-id", "CVE-20807"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := MockNewCveCommand(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, "IMAGE NAME TAG DIGEST SIZE") + }) + }) +} + +func MockNewCveCommand(searchService SearchService) *cobra.Command { + searchCveParams := make(map[string]*string) + + var servURL, user, outputFormat string + + var verifyTLS, fixedFlag, verbose bool + + cveCmd := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + home, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + configPath := path.Join(home + "/.zot") + if len(args) > 0 { + urlFromConfig, err := getConfigValue(configPath, args[0], "url") + if err != nil { + cmd.SilenceUsage = true + + return err + } + + if urlFromConfig == "" { + return zotErrors.ErrNoURLProvided + } + + servURL = urlFromConfig + } else { + return zotErrors.ErrNoURLProvided + } + + if len(args) > 0 { + var err error + + verifyTLS, err = parseBooleanConfig(configPath, args[0], verifyTLSConfig) + if err != nil { + cmd.SilenceUsage = true + + return err + } + } + + verbose = false + + searchConfig := searchConfig{ + params: searchCveParams, + searchService: searchService, + servURL: &servURL, + user: &user, + outputFormat: &outputFormat, + fixedFlag: &fixedFlag, + verifyTLS: &verifyTLS, + verbose: &verbose, + resultWriter: cmd.OutOrStdout(), + } + + err = MockSearchCve(searchConfig) + + if err != nil { + cmd.SilenceUsage = true + + return err + } + + return nil + }, + } + + vars := cveFlagVariables{ + searchCveParams: searchCveParams, + servURL: &servURL, + user: &user, + outputFormat: &outputFormat, + fixedFlag: &fixedFlag, + } + + setupCveFlags(cveCmd, vars) + + return cveCmd +} + +func MockSearchCve(searchConfig searchConfig) error { + searchers := getCveSearchers() + + for _, searcher := range searchers { + found, err := searcher.search(searchConfig) + if found { + if err != nil { + return err + } + + return nil + } + } + + return zotErrors.ErrInvalidFlagsCombination +} diff --git a/pkg/cli/image_cmd.go b/pkg/cli/image_cmd.go index f6ad676a..0fbd9372 100644 --- a/pkg/cli/image_cmd.go +++ b/pkg/cli/image_cmd.go @@ -129,7 +129,15 @@ func setupImageFlags(imageCmd *cobra.Command, searchImageParams map[string]*stri } func searchImage(searchConfig searchConfig) error { - for _, searcher := range getImageSearchers() { + var searchers []searcher + + if checkExtEndPoint(*searchConfig.servURL) { + searchers = getImageSearchersGQL() + } else { + searchers = getImageSearchers() + } + + for _, searcher := range searchers { found, err := searcher.search(searchConfig) if found { if err != nil { diff --git a/pkg/cli/image_cmd_test.go b/pkg/cli/image_cmd_test.go index bbe79989..92cd8a5b 100644 --- a/pkg/cli/image_cmd_test.go +++ b/pkg/cli/image_cmd_test.go @@ -21,10 +21,12 @@ import ( godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" + "github.com/spf13/cobra" "gopkg.in/resty.v1" zotErrors "zotregistry.io/zot/errors" "zotregistry.io/zot/pkg/api" "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/api/constants" extconf "zotregistry.io/zot/pkg/extensions/config" "zotregistry.io/zot/pkg/test" ) @@ -56,6 +58,7 @@ func TestSearchImageCmd(t *testing.T) { So(err, ShouldBeNil) }) }) + Convey("Test image no url", t, func() { args := []string{"imagetest", "--name", "dummyIdRandom"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) @@ -126,6 +129,7 @@ func TestSearchImageCmd(t *testing.T) { So(err, ShouldEqual, zotErrors.ErrInvalidURL) So(buff.String(), ShouldContainSubstring, "invalid URL format") }) + Convey("Test image invalid url port", t, func() { args := []string{"imagetest", "--name", "dummyImageName", "--url", "http://localhost:99999"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) @@ -153,6 +157,7 @@ func TestSearchImageCmd(t *testing.T) { So(buff.String(), ShouldContainSubstring, "invalid port") }) }) + Convey("Test image unreachable", t, func() { args := []string{"imagetest", "--name", "dummyImageName", "--url", "http://localhost:9999"} configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) @@ -168,10 +173,8 @@ func TestSearchImageCmd(t *testing.T) { Convey("Test image url from config", t, func() { args := []string{"imagetest", "--name", "dummyImageName"} - configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -215,15 +218,44 @@ func TestSearchImageCmd(t *testing.T) { So(err, ShouldBeNil) }) }) + + Convey("Test image by digest", t, func() { + args := []string{"imagetest", "--digest", "DigestsA", "--url", "someUrlImage"} + configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) + defer os.Remove(configPath) + imageCmd := NewImageCommand(new(mockService)) + buff := bytes.NewBufferString("") + imageCmd.SetOut(buff) + imageCmd.SetErr(buff) + imageCmd.SetArgs(args) + err := imageCmd.Execute() + space := regexp.MustCompile(`\s+`) + str := space.ReplaceAllString(buff.String(), " ") + So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE anImage tag DigestsA 123kB") + So(err, ShouldBeNil) + + Convey("invalid URL format", func() { + args := []string{"imagetest", "--digest", "digest", "--url", "invalidURL"} + configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`) + defer os.Remove(configPath) + imageCmd := NewImageCommand(NewSearchService()) + buff := bytes.NewBufferString("") + imageCmd.SetOut(buff) + imageCmd.SetErr(buff) + imageCmd.SetArgs(args) + err := imageCmd.Execute() + So(err, ShouldNotBeNil) + So(err, ShouldEqual, zotErrors.ErrInvalidURL) + So(buff.String(), ShouldContainSubstring, "invalid URL format") + }) + }) } func TestListRepos(t *testing.T) { Convey("Test listing repositories", t, func() { args := []string{"config-test"} - configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -264,11 +296,9 @@ func TestListRepos(t *testing.T) { Convey("Test listing repositories error", t, func() { args := []string{"config-test"} - configPath := makeConfigFile(`{"configs":[{"_name":"config-test", - "url":"https://invalid.invalid","showspinner":false}]}`) + "url":"https://invalid.invalid","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewRepoCommand(new(searchService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -280,10 +310,8 @@ func TestListRepos(t *testing.T) { Convey("Test unable to get config value", t, func() { args := []string{"config-test-inexistent"} - configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -295,10 +323,8 @@ func TestListRepos(t *testing.T) { Convey("Test error - no url provided", t, func() { args := []string{"config-test"} - configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -310,10 +336,8 @@ func TestListRepos(t *testing.T) { Convey("Test error - no args provided", t, func() { var args []string - configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -325,11 +349,9 @@ func TestListRepos(t *testing.T) { Convey("Test error - spinner config invalid", t, func() { args := []string{"config-test"} - configPath := makeConfigFile(`{"configs":[{"_name":"config-test", - "url":"https://test-url.com","showspinner":invalid}]}`) + "url":"https://test-url.com","showspinner":invalid}]}`) defer os.Remove(configPath) - cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -341,11 +363,9 @@ func TestListRepos(t *testing.T) { Convey("Test error - verifyTLSConfig fails", t, func() { args := []string{"config-test"} - - configPath := makeConfigFile(`{"configs":[{"_name":"config-test", - "verify-tls":"invalid", "url":"https://test-url.com","showspinner":false}]}`) + configPath := makeConfigFile(`{"configs":[{"_name":"config-test", + "verify-tls":"invalid", "url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewRepoCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -359,10 +379,8 @@ func TestListRepos(t *testing.T) { func TestOutputFormat(t *testing.T) { Convey("Test text", t, func() { args := []string{"imagetest", "--name", "dummyImageName", "-o", "text"} - configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -375,12 +393,12 @@ func TestOutputFormat(t *testing.T) { So(err, ShouldBeNil) }) + // get image config functia + Convey("Test json", t, func() { args := []string{"imagetest", "--name", "dummyImageName", "-o", "json"} - configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -389,17 +407,15 @@ func TestOutputFormat(t *testing.T) { err := cmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") - So(strings.TrimSpace(str), ShouldEqual, `{ "name": "dummyImageName", "tags": [ { "name":`+ - ` "tag", "size": 123445, "digest": "DigestsAreReallyLong", "configDigest": "", "layerDigests": null } ] }`) + So(strings.TrimSpace(str), ShouldEqual, `{ "repoName": "dummyImageName", "tag": "tag", `+ + `"configDigest": "", "digest": "DigestsAreReallyLong", "layers": null, "size": "123445" }`) So(err, ShouldBeNil) }) Convey("Test yaml", t, func() { args := []string{"imagetest", "--name", "dummyImageName", "-o", "yaml"} - configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -408,16 +424,21 @@ func TestOutputFormat(t *testing.T) { err := cmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") - So(strings.TrimSpace(str), ShouldEqual, `name: dummyImageName tags: -`+ - ` name: tag size: 123445 digest: DigestsAreReallyLong configdigest: "" layers: []`) + So( + strings.TrimSpace(str), + ShouldEqual, + `reponame: dummyImageName tag: tag configdigest: "" `+ + `digest: DigestsAreReallyLong layers: [] size: "123445"`, + ) So(err, ShouldBeNil) Convey("Test yml", func() { args := []string{"imagetest", "--name", "dummyImageName", "-o", "yml"} - - configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) + configPath := makeConfigFile( + `{"configs":[{"_name":"imagetest",` + + `"url":"https://test-url.com","showspinner":false}]}`, + ) defer os.Remove(configPath) - cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -426,18 +447,20 @@ func TestOutputFormat(t *testing.T) { err := cmd.Execute() space := regexp.MustCompile(`\s+`) str := space.ReplaceAllString(buff.String(), " ") - So(strings.TrimSpace(str), ShouldEqual, `name: dummyImageName tags: -`+ - ` name: tag size: 123445 digest: DigestsAreReallyLong configdigest: "" layers: []`) + So( + strings.TrimSpace(str), + ShouldEqual, + `reponame: dummyImageName tag: tag configdigest: "" `+ + `digest: DigestsAreReallyLong layers: [] size: "123445"`, + ) So(err, ShouldBeNil) }) }) Convey("Test invalid", t, func() { args := []string{"imagetest", "--name", "dummyImageName", "-o", "random"} - configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`) defer os.Remove(configPath) - cmd := NewImageCommand(new(mockService)) buff := bytes.NewBufferString("") cmd.SetOut(buff) @@ -449,7 +472,7 @@ func TestOutputFormat(t *testing.T) { }) } -func TestServerResponse(t *testing.T) { +func TestServerResponseGQL(t *testing.T) { Convey("Test from real server", t, func() { port := test.GetFreePort() url := test.GetBaseURL(port) @@ -491,7 +514,6 @@ func TestServerResponse(t *testing.T) { configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) defer os.Remove(configPath) cmd := NewImageCommand(new(searchService)) - // buff := bytes.NewBufferString("") buff := &bytes.Buffer{} cmd.SetOut(buff) cmd.SetErr(buff) @@ -504,6 +526,19 @@ func TestServerResponse(t *testing.T) { So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIZE") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B") + Convey("Test all images invalid output format", func() { + args := []string{"imagetest", "-o", "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() { @@ -567,6 +602,20 @@ func TestServerResponse(t *testing.T) { So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B") }) + + Convey("invalid output format", func() { + args := []string{"imagetest", "--name", "repo7", "-o", "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() { @@ -590,6 +639,7 @@ func TestServerResponse(t *testing.T) { So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIZE") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B") + Convey("with shorthand", func() { args := []string{"imagetest", "-d", "883fc0c5"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) @@ -608,9 +658,37 @@ func TestServerResponse(t *testing.T) { So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B") }) + + Convey("nonexistent digest", func() { + args := []string{"imagetest", "--digest", "d1g35t"} + 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{"imagetest", "--digest", "883fc0c5", "-o", "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 invalid name", func() { + Convey("Test image by name nonexistent name", func() { args := []string{"imagetest", "--name", "repo777"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) defer os.Remove(configPath) @@ -620,16 +698,15 @@ func TestServerResponse(t *testing.T) { cmd.SetErr(buff) cmd.SetArgs(args) err := cmd.Execute() - So(err, ShouldNotBeNil) - actual := buff.String() - So(actual, ShouldContainSubstring, "unknown") + So(err, ShouldBeNil) + So(len(buff.String()), ShouldEqual, 0) }) Convey("Test list repos error", func() { args := []string{"config-test"} configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"config-test", - "url":"%s","showspinner":false}]}`, url)) + "url":"%s","showspinner":false}]}`, url)) defer os.Remove(configPath) cmd := NewRepoCommand(new(searchService)) @@ -648,6 +725,361 @@ func TestServerResponse(t *testing.T) { }) } +func TestServerResponse(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{Enable: &defaultVal}, + } + ctlr := api.NewController(conf) + ctlr.Config.Storage.RootDirectory = t.TempDir() + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(context.Background()); err != nil { + return + } + }(ctlr) + // wait till ready + for { + _, err := resty.R().Get(url) + if err == nil { + break + } + + time.Sleep(100 * time.Millisecond) + } + defer func(controller *api.Controller) { + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(ctlr) + + err := uploadManifest(url) + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + So(err, ShouldBeNil) + + Convey("Test all images", func() { + t.Logf("%s", ctlr.Config.Storage.RootDirectory) + args := []string{"imagetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := MockNewImageCommand(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, "IMAGE NAME TAG DIGEST SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B") + }) + + Convey("Test all images verbose", func() { + args := []string{"imagetest", "--verbose"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := MockNewImageCommand(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): + // IMAGE NAME TAG DIGEST CONFIG LAYERS SIZE + // repo7 test:2.0 a0ca253b b8781e88 15B + // b8781e88 15B + // repo7 test:1.0 a0ca253b b8781e88 15B + // b8781e88 15B + So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST CONFIG LAYERS SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 3a1d2d0c 15B b8781e88 15B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 3a1d2d0c 15B b8781e88 15B") + }) + + Convey("Test image by name", func() { + args := []string{"imagetest", "--name", "repo7"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := MockNewImageCommand(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, "IMAGE NAME TAG DIGEST SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B") + }) + + Convey("Test image by digest", func() { + args := []string{"imagetest", "--digest", "883fc0c5"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := MockNewImageCommand(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): + // IMAGE NAME TAG DIGEST SIZE + // repo7 test:2.0 a0ca253b 15B + // repo7 test:1.0 a0ca253b 15B + So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIZE") + So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B") + So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B") + + Convey("nonexistent digest", func() { + args := []string{"imagetest", "--digest", "d1g35t"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := MockNewImageCommand(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 image by name nonexistent name", func() { + args := []string{"imagetest", "--name", "repo777"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cmd := MockNewImageCommand(new(searchService)) + buff := bytes.NewBufferString("") + cmd.SetOut(buff) + cmd.SetErr(buff) + cmd.SetArgs(args) + err := cmd.Execute() + So(err, ShouldNotBeNil) + actual := buff.String() + So(actual, ShouldContainSubstring, "unknown") + }) + }) +} + +func TestServerResponseGQLWithoutPermissions(t *testing.T) { + port := test.GetFreePort() + url := test.GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + + dir := t.TempDir() + + err := test.CopyFiles("../../test/data/zot-test", path.Join(dir, "zot-test")) + if err != nil { + panic(err) + } + + err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o000) + if err != nil { + panic(err) + } + + conf.Storage.RootDirectory = dir + cveConfig := &extconf.CVEConfig{ + UpdateInterval: 2, + } + defaultVal := true + searchConfig := &extconf.SearchConfig{ + CVE: cveConfig, + Enable: &defaultVal, + } + conf.Extensions = &extconf.ExtensionConfig{ + Search: searchConfig, + } + + ctlr := api.NewController(conf) + + go func(controller *api.Controller) { + // this blocks + if err := controller.Run(context.Background()); err != nil { + return + } + }(ctlr) + // wait till ready + for { + res, err := resty.R().Get(url + constants.ExtSearchPrefix) + if err == nil && res.StatusCode() == 422 { + break + } + + time.Sleep(100 * time.Millisecond) + } + time.Sleep(90 * time.Second) + + defer func(controller *api.Controller) { + err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o777) + if err != nil { + panic(err) + } + ctx := context.Background() + _ = controller.Server.Shutdown(ctx) + }(ctlr) + + Convey("Test all images", t, func() { + args := []string{"imagetest"} + configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) + defer os.Remove(configPath) + cveCmd := NewImageCommand(new(searchService)) + buff := bytes.NewBufferString("") + cveCmd.SetOut(buff) + cveCmd.SetErr(buff) + cveCmd.SetArgs(args) + err = cveCmd.Execute() + So(err, ShouldNotBeNil) + }) + + Convey("Test all images verbose", t, func() { + args := []string{"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, ShouldNotBeNil) + }) + + Convey("Test image by name", t, func() { + args := []string{"imagetest", "--name", "zot-test"} + 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) + }) + + Convey("Test image by digest", t, func() { + args := []string{"imagetest", "--digest", "2bacca16"} + 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) + }) +} + +func MockNewImageCommand(searchService SearchService) *cobra.Command { + searchImageParams := make(map[string]*string) + + var servURL, user, outputFormat string + + var verifyTLS, verbose bool + + imageCmd := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + home, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + configPath := path.Join(home + "/.zot") + if len(args) > 0 { + urlFromConfig, err := getConfigValue(configPath, args[0], "url") + if err != nil { + cmd.SilenceUsage = true + + return err + } + + if urlFromConfig == "" { + return zotErrors.ErrNoURLProvided + } + + servURL = urlFromConfig + } else { + return zotErrors.ErrNoURLProvided + } + + if len(args) > 0 { + var err error + + verifyTLS, err = parseBooleanConfig(configPath, args[0], verifyTLSConfig) + if err != nil { + cmd.SilenceUsage = true + + return err + } + } + + searchConfig := searchConfig{ + params: searchImageParams, + searchService: searchService, + servURL: &servURL, + user: &user, + outputFormat: &outputFormat, + verbose: &verbose, + verifyTLS: &verifyTLS, + resultWriter: cmd.OutOrStdout(), + } + + err = MockSearchImage(searchConfig) + + if err != nil { + cmd.SilenceUsage = true + + return err + } + + return nil + }, + } + + setupImageFlags(imageCmd, searchImageParams, &servURL, &user, &outputFormat, &verbose) + imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter) + + return imageCmd +} + +func MockSearchImage(searchConfig searchConfig) error { + searchers := getImageSearchers() + + for _, searcher := range searchers { + found, err := searcher.search(searchConfig) + if found { + if err != nil { + return err + } + + return nil + } + } + + return zotErrors.ErrInvalidFlagsCombination +} + func uploadManifest(url string) error { // create a blob/layer resp, _ := resty.R().Post(url + "/v2/repo7/blobs/uploads/") @@ -741,6 +1173,131 @@ func (service mockService) getRepos(ctx context.Context, config searchConfig, us channel <- stringResult{"", nil} } +func (service mockService) getImagesGQL(ctx context.Context, config searchConfig, username, password string, + imageName string, +) (*imageListStructGQL, error) { + imageListGQLResponse := &imageListStructGQL{} + imageListGQLResponse.Data.ImageList = []imageStruct{ + { + RepoName: "dummyImageName", + Tag: "tag", + Digest: "DigestsAreReallyLong", + Size: "123445", + }, + } + + return imageListGQLResponse, nil +} + +func (service mockService) getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string, + digest string, +) (*imageListStructForDigestGQL, error) { + imageListGQLResponse := &imageListStructForDigestGQL{} + imageListGQLResponse.Data.ImageList = []imageStruct{ + { + RepoName: "randomimageName", + Tag: "tag", + Digest: "DigestsAreReallyLong", + Size: "123445", + }, + } + + return imageListGQLResponse, nil +} + +func (service mockService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string, + digest string, +) (*imagesForCve, error) { + imagesForCve := &imagesForCve{ + Errors: nil, + Data: struct { + ImageList []imageStruct `json:"ImageListForCVE"` // nolint:tagliatelle + }{}, + } + + imagesForCve.Errors = nil + + mockedImage := service.getMockedImageByName("anImage") + imagesForCve.Data.ImageList = []imageStruct{mockedImage} + + return imagesForCve, nil +} + +func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, + imageName, cveID string, +) (*imagesForCve, error) { + images := &imagesForCve{ + Errors: nil, + Data: struct { + ImageList []imageStruct `json:"ImageListForCVE"` //nolint:tagliatelle // graphQL schema + }{}, + } + + images.Errors = nil + + mockedImage := service.getMockedImageByName(imageName) + images.Data.ImageList = []imageStruct{mockedImage} + + return images, nil +} + +func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, + imageName, cveID string, +) (*fixedTags, error) { + fixedTags := &fixedTags{ + Errors: nil, + Data: struct { + ImageList []imageStruct `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema + }{}, + } + + fixedTags.Errors = nil + + mockedImage := service.getMockedImageByName(imageName) + fixedTags.Data.ImageList = []imageStruct{mockedImage} + + return fixedTags, nil +} + +func (service mockService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password, + imageName string, +) (*cveResult, error) { + cveRes := &cveResult{} + cveRes.Data = cveData{ + CVEListForImage: cveListForImage{ + Tag: imageName, + CVEList: []cve{ + { + ID: "dummyCVEID", + Description: "Description of the CVE", + Title: "Title of that CVE", + Severity: "HIGH", + PackageList: []packageList{ + { + Name: "packagename", + FixedVersion: "fixedver", + InstalledVersion: "installedver", + }, + }, + }, + }, + }, + } + + return cveRes, nil +} + +// nolint: goconst +func (service mockService) getMockedImageByName(imageName string) imageStruct { + image := imageStruct{} + image.RepoName = imageName + image.Tag = "tag" + image.Digest = "DigestsAreReallyLong" + image.Size = "123445" + + return image +} + func (service mockService) getAllImages(ctx context.Context, config searchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup, ) { @@ -748,14 +1305,10 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig defer close(channel) image := &imageStruct{} - image.Name = "randomimageName" - image.Tags = []tags{ - { - Name: "tag", - Digest: "DigestsAreReallyLong", - Size: 123445, - }, - } + image.RepoName = "randomimageName" + image.Tag = "tag" + image.Digest = "DigestsAreReallyLong" + image.Size = "123445" str, err := image.string(*config.outputFormat) if err != nil { @@ -774,14 +1327,10 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf defer close(channel) image := &imageStruct{} - image.Name = imageName - image.Tags = []tags{ - { - Name: "tag", - Digest: "DigestsAreReallyLong", - Size: 123445, - }, - } + image.RepoName = imageName + image.Tag = "tag" + image.Digest = "DigestsAreReallyLong" + image.Size = "123445" str, err := image.string(*config.outputFormat) if err != nil { @@ -831,6 +1380,18 @@ func (service mockService) getCveByImage(ctx context.Context, config searchConfi rch <- stringResult{str, nil} } +func (service mockService) getFixedTagsForCVE(ctx context.Context, config searchConfig, + username, password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup, +) { + service.getImageByName(ctx, config, username, password, imageName, rch, wtgrp) +} + +func (service mockService) getImageByNameAndCVEID(ctx context.Context, config searchConfig, username, + password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup, +) { + service.getImageByName(ctx, config, username, password, imageName, rch, wtgrp) +} + func (service mockService) getImagesByCveID(ctx context.Context, config searchConfig, username, password, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { @@ -843,18 +1404,6 @@ func (service mockService) getImagesByDigest(ctx context.Context, config searchC service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp) } -func (service mockService) getImageByNameAndCVEID(ctx context.Context, config searchConfig, username, - password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup, -) { - service.getImageByName(ctx, config, username, password, imageName, rch, wtgrp) -} - -func (service mockService) getFixedTagsForCVE(ctx context.Context, config searchConfig, - username, password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup, -) { - service.getImageByName(ctx, config, username, password, imageName, rch, wtgrp) -} - func makeConfigFile(content string) string { os.Setenv("HOME", os.TempDir()) diff --git a/pkg/cli/searcher.go b/pkg/cli/searcher.go index e7c0205a..cf08d086 100644 --- a/pkg/cli/searcher.go +++ b/pkg/cli/searcher.go @@ -37,6 +37,27 @@ func getCveSearchers() []searcher { return searchers } +func getImageSearchersGQL() []searcher { + searchers := []searcher{ + new(allImagesSearcherGQL), + new(imageByNameSearcherGQL), + new(imagesByDigestSearcherGQL), + } + + return searchers +} + +func getCveSearchersGQL() []searcher { + searchers := []searcher{ + new(cveByImageSearcherGQL), + new(imagesByCVEIDSearcherGQL), + new(tagsByImageNameAndCVEIDSearcherGQL), + new(fixedTagsSearcherGQL), + } + + return searchers +} + type searcher interface { search(searchConfig searchConfig) (bool, error) } @@ -96,6 +117,18 @@ func (search allImagesSearcher) search(config searchConfig) (bool, error) { } } +type allImagesSearcherGQL struct{} + +func (search allImagesSearcherGQL) search(config searchConfig) (bool, error) { + if !canSearch(config.params, newSet("")) { + return false, nil + } + + err := getImages(config) + + return true, err +} + type imageByNameSearcher struct{} func (search imageByNameSearcher) search(config searchConfig) (bool, error) { @@ -128,6 +161,32 @@ func (search imageByNameSearcher) search(config searchConfig) (bool, error) { } } +type imageByNameSearcherGQL struct{} + +func (search imageByNameSearcherGQL) search(config searchConfig) (bool, error) { + if !canSearch(config.params, newSet("imageName")) { + return false, nil + } + + err := getImages(config) + + return true, err +} + +func getImages(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, *config.params["imageName"]) + if err != nil { + return err + } + + return printResult(config, imageList.Data.ImageList) +} + type imagesByDigestSearcher struct{} func (search imagesByDigestSearcher) search(config searchConfig) (bool, error) { @@ -160,6 +219,32 @@ func (search imagesByDigestSearcher) search(config searchConfig) (bool, error) { } } +type imagesByDigestSearcherGQL struct{} + +func (search imagesByDigestSearcherGQL) search(config searchConfig) (bool, error) { + if !canSearch(config.params, newSet("digest")) { + return false, nil + } + + // var builder strings.Builder + + username, password := getUsernameAndPassword(*config.user) + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + imageList, err := config.searchService.getImagesByDigestGQL(ctx, config, username, password, *config.params["digest"]) + if err != nil { + return true, err + } + + if err := printResult(config, imageList.Data.ImageList); err != nil { + return true, err + } + + return true, nil +} + type cveByImageSearcher struct{} func (search cveByImageSearcher) search(config searchConfig) (bool, error) { @@ -195,10 +280,49 @@ func (search cveByImageSearcher) search(config searchConfig) (bool, error) { } } +type cveByImageSearcherGQL struct{} + +func (search cveByImageSearcherGQL) search(config searchConfig) (bool, error) { + if !canSearch(config.params, newSet("imageName")) || *config.fixedFlag { + return false, nil + } + + if !validateImageNameTag(*config.params["imageName"]) { + return true, errInvalidImageNameAndTag + } + + var builder strings.Builder + + username, password := getUsernameAndPassword(*config.user) + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + cveList, err := config.searchService.getCveByImageGQL(ctx, config, username, password, *config.params["imageName"]) + if err != nil { + return true, err + } + + if len(cveList.Data.CVEListForImage.CVEList) > 0 && + (*config.outputFormat == defaultOutoutFormat || *config.outputFormat == "") { + printCVETableHeader(&builder, *config.verbose) + fmt.Fprint(config.resultWriter, builder.String()) + } + + out, err := cveList.string(*config.outputFormat) + if err != nil { + return true, err + } + + fmt.Fprint(config.resultWriter, out) + + return true, nil +} + type imagesByCVEIDSearcher struct{} func (search imagesByCVEIDSearcher) search(config searchConfig) (bool, error) { - if !canSearch(config.params, newSet("cvid")) || *config.fixedFlag { + if !canSearch(config.params, newSet("cveID")) || *config.fixedFlag { return false, nil } @@ -210,7 +334,7 @@ func (search imagesByCVEIDSearcher) search(config searchConfig) (bool, error) { wg.Add(1) - go config.searchService.getImagesByCveID(ctx, config, username, password, *config.params["cvid"], strErr, &wg) + go config.searchService.getImagesByCveID(ctx, config, username, password, *config.params["cveID"], strErr, &wg) wg.Add(1) errCh := make(chan error, 1) @@ -226,10 +350,34 @@ func (search imagesByCVEIDSearcher) search(config searchConfig) (bool, error) { } } +type imagesByCVEIDSearcherGQL struct{} + +func (search imagesByCVEIDSearcherGQL) search(config searchConfig) (bool, error) { + if !canSearch(config.params, newSet("cveID")) || *config.fixedFlag { + return false, nil + } + + username, password := getUsernameAndPassword(*config.user) + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + imageList, err := config.searchService.getImagesByCveIDGQL(ctx, config, username, password, *config.params["cveID"]) + if err != nil { + return true, err + } + + if err := printResult(config, imageList.Data.ImageList); err != nil { + return true, err + } + + return true, nil +} + type tagsByImageNameAndCVEIDSearcher struct{} func (search tagsByImageNameAndCVEIDSearcher) search(config searchConfig) (bool, error) { - if !canSearch(config.params, newSet("cvid", "imageName")) || *config.fixedFlag { + if !canSearch(config.params, newSet("cveID", "imageName")) || *config.fixedFlag { return false, nil } @@ -246,7 +394,7 @@ func (search tagsByImageNameAndCVEIDSearcher) search(config searchConfig) (bool, wg.Add(1) go config.searchService.getImageByNameAndCVEID(ctx, config, username, password, *config.params["imageName"], - *config.params["cvid"], strErr, &wg) + *config.params["cveID"], strErr, &wg) wg.Add(1) errCh := make(chan error, 1) @@ -262,10 +410,38 @@ func (search tagsByImageNameAndCVEIDSearcher) search(config searchConfig) (bool, } } +type tagsByImageNameAndCVEIDSearcherGQL struct{} + +func (search tagsByImageNameAndCVEIDSearcherGQL) search(config searchConfig) (bool, error) { + if !canSearch(config.params, newSet("cveID", "imageName")) || *config.fixedFlag { + return false, nil + } + + if strings.Contains(*config.params["imageName"], ":") { + return true, errInvalidImageName + } + + err := getTagsByCVE(config) + + return true, err +} + +type fixedTagsSearcherGQL struct{} + +func (search fixedTagsSearcherGQL) search(config searchConfig) (bool, error) { + if !canSearch(config.params, newSet("cveID", "imageName")) || !*config.fixedFlag { + return false, nil + } + + err := getTagsByCVE(config) + + return true, err +} + type fixedTagsSearcher struct{} func (search fixedTagsSearcher) search(config searchConfig) (bool, error) { - if !canSearch(config.params, newSet("cvid", "imageName")) || !*config.fixedFlag { + if !canSearch(config.params, newSet("cveID", "imageName")) || !*config.fixedFlag { return false, nil } @@ -282,7 +458,7 @@ func (search fixedTagsSearcher) search(config searchConfig) (bool, error) { wg.Add(1) go config.searchService.getFixedTagsForCVE(ctx, config, username, password, *config.params["imageName"], - *config.params["cvid"], strErr, &wg) + *config.params["cveID"], strErr, &wg) wg.Add(1) errCh := make(chan error, 1) @@ -298,6 +474,39 @@ func (search fixedTagsSearcher) search(config searchConfig) (bool, error) { } } +func getTagsByCVE(config searchConfig) error { + if strings.Contains(*config.params["imageName"], ":") { + return errInvalidImageName + } + + username, password := getUsernameAndPassword(*config.user) + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + var imageList []imageStruct + + if *config.fixedFlag { + fixedTags, err := config.searchService.getFixedTagsForCVEGQL(ctx, config, username, password, + *config.params["imageName"], *config.params["cveID"]) + if err != nil { + return err + } + + imageList = fixedTags.Data.ImageList + } else { + tags, err := config.searchService.getTagsForCVEGQL(ctx, config, username, password, + *config.params["imageName"], *config.params["cveID"]) + if err != nil { + return err + } + + imageList = tags.Data.ImageList + } + + return printResult(config, imageList) +} + func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan stringResult, cancel context.CancelFunc, printHeader printHeader, errCh chan error, ) { @@ -376,12 +585,14 @@ type spinnerState struct { enabled bool } +// nolint func (spinner *spinnerState) startSpinner() { if spinner.enabled { spinner.spinner.Start() } } +// nolint func (spinner *spinnerState) stopSpinner() { if spinner.enabled && spinner.spinner.Active() { spinner.spinner.Stop() @@ -397,14 +608,14 @@ func getEmptyStruct() struct{} { } func newSet(initialValues ...string) *set { - ret := &set{} - ret.m = make(map[string]struct{}) + setValues := &set{} + setValues.m = make(map[string]struct{}) for _, val := range initialValues { - ret.m[val] = getEmptyStruct() + setValues.m[val] = getEmptyStruct() } - return ret + return setValues } func (s *set) contains(value string) bool { @@ -413,6 +624,10 @@ func (s *set) contains(value string) bool { return c } +const ( + waitTimeout = httpTimeout + 5*time.Second +) + var ( ErrCannotSearch = errors.New("cannot search with these parameters") ErrInvalidOutputFormat = errors.New("invalid output format") @@ -438,7 +653,7 @@ func printImageTableHeader(writer io.Writer, verbose bool) { table.SetColMinWidth(colLayersIndex, layersWidth) } - row := make([]string, 6) //nolint:gomnd + row := make([]string, 6) // nolint:gomnd row[colImageNameIndex] = "IMAGE NAME" row[colTagIndex] = "TAG" @@ -465,9 +680,28 @@ func printCVETableHeader(writer io.Writer, verbose bool) { table.Render() } -const ( - waitTimeout = httpTimeout + 5*time.Second -) +func printResult(config searchConfig, imageList []imageStruct) error { + var builder strings.Builder + + if len(imageList) > 0 { + printImageTableHeader(&builder, *config.verbose) + fmt.Fprint(config.resultWriter, builder.String()) + } + + for i := range imageList { + img := imageList[i] + img.verbose = *config.verbose + + out, err := img.string(*config.outputFormat) + if err != nil { + return err + } + + fmt.Fprint(config.resultWriter, out) + } + + return nil +} var ( errInvalidImageNameAndTag = errors.New("cli: Invalid input format. Expected IMAGENAME:TAG") diff --git a/pkg/cli/service.go b/pkg/cli/service.go index 1514ad01..0f97cca2 100644 --- a/pkg/cli/service.go +++ b/pkg/cli/service.go @@ -9,9 +9,9 @@ import ( "fmt" "io" "net/url" + "strconv" "strings" "sync" - "time" "github.com/dustin/go-humanize" jsoniter "github.com/json-iterator/go" @@ -22,22 +22,35 @@ import ( ) type SearchService interface { + getImagesGQL(ctx context.Context, config searchConfig, username, password string, + imageName string) (*imageListStructGQL, error) + getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string, + digest string) (*imageListStructForDigestGQL, error) + getCveByImageGQL(ctx context.Context, config searchConfig, username, password, + imageName string) (*cveResult, error) + getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string, + digest string) (*imagesForCve, error) + getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName, + cveID string) (*imagesForCve, error) + getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName, + cveID string) (*fixedTags, error) + getAllImages(ctx context.Context, config searchConfig, username, password string, channel chan stringResult, wtgrp *sync.WaitGroup) - getImageByName(ctx context.Context, config searchConfig, username, password, imageName string, - channel chan stringResult, wtgrp *sync.WaitGroup) getCveByImage(ctx context.Context, config searchConfig, username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup) getImagesByCveID(ctx context.Context, config searchConfig, username, password, cvid string, channel chan stringResult, wtgrp *sync.WaitGroup) getImagesByDigest(ctx context.Context, config searchConfig, username, password, digest string, channel chan stringResult, wtgrp *sync.WaitGroup) - getImageByNameAndCVEID(ctx context.Context, config searchConfig, username, password, imageName, cvid string, - channel chan stringResult, wtgrp *sync.WaitGroup) getFixedTagsForCVE(ctx context.Context, config searchConfig, username, password, imageName, cvid string, channel chan stringResult, wtgrp *sync.WaitGroup) 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, + channel chan stringResult, wtgrp *sync.WaitGroup) + getImageByNameAndCVEID(ctx context.Context, config searchConfig, username, password, imageName, cvid string, + channel chan stringResult, wtgrp *sync.WaitGroup) } type searchService struct{} @@ -46,6 +59,116 @@ func NewSearchService() SearchService { return searchService{} } +func (service searchService) getImagesGQL(ctx context.Context, config searchConfig, username, password string, + imageName string, +) (*imageListStructGQL, error) { + query := fmt.Sprintf(`{ImageList(repo: "%s") {`+` + RepoName Tag Digest ConfigDigest Size Layers {Size Digest}} + }`, + imageName) + result := &imageListStructGQL{} + + err := service.makeGraphQLQuery(ctx, config, username, password, query, result) + + if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil { + return nil, errResult + } + + return result, nil +} + +func (service searchService) getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string, + digest string, +) (*imageListStructForDigestGQL, error) { + query := fmt.Sprintf(`{ImageListForDigest(id: "%s") {`+` + RepoName Tag Digest ConfigDigest Size Layers {Size Digest}} + }`, + digest) + result := &imageListStructForDigestGQL{} + + err := service.makeGraphQLQuery(ctx, config, username, password, query, result) + + if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil { + return nil, errResult + } + + return result, nil +} + +func (service searchService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, + password, cveID string, +) (*imagesForCve, error) { + query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+` + RepoName Tag Digest Size} + }`, + cveID) + result := &imagesForCve{} + + err := service.makeGraphQLQuery(ctx, config, username, password, query, result) + + if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil { + return nil, errResult + } + + return result, nil +} + +func (service searchService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password, + imageName string, +) (*cveResult, error) { + query := fmt.Sprintf(`{ CVEListForImage (image:"%s")`+ + ` { Tag CVEList { Id Title Severity Description `+ + `PackageList {Name InstalledVersion FixedVersion}} } }`, imageName) + result := &cveResult{} + + err := service.makeGraphQLQuery(ctx, config, username, password, query, result) + + if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil { + return nil, errResult + } + + result.Data.CVEListForImage.CVEList = groupCVEsBySeverity(result.Data.CVEListForImage.CVEList) + + return result, nil +} + +func (service searchService) getTagsForCVEGQL(ctx context.Context, config searchConfig, + username, password, imageName, cveID string, +) (*imagesForCve, error) { + query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+` + RepoName Tag Digest Size} + }`, + cveID) + result := &imagesForCve{} + + err := service.makeGraphQLQuery(ctx, config, username, password, query, result) + + if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil { + return nil, errResult + } + + return result, nil +} + +func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, + username, password, imageName, cveID string, +) (*fixedTags, error) { + query := fmt.Sprintf(`{ImageListWithCVEFixed(id: "%s", image: "%s") {`+` + RepoName Tag Digest Size} + }`, + cveID, imageName) + + result := &fixedTags{} + + err := service.makeGraphQLQuery(ctx, config, username, password, query, result) + + if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil { + return nil, errResult + } + + return result, nil +} + func (service searchService) getImageByName(ctx context.Context, config searchConfig, username, password, imageName string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { @@ -126,8 +249,8 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag return } - tagsList := &tagListResp{} - _, err = makeGETRequest(ctx, tagListEndpoint, username, password, *config.verifyTLS, &tagsList) + tagList := &tagListResp{} + _, err = makeGETRequest(ctx, tagListEndpoint, username, password, *config.verifyTLS, &tagList) if err != nil { if isContextDone(ctx) { @@ -138,7 +261,7 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag return } - for _, tag := range tagsList.Tags { + for _, tag := range tagList.Tags { wtgrp.Add(1) go addManifestCallToPool(ctx, config, pool, username, password, imageName, tag, rch, wtgrp) @@ -152,7 +275,7 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search defer close(rch) query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+` - Name Tags } + RepoName Tag Digest Size} }`, cvid) result := &imagesForCve{} @@ -189,12 +312,10 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search go rlim.startRateLimiter(ctx) - for _, image := range result.Data.ImageListForCVE { - for _, tag := range image.Tags { - localWg.Add(1) + for _, image := range result.Data.ImageList { + localWg.Add(1) - go addManifestCallToPool(ctx, config, rlim, username, password, image.Name, tag, rch, &localWg) - } + go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg) } localWg.Wait() @@ -207,7 +328,7 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc defer close(rch) query := fmt.Sprintf(`{ImageListForDigest(id: "%s") {`+` - Name Tags } + RepoName Tag Digest ConfigDigest Size Layers {Size Digest}} }`, digest) result := &imagesForDigest{} @@ -244,12 +365,10 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc go rlim.startRateLimiter(ctx) - for _, image := range result.Data.ImageListForDigest { - for _, tag := range image.Tags { - localWg.Add(1) + for _, image := range result.Data.ImageList { + localWg.Add(1) - go addManifestCallToPool(ctx, config, rlim, username, password, image.Name, tag, rch, &localWg) - } + go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg) } localWg.Wait() @@ -262,7 +381,7 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config defer close(rch) query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+` - Name Tags } + RepoName Tag Digest ConfigDigest Size Layers {Size Digest}} }`, cvid) result := &imagesForCve{} @@ -299,16 +418,14 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config go rlim.startRateLimiter(ctx) - for _, image := range result.Data.ImageListForCVE { - if !strings.EqualFold(imageName, image.Name) { + for _, image := range result.Data.ImageList { + if !strings.EqualFold(imageName, image.RepoName) { continue } - for _, tag := range image.Tags { - localWg.Add(1) + localWg.Add(1) - go addManifestCallToPool(ctx, config, rlim, username, password, image.Name, tag, rch, &localWg) - } + go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg) } localWg.Wait() @@ -368,6 +485,59 @@ func (service searchService) getCveByImage(ctx context.Context, config searchCon rch <- stringResult{str, nil} } +func (service searchService) getFixedTagsForCVE(ctx context.Context, config searchConfig, + username, password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup, +) { + defer wtgrp.Done() + defer close(rch) + + query := fmt.Sprintf(`{ImageListWithCVEFixed (id: "%s", image: "%s") {`+` + RepoName Tag Digest Size} + }`, + cvid, imageName) + result := &fixedTags{} + + err := service.makeGraphQLQuery(ctx, config, username, password, query, result) + if err != nil { + if isContextDone(ctx) { + return + } + rch <- stringResult{"", err} + + return + } + + if result.Errors != nil { + var errBuilder strings.Builder + + for _, err := range result.Errors { + fmt.Fprintln(&errBuilder, err.Message) + } + + if isContextDone(ctx) { + return + } + rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113 + + return + } + + var localWg sync.WaitGroup + + rlim := newSmoothRateLimiter(&localWg, rch) + localWg.Add(1) + + go rlim.startRateLimiter(ctx) + + for _, img := range result.Data.ImageList { + localWg.Add(1) + + go addManifestCallToPool(ctx, config, rlim, username, password, imageName, img.Tag, rch, &localWg) + } + + localWg.Wait() +} + func groupCVEsBySeverity(cveList []cve) []cve { var ( unknown = make([]cve, 0) @@ -421,63 +591,10 @@ func isContextDone(ctx context.Context) bool { } } -func (service searchService) getFixedTagsForCVE(ctx context.Context, config searchConfig, - username, password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup, -) { - defer wtgrp.Done() - defer close(rch) - - query := fmt.Sprintf(`{ImageListWithCVEFixed (id: "%s", image: "%s") {`+` - Tags {Name Timestamp} } - }`, - cvid, imageName) - result := &fixedTags{} - - err := service.makeGraphQLQuery(ctx, config, username, password, query, result) - if err != nil { - if isContextDone(ctx) { - return - } - rch <- stringResult{"", err} - - return - } - - if result.Errors != nil { - var errBuilder strings.Builder - - for _, err := range result.Errors { - fmt.Fprintln(&errBuilder, err.Message) - } - - if isContextDone(ctx) { - return - } - rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113 - - return - } - - var localWg sync.WaitGroup - - rlim := newSmoothRateLimiter(&localWg, rch) - localWg.Add(1) - - go rlim.startRateLimiter(ctx) - - for _, imgTag := range result.Data.ImageListWithCVEFixed.Tags { - localWg.Add(1) - - go addManifestCallToPool(ctx, config, rlim, username, password, imageName, imgTag.Name, rch, &localWg) - } - - localWg.Wait() -} - // Query using JQL, 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, +func (service searchService) makeGraphQLQuery(ctx context.Context, + config searchConfig, username, password, query string, resultPtr interface{}, ) error { endPoint, err := combineServerAndEndpointURL(*config.servURL, constants.ExtSearchPrefix) @@ -493,6 +610,34 @@ func (service searchService) makeGraphQLQuery(ctx context.Context, config search return nil } +func checkResultGraphQLQuery(ctx context.Context, err error, resultErrors []errorGraphQL, +) error { + if err != nil { + if isContextDone(ctx) { + return nil // nolint:nilnil + } + + return err + } + + if resultErrors != nil { + var errBuilder strings.Builder + + for _, error := range resultErrors { + fmt.Fprintln(&errBuilder, error.Message) + } + + if isContextDone(ctx) { + return nil + } + + // nolint: goerr113 + return errors.New(errBuilder.String()) + } + + return nil +} + func addManifestCallToPool(ctx context.Context, config searchConfig, pool *requestsPool, username, password, imageName, tagName string, rch chan stringResult, wtgrp *sync.WaitGroup, ) { @@ -533,6 +678,11 @@ type errorGraphQL struct { Path []string `json:"path"` } +type tagListResp struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} + //nolint:tagliatelle // graphQL schema type packageList struct { Name string `json:"Name"` @@ -579,7 +729,7 @@ func (cve cveResult) stringPlainText() (string, error) { table := getCVETableWriter(&builder) for _, c := range cve.Data.CVEListForImage.CVEList { - id := ellipsize(c.ID, cvidWidth, ellipsis) + id := ellipsize(c.ID, cveIDWidth, ellipsis) title := ellipsize(c.Title, cveTitleWidth, ellipsis) severity := ellipsize(c.Severity, cveSeverityWidth, ellipsis) row := make([]string, 3) //nolint:gomnd @@ -618,51 +768,50 @@ func (cve cveResult) stringYAML() (string, error) { type fixedTags struct { Errors []errorGraphQL `json:"errors"` Data struct { - //nolint:tagliatelle // graphQL schema - ImageListWithCVEFixed struct { - Tags []struct { - Name string `json:"Name"` - Timestamp time.Time `json:"Timestamp"` - } `json:"Tags"` - } `json:"ImageListWithCVEFixed"` + ImageList []imageStruct `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema } `json:"data"` } type imagesForCve struct { Errors []errorGraphQL `json:"errors"` Data struct { - ImageListForCVE []tagListResp `json:"ImageListForCVE"` //nolint:tagliatelle // graphQL schema + ImageList []imageStruct `json:"ImageListForCVE"` //nolint:tagliatelle // graphQL schema + } `json:"data"` +} + +type imageStruct struct { + RepoName string `json:"repoName"` + Tag string `json:"tag"` + ConfigDigest string `json:"configDigest"` + Digest string `json:"digest"` + Layers []layer `json:"layers"` + Size string `json:"size"` + verbose bool +} + +type imageListStructGQL struct { + Errors []errorGraphQL `json:"errors"` + Data struct { + ImageList []imageStruct `json:"ImageList"` // nolint:tagliatelle + } `json:"data"` +} + +type imageListStructForDigestGQL struct { + Errors []errorGraphQL `json:"errors"` + Data struct { + ImageList []imageStruct `json:"ImageListForDigest"` // nolint:tagliatelle } `json:"data"` } type imagesForDigest struct { Errors []errorGraphQL `json:"errors"` Data struct { - ImageListForDigest []tagListResp `json:"ImageListForDigest"` //nolint:tagliatelle // graphQL schema + ImageList []imageStruct `json:"ImageListForDigest"` //nolint:tagliatelle // graphQL schema } `json:"data"` } -type tagListResp struct { - Name string `json:"name"` - Tags []string `json:"tags"` -} - -type imageStruct struct { - Name string `json:"name"` - Tags []tags `json:"tags"` - verbose bool -} - -type tags struct { - Name string `json:"name"` - Size uint64 `json:"size"` - Digest string `json:"digest"` - ConfigDigest string `json:"configDigest"` - Layers []layer `json:"layerDigests"` -} - type layer struct { - Size uint64 `json:"size"` + Size uint64 `json:"size,string"` Digest string `json:"digest"` } @@ -693,41 +842,41 @@ func (img imageStruct) stringPlainText() (string, error) { table.SetColMinWidth(colLayersIndex, layersWidth) } - for _, tag := range img.Tags { - imageName := ellipsize(img.Name, imageNameWidth, ellipsis) - tagName := ellipsize(tag.Name, tagWidth, ellipsis) - digest := ellipsize(tag.Digest, digestWidth, "") - size := ellipsize(strings.ReplaceAll(humanize.Bytes(tag.Size), " ", ""), sizeWidth, ellipsis) - config := ellipsize(tag.ConfigDigest, configWidth, "") - row := make([]string, 6) //nolint:gomnd + imageName := ellipsize(img.RepoName, imageNameWidth, ellipsis) + tagName := ellipsize(img.Tag, tagWidth, ellipsis) + digest := ellipsize(img.Digest, digestWidth, "") + imgSize, _ := strconv.ParseUint(img.Size, 10, 64) + size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis) + config := ellipsize(img.ConfigDigest, configWidth, "") + row := make([]string, 6) // nolint:gomnd - row[colImageNameIndex] = imageName - row[colTagIndex] = tagName - row[colDigestIndex] = digest - row[colSizeIndex] = size + row[colImageNameIndex] = imageName + row[colTagIndex] = tagName + row[colDigestIndex] = digest + row[colSizeIndex] = size - if img.verbose { - row[colConfigIndex] = config - row[colLayersIndex] = "" - } + if img.verbose { + row[colConfigIndex] = config + row[colLayersIndex] = "" + } - table.Append(row) + table.Append(row) - if img.verbose { - for _, entry := range tag.Layers { - layerSize := ellipsize(strings.ReplaceAll(humanize.Bytes(entry.Size), " ", ""), sizeWidth, ellipsis) - layerDigest := ellipsize(entry.Digest, digestWidth, "") + if img.verbose { + for _, entry := range img.Layers { + layerSize := entry.Size + size := ellipsize(strings.ReplaceAll(humanize.Bytes(layerSize), " ", ""), sizeWidth, ellipsis) + layerDigest := ellipsize(entry.Digest, digestWidth, "") - layerRow := make([]string, 6) //nolint:gomnd - layerRow[colImageNameIndex] = "" - layerRow[colTagIndex] = "" - layerRow[colDigestIndex] = "" - layerRow[colSizeIndex] = layerSize - layerRow[colConfigIndex] = "" - layerRow[colLayersIndex] = layerDigest + layerRow := make([]string, 6) // nolint:gomnd + layerRow[colImageNameIndex] = "" + layerRow[colTagIndex] = "" + layerRow[colDigestIndex] = "" + layerRow[colSizeIndex] = size + layerRow[colConfigIndex] = "" + layerRow[colLayersIndex] = layerDigest - table.Append(layerRow) - } + table.Append(layerRow) } } @@ -760,6 +909,7 @@ type catalogResponse struct { Repositories []string `json:"repositories"` } +//nolint:tagliatelle type manifestResponse struct { Layers []struct { MediaType string `json:"mediaType"` @@ -767,8 +917,8 @@ type manifestResponse struct { Size uint64 `json:"size"` } `json:"layers"` Annotations struct { - WsTychoStackerStackerYaml string `json:"ws.tycho.stacker.stacker_yaml"` //nolint:tagliatelle // custom annotation - WsTychoStackerGitVersion string `json:"ws.tycho.stacker.git_version"` //nolint:tagliatelle // custom annotation + WsTychoStackerStackerYaml string `json:"ws.tycho.stacker.stacker_yaml"` + WsTychoStackerGitVersion string `json:"ws.tycho.stacker.git_version"` } `json:"annotations"` Config struct { Size int `json:"size"` @@ -836,7 +986,7 @@ func getCVETableWriter(writer io.Writer) *tablewriter.Table { table.SetBorder(false) table.SetTablePadding(" ") table.SetNoWhiteSpace(true) - table.SetColMinWidth(colCVEIDIndex, cvidWidth) + table.SetColMinWidth(colCVEIDIndex, cveIDWidth) table.SetColMinWidth(colCVESeverityIndex, cveSeverityWidth) table.SetColMinWidth(colCVETitleIndex, cveTitleWidth) @@ -895,7 +1045,7 @@ const ( colLayersIndex = 4 colSizeIndex = 5 - cvidWidth = 16 + cveIDWidth = 16 cveSeverityWidth = 8 cveTitleWidth = 48 diff --git a/pkg/extensions/search/common/common.go b/pkg/extensions/search/common/common.go index 5c4ba00c..f6018cdf 100644 --- a/pkg/extensions/search/common/common.go +++ b/pkg/extensions/search/common/common.go @@ -2,7 +2,6 @@ package common import ( "fmt" - "path" "sort" "strings" "time" @@ -25,14 +24,6 @@ type TagInfo struct { Timestamp time.Time } -func GetImageRepoPath(image string, storeController storage.StoreController) string { - rootDir := GetRootDir(image, storeController) - - repo := GetRepo(image) - - return path.Join(rootDir, repo) -} - func GetRootDir(image string, storeController storage.StoreController) string { var rootDir string diff --git a/pkg/extensions/search/common/common_test.go b/pkg/extensions/search/common/common_test.go index 05bc5bd0..0dcfd044 100644 --- a/pkg/extensions/search/common/common_test.go +++ b/pkg/extensions/search/common/common_test.go @@ -97,7 +97,7 @@ type RepoSummary struct { Platforms []OsArch `json:"platforms"` Vendors []string `json:"vendors"` Score int `json:"score"` - NewestTag ImageSummary `json:"newestTag"` + NewestImage ImageSummary `json:"newestImage"` } type LayerSummary struct { @@ -126,8 +126,8 @@ type ErrorGQL struct { } type ImageInfo struct { - Name string - Latest string + RepoName string + Tag string LastUpdated time.Time Description string Licenses string @@ -377,7 +377,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -388,9 +388,9 @@ func TestLatestTagSearchHTTP(t *testing.T) { So(len(responseStruct.ImgListWithLatestTag.Images), ShouldEqual, 4) images := responseStruct.ImgListWithLatestTag.Images - So(images[0].Latest, ShouldEqual, "0.0.1") + So(images[0].Tag, ShouldEqual, "0.0.1") - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) @@ -399,7 +399,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -423,7 +423,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -434,7 +434,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -444,7 +444,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -455,7 +455,7 @@ func TestLatestTagSearchHTTP(t *testing.T) { panic(err) } - resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){Name%20Latest}}") + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query={ImageListWithLatestTag(){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -532,7 +532,7 @@ func TestExpandedRepoInfo(t *testing.T) { So(responseStruct.ExpandedRepoInfo.RepoInfo.Summary.Name, ShouldEqual, "zot-cve-test") So(responseStruct.ExpandedRepoInfo.RepoInfo.Summary.Score, ShouldEqual, -1) - query = "{ExpandedRepoInfo(repo:\"zot-cve-test\"){Manifests%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}" + query = "{ExpandedRepoInfo(repo:\"zot-cve-test\"){Images%20{Digest%20IsSigned%20Tag%20Layers%20{Size%20Digest}}}}" resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) @@ -543,10 +543,10 @@ func TestExpandedRepoInfo(t *testing.T) { err = json.Unmarshal(resp.Body(), responseStruct) So(err, ShouldBeNil) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0) found := false - for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests { + for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images { if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" { found = true So(m.IsSigned, ShouldEqual, false) @@ -564,10 +564,10 @@ func TestExpandedRepoInfo(t *testing.T) { err = json.Unmarshal(resp.Body(), responseStruct) So(err, ShouldBeNil) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0) found = false - for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests { + for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images { if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" { found = true So(m.IsSigned, ShouldEqual, true) @@ -575,14 +575,14 @@ func TestExpandedRepoInfo(t *testing.T) { } So(found, ShouldEqual, true) - query = "{ExpandedRepoInfo(repo:\"\"){Manifests%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}" + query = "{ExpandedRepoInfo(repo:\"\"){Images%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}" resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - query = "{ExpandedRepoInfo(repo:\"zot-test\"){Manifests%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}" + query = "{ExpandedRepoInfo(repo:\"zot-test\"){Images%20{Digest%20Tag%20IsSigned%20Layers%20{Size%20Digest}}}}" resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) @@ -590,10 +590,10 @@ func TestExpandedRepoInfo(t *testing.T) { err = json.Unmarshal(resp.Body(), responseStruct) So(err, ShouldBeNil) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0) found = false - for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests { + for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images { if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" { found = true So(m.IsSigned, ShouldEqual, false) @@ -611,10 +611,10 @@ func TestExpandedRepoInfo(t *testing.T) { err = json.Unmarshal(resp.Body(), responseStruct) So(err, ShouldBeNil) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0) - So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0) + So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0) found = false - for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests { + for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images { if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" { found = true So(m.IsSigned, ShouldEqual, true) @@ -832,7 +832,7 @@ func TestGlobalSearch(t *testing.T) { } Vendors Score - NewestTag { + NewestImage { RepoName Tag LastUpdated @@ -911,14 +911,14 @@ func TestGlobalSearch(t *testing.T) { So(repo.Vendors[0], ShouldEqual, image.Vendor) So(repo.Platforms[0].Os, ShouldEqual, image.Platform.Os) So(repo.Platforms[0].Arch, ShouldEqual, image.Platform.Arch) - So(repo.NewestTag.RepoName, ShouldEqual, image.RepoName) - So(repo.NewestTag.Tag, ShouldEqual, image.Tag) - So(repo.NewestTag.LastUpdated, ShouldEqual, image.LastUpdated) - So(repo.NewestTag.Size, ShouldEqual, image.Size) - So(repo.NewestTag.IsSigned, ShouldEqual, image.IsSigned) - So(repo.NewestTag.Vendor, ShouldEqual, image.Vendor) - So(repo.NewestTag.Platform.Os, ShouldEqual, image.Platform.Os) - So(repo.NewestTag.Platform.Arch, ShouldEqual, image.Platform.Arch) + So(repo.NewestImage.RepoName, ShouldEqual, image.RepoName) + So(repo.NewestImage.Tag, ShouldEqual, image.Tag) + So(repo.NewestImage.LastUpdated, ShouldEqual, image.LastUpdated) + So(repo.NewestImage.Size, ShouldEqual, image.Size) + So(repo.NewestImage.IsSigned, ShouldEqual, image.IsSigned) + So(repo.NewestImage.Vendor, ShouldEqual, image.Vendor) + So(repo.NewestImage.Platform.Os, ShouldEqual, image.Platform.Os) + So(repo.NewestImage.Platform.Arch, ShouldEqual, image.Platform.Arch) } // GetRepositories fail diff --git a/pkg/extensions/search/common/oci_layout.go b/pkg/extensions/search/common/oci_layout.go index 6702bf27..df0f1739 100644 --- a/pkg/extensions/search/common/oci_layout.go +++ b/pkg/extensions/search/common/oci_layout.go @@ -43,11 +43,11 @@ type BaseOciLayoutUtils struct { } type RepoInfo struct { - Manifests []Manifest `json:"manifests"` - Summary RepoSummary + Summary RepoSummary + Images []Image `json:"images"` } -type Manifest struct { +type Image struct { Tag string `json:"tag"` Digest string `json:"digest"` IsSigned bool `json:"isSigned"` @@ -358,7 +358,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) // made up of all manifests, configs and image layers repoSize := int64(0) - manifests := make([]Manifest, 0) + manifests := make([]Image, 0) tagsInfo, err := olu.GetImageTagsWithTimestamp(name) if err != nil { @@ -376,7 +376,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) repoVendors := make([]string, 0, len(manifestList)) for _, man := range manifestList { - manifestInfo := Manifest{} + manifestInfo := Image{} manifestInfo.Digest = man.Digest.Encoded() @@ -441,7 +441,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) manifests = append(manifests, manifestInfo) } - repo.Manifests = manifests + repo.Images = manifests lastUpdate, err := olu.GetRepoLastUpdated(name) if err != nil { diff --git a/pkg/extensions/search/cve/cve.go b/pkg/extensions/search/cve/cve.go index c91b80f0..e331ddfb 100644 --- a/pkg/extensions/search/cve/cve.go +++ b/pkg/extensions/search/cve/cve.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/commands/operation" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/types" + ispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/urfave/cli/v2" "zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/log" @@ -141,19 +142,21 @@ func (cveinfo CveInfo) GetTrivyContext(image string) *TrivyCtx { func (cveinfo CveInfo) GetImageListForCVE(repo, cvid string, imgStore storage.ImageStore, trivyCtx *TrivyCtx, -) ([]*string, error) { - tags := make([]*string, 0) - - tagList, err := imgStore.GetImageTags(repo) - if err != nil { - cveinfo.Log.Error().Err(err).Msg("unable to get list of image tag") - - return tags, err - } +) ([]ImageInfoByCVE, error) { + imgList := make([]ImageInfoByCVE, 0) rootDir := imgStore.RootDir() - for _, tag := range tagList { + manifests, err := cveinfo.LayoutUtils.GetImageManifests(repo) + if err != nil { + cveinfo.Log.Error().Err(err).Msg("unable to get list of image tag") + + return imgList, err + } + + for _, manifest := range manifests { + tag := manifest.Annotations[ispec.AnnotationRefName] + image := fmt.Sprintf("%s:%s", repo, tag) trivyCtx.Input = path.Join(rootDir, image) @@ -177,8 +180,20 @@ func (cveinfo CveInfo) GetImageListForCVE(repo, cvid string, imgStore storage.Im for _, result := range report.Results { for _, vulnerability := range result.Vulnerabilities { if vulnerability.VulnerabilityID == cvid { - copyImgTag := tag - tags = append(tags, ©ImgTag) + digest := manifest.Digest + + imageBlobManifest, err := cveinfo.LayoutUtils.GetImageBlobManifest(repo, digest) + if err != nil { + cveinfo.Log.Error().Err(err).Msg("unable to read image blob manifest") + + return []ImageInfoByCVE{}, err + } + + imgList = append(imgList, ImageInfoByCVE{ + Tag: tag, + Digest: digest, + Manifest: imageBlobManifest, + }) break } @@ -186,5 +201,5 @@ func (cveinfo CveInfo) GetImageListForCVE(repo, cvid string, imgStore storage.Im } } - return tags, nil + return imgList, nil } diff --git a/pkg/extensions/search/cve/cve_test.go b/pkg/extensions/search/cve/cve_test.go index 98d48d9d..f16a96c1 100644 --- a/pkg/extensions/search/cve/cve_test.go +++ b/pkg/extensions/search/cve/cve_test.go @@ -46,23 +46,14 @@ type CveResult struct { ImgList ImgList `json:"data"` } -type ImgWithFixedCVE struct { - ImgResults ImgResults `json:"data"` -} - //nolint:tagliatelle // graphQL schema -type ImgResults struct { - ImgResultForFixedCVE ImgResultForFixedCVE `json:"ImgResultForFixedCVE"` +type ImgListWithCVEFixed struct { + Images []ImageInfo `json:"ImageListWithCVEFixed"` } -//nolint:tagliatelle // graphQL schema -type ImgResultForFixedCVE struct { - Tags []TagInfo `json:"Tags"` -} - -type TagInfo struct { - Name string - Timestamp time.Time +type ImageInfo struct { + RepoName string + LastUpdated time.Time } //nolint:tagliatelle // graphQL schema @@ -470,24 +461,24 @@ func TestCVESearch(t *testing.T) { cvid := cveResult.ImgList.CVEResultForImage.CVEList[0].ID - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - var imgFixedCVEResult ImgWithFixedCVE - err = json.Unmarshal(resp.Body(), &imgFixedCVEResult) + var imgListWithCVEFixed ImgListWithCVEFixed + err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed) So(err, ShouldBeNil) - So(len(imgFixedCVEResult.ImgResults.ImgResultForFixedCVE.Tags), ShouldEqual, 0) + So(len(imgListWithCVEFixed.Images), ShouldEqual, 0) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-cve-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-cve-test\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - err = json.Unmarshal(resp.Body(), &imgFixedCVEResult) + err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed) So(err, ShouldBeNil) - So(len(imgFixedCVEResult.ImgResults.ImgResultForFixedCVE.Tags), ShouldEqual, 0) + So(len(imgListWithCVEFixed.Images), ShouldEqual, 0) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -504,7 +495,7 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noindex\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noindex\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -512,7 +503,7 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-index\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-index\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -520,11 +511,11 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noblob\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noblob\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-test\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-test\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -532,7 +523,7 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-blob\"){Tags{Name%20Timestamp}}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-blob\"){RepoName%20LastUpdated}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -544,7 +535,7 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(id:\"CVE-201-20482\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(id:\"CVE-201-20482\"){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -585,11 +576,11 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(tet:\"CVE-2018-20482\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(tet:\"CVE-2018-20482\"){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageistForCVE(id:\"CVE-2018-20482\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageistForCVE(id:\"CVE-2018-20482\"){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) @@ -601,7 +592,7 @@ func TestCVESearch(t *testing.T) { So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(id:\"" + cvid + "\"){Name%20Tags}}") + resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForCVE(id:\"" + cvid + "\"){RepoName%20Tag}}") So(resp, ShouldNotBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) diff --git a/pkg/extensions/search/cve/models.go b/pkg/extensions/search/cve/models.go index c4e24b94..36eabe60 100644 --- a/pkg/extensions/search/cve/models.go +++ b/pkg/extensions/search/cve/models.go @@ -2,6 +2,8 @@ package cveinfo import ( + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/opencontainers/go-digest" "github.com/urfave/cli/v2" "zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/log" @@ -25,3 +27,9 @@ type TrivyCtx struct { Input string Ctx *cli.Context } + +type ImageInfoByCVE struct { + Tag string + Digest digest.Digest + Manifest v1.Manifest +} diff --git a/pkg/extensions/search/digest/digest.go b/pkg/extensions/search/digest/digest.go index 34043247..69083c71 100644 --- a/pkg/extensions/search/digest/digest.go +++ b/pkg/extensions/search/digest/digest.go @@ -3,6 +3,8 @@ package digestinfo import ( "strings" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" "zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/log" @@ -15,6 +17,12 @@ type DigestInfo struct { LayoutUtils *common.BaseOciLayoutUtils } +type ImageInfoByDigest struct { + Tag string + Digest digest.Digest + Manifest v1.Manifest +} + // NewDigestInfo initializes a new DigestInfo object. func NewDigestInfo(storeController storage.StoreController, log log.Logger) *DigestInfo { layoutUtils := common.NewBaseOciLayoutUtils(storeController, log) @@ -23,14 +31,14 @@ func NewDigestInfo(storeController storage.StoreController, log log.Logger) *Dig } // FilterImagesByDigest returns a list of image tags in a repository matching a specific divest. -func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]*string, error) { - uniqueTags := []*string{} +func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]ImageInfoByDigest, error) { + imageTags := []ImageInfoByDigest{} manifests, err := digestinfo.LayoutUtils.GetImageManifests(repo) if err != nil { digestinfo.Log.Error().Err(err).Msg("unable to read image manifests") - return uniqueTags, err + return imageTags, err } for _, manifest := range manifests { @@ -42,7 +50,7 @@ func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]*strin if err != nil { digestinfo.Log.Error().Err(err).Msg("unable to read image blob manifest") - return uniqueTags, err + return imageTags, err } tags := []*string{} @@ -71,12 +79,12 @@ func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]*strin for _, entry := range tags { if _, value := keys[*entry]; !value { - uniqueTags = append(uniqueTags, entry) + imageTags = append(imageTags, ImageInfoByDigest{Tag: *entry, Digest: imageDigest, Manifest: imageBlobManifest}) keys[*entry] = true } } } } - return uniqueTags, nil + return imageTags, nil } diff --git a/pkg/extensions/search/digest/digest_test.go b/pkg/extensions/search/digest/digest_test.go index dd22132e..1050f65f 100644 --- a/pkg/extensions/search/digest/digest_test.go +++ b/pkg/extensions/search/digest/digest_test.go @@ -12,7 +12,6 @@ import ( "testing" "time" - "github.com/opencontainers/go-digest" . "github.com/smartystreets/goconvey/convey" "gopkg.in/resty.v1" "zotregistry.io/zot/pkg/api" @@ -45,8 +44,11 @@ type ImgListForDigest struct { //nolint:tagliatelle // graphQL schema type ImgInfo struct { - Name string `json:"Name"` - Tags []string `json:"Tags"` + RepoName string `json:"RepoName"` + Tag string `json:"Tag"` + ConfigDigest string `json:"ConfigDigest"` + Digest string `json:"Digest"` + Size string `json:"Size"` } type ErrorGQL struct { @@ -97,15 +99,10 @@ func testSetup() error { return err } - conf := config.New() - conf.Extensions = &extconf.ExtensionConfig{} - conf.Extensions.Lint = &extconf.LintConfig{} - log := log.NewLogger("debug", "") metrics := monitoring.NewMetricsServer(false, log) storeController := storage.StoreController{ - DefaultStore: storage.NewImageStore(rootDir, false, storage.DefaultGCDelay, - false, false, log, metrics, nil), + DefaultStore: storage.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil), } digestInfo = digestinfo.NewDigestInfo(storeController, log) @@ -115,33 +112,31 @@ func testSetup() error { func TestDigestInfo(t *testing.T) { Convey("Test image tag", t, func() { + log := log.NewLogger("debug", "") + metrics := monitoring.NewMetricsServer(false, log) + storeController := storage.StoreController{ + DefaultStore: storage.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil), + } + + digestInfo = digestinfo.NewDigestInfo(storeController, log) + // Search by manifest digest - var ( - manifestDigest digest.Digest - configDigest digest.Digest - layerDigest digest.Digest - ) - - manifestDigest, _, layerDigest = GetOciLayoutDigests("../../../../test/data/zot-cve-test") - - imageTags, err := digestInfo.GetImageTagsByDigest("zot-cve-test", string(manifestDigest)) + imageTags, err := digestInfo.GetImageTagsByDigest("zot-cve-test", "63a795ca") So(err, ShouldBeNil) So(len(imageTags), ShouldEqual, 1) - So(*imageTags[0], ShouldEqual, "0.0.1") + So(imageTags[0].Tag, ShouldEqual, "0.0.1") // Search by config digest - _, configDigest, _ = GetOciLayoutDigests("../../../../test/data/zot-test") - - imageTags, err = digestInfo.GetImageTagsByDigest("zot-test", string(configDigest)) + imageTags, err = digestInfo.GetImageTagsByDigest("zot-test", "adf3bb6c") So(err, ShouldBeNil) So(len(imageTags), ShouldEqual, 1) - So(*imageTags[0], ShouldEqual, "0.0.1") + So(imageTags[0].Tag, ShouldEqual, "0.0.1") // Search by layer digest - imageTags, err = digestInfo.GetImageTagsByDigest("zot-cve-test", string(layerDigest)) + imageTags, err = digestInfo.GetImageTagsByDigest("zot-cve-test", "7a0437f0") So(err, ShouldBeNil) So(len(imageTags), ShouldEqual, 1) - So(*imageTags[0], ShouldEqual, "0.0.1") + So(imageTags[0].Tag, ShouldEqual, "0.0.1") // Search by non-existent image imageTags, err = digestInfo.GetImageTagsByDigest("zot-tes", "63a795ca") @@ -202,8 +197,10 @@ func TestDigestSearchHTTP(t *testing.T) { So(resp.StatusCode(), ShouldEqual, 422) // "sha" should match all digests in all images - resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + - "?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}") + resp, err = resty.R().Get( + baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"sha")` + + `{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`, + ) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -213,16 +210,14 @@ func TestDigestSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(len(responseStruct.Errors), ShouldEqual, 0) So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 2) - So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1) - So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1) + So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1") // Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}} - var layerDigest digest.Digest - var manifestDigest digest.Digest - manifestDigest, _, layerDigest = GetOciLayoutDigests("../../../../test/data/zot-test") - - resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForDigest(id:\"" + - string(layerDigest) + "\"){Name%20Tags}}") + // "2bacca16" should match the manifest of 1 image + resp, err = resty.R().Get( + baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"2bacca16")` + + `{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`, + ) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -231,15 +226,14 @@ func TestDigestSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(len(responseStruct.Errors), ShouldEqual, 0) So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1) - So(responseStruct.ImgListForDigest.Images[0].Name, ShouldEqual, "zot-test") - So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1) - So(responseStruct.ImgListForDigest.Images[0].Tags[0], ShouldEqual, "0.0.1") + So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-test") + So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1") - // Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}} - - resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + - "?query={ImageListForDigest(id:\"" + - string(manifestDigest) + "\"){Name%20Tags}}") + // "adf3bb6c" should match the config of 1 image + resp, err = resty.R().Get( + baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"adf3bb6c")` + + `{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`, + ) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -248,15 +242,15 @@ func TestDigestSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(len(responseStruct.Errors), ShouldEqual, 0) So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1) - So(responseStruct.ImgListForDigest.Images[0].Name, ShouldEqual, "zot-test") - So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1) - So(responseStruct.ImgListForDigest.Images[0].Tags[0], ShouldEqual, "0.0.1") + So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-test") + So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1") // Call should return {"data":{"ImageListForDigest":[{"Name":"zot-cve-test","Tags":["0.0.1"]}]}} - - _, _, layerDigest = GetOciLayoutDigests("../../../../test/data/zot-cve-test") - resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForDigest(id:\"" + - string(layerDigest) + "\"){Name%20Tags}}") + // "7a0437f0" should match the layer of 1 image + resp, err = resty.R().Get( + baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"7a0437f0")` + + `{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`, + ) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -265,14 +259,15 @@ func TestDigestSearchHTTP(t *testing.T) { So(err, ShouldBeNil) So(len(responseStruct.Errors), ShouldEqual, 0) So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1) - So(responseStruct.ImgListForDigest.Images[0].Name, ShouldEqual, "zot-cve-test") - So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1) - So(responseStruct.ImgListForDigest.Images[0].Tags[0], ShouldEqual, "0.0.1") + So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-cve-test") + So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1") // Call should return {"data":{"ImageListForDigest":[]}} // "1111111" should match 0 images - resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + - "?query={ImageListForDigest(id:\"1111111\"){Name%20Tags}}") + resp, err = resty.R().Get( + baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"1111111")` + + `{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`, + ) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) @@ -283,8 +278,10 @@ func TestDigestSearchHTTP(t *testing.T) { So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 0) // Call should return {"errors": [{....}]", data":null}} - resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + - "?query={ImageListForDigest(id:\"1111111\"){Name%20Tag343s}}") + resp, err = resty.R().Get( + baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"1111111")` + + `{RepoName%20Tag343s}}`, + ) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 422) @@ -354,8 +351,10 @@ func TestDigestSearchHTTPSubPaths(t *testing.T) { So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 422) - resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + - "?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}") + resp, err = resty.R().Get( + baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"sha")` + + `{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`, + ) So(resp, ShouldNotBeNil) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index 69d1c6d4..2b7d4d1b 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -9,6 +9,7 @@ import ( "fmt" "strconv" "sync" + "sync/atomic" "time" "github.com/99designs/gqlgen/graphql" @@ -61,45 +62,22 @@ type ComplexityRoot struct { Repos func(childComplexity int) int } - ImageInfo struct { - Description func(childComplexity int) int - Labels func(childComplexity int) int - LastUpdated func(childComplexity int) int - Latest func(childComplexity int) int - Licenses func(childComplexity int) int - Name func(childComplexity int) int - Size func(childComplexity int) int - Vendor func(childComplexity int) int - } - ImageSummary struct { - IsSigned func(childComplexity int) int - LastUpdated func(childComplexity int) int - Platform func(childComplexity int) int - RepoName func(childComplexity int) int - Score func(childComplexity int) int - Size func(childComplexity int) int - Tag func(childComplexity int) int - Vendor func(childComplexity int) int - } - - ImgResultForCVE struct { - Name func(childComplexity int) int - Tags func(childComplexity int) int - } - - ImgResultForDigest struct { - Name func(childComplexity int) int - Tags func(childComplexity int) int - } - - ImgResultForFixedCVE struct { - Tags func(childComplexity int) int - } - - LayerInfo struct { - Digest func(childComplexity int) int - Size func(childComplexity int) int + ConfigDigest func(childComplexity int) int + Description func(childComplexity int) int + Digest func(childComplexity int) int + DownloadCount func(childComplexity int) int + IsSigned func(childComplexity int) int + Labels func(childComplexity int) int + LastUpdated func(childComplexity int) int + Layers func(childComplexity int) int + Licenses func(childComplexity int) int + Platform func(childComplexity int) int + RepoName func(childComplexity int) int + Score func(childComplexity int) int + Size func(childComplexity int) int + Tag func(childComplexity int) int + Vendor func(childComplexity int) int } LayerSummary struct { @@ -108,13 +86,6 @@ type ComplexityRoot struct { Size func(childComplexity int) int } - ManifestInfo struct { - Digest func(childComplexity int) int - IsSigned func(childComplexity int) int - Layers func(childComplexity int) int - Tag func(childComplexity int) int - } - OsArch struct { Arch func(childComplexity int) int Os func(childComplexity int) int @@ -130,6 +101,7 @@ type ComplexityRoot struct { CVEListForImage func(childComplexity int, image string) int ExpandedRepoInfo func(childComplexity int, repo string) int GlobalSearch func(childComplexity int, query string) int + ImageList func(childComplexity int, repo string) int ImageListForCve func(childComplexity int, id string) int ImageListForDigest func(childComplexity int, id string) int ImageListWithCVEFixed func(childComplexity int, id string, image string) int @@ -137,33 +109,31 @@ type ComplexityRoot struct { } RepoInfo struct { - Manifests func(childComplexity int) int - Summary func(childComplexity int) int + Images func(childComplexity int) int + Summary func(childComplexity int) int } RepoSummary struct { - LastUpdated func(childComplexity int) int - Name func(childComplexity int) int - NewestTag func(childComplexity int) int - Platforms func(childComplexity int) int - Score func(childComplexity int) int - Size func(childComplexity int) int - Vendors func(childComplexity int) int - } - - TagInfo struct { - Digest func(childComplexity int) int - Name func(childComplexity int) int - Timestamp func(childComplexity int) int + DownloadCount func(childComplexity int) int + IsBookmarked func(childComplexity int) int + LastUpdated func(childComplexity int) int + Name func(childComplexity int) int + NewestImage func(childComplexity int) int + Platforms func(childComplexity int) int + Score func(childComplexity int) int + Size func(childComplexity int) int + StarCount func(childComplexity int) int + Vendors func(childComplexity int) int } } type QueryResolver interface { CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error) - ImageListForCve(ctx context.Context, id string) ([]*ImgResultForCve, error) - ImageListWithCVEFixed(ctx context.Context, id string, image string) (*ImgResultForFixedCve, error) - ImageListForDigest(ctx context.Context, id string) ([]*ImgResultForDigest, error) - ImageListWithLatestTag(ctx context.Context) ([]*ImageInfo, error) + ImageListForCve(ctx context.Context, id string) ([]*ImageSummary, error) + ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*ImageSummary, error) + ImageListForDigest(ctx context.Context, id string) ([]*ImageSummary, error) + ImageListWithLatestTag(ctx context.Context) ([]*ImageSummary, error) + ImageList(ctx context.Context, repo string) ([]*ImageSummary, error) ExpandedRepoInfo(ctx context.Context, repo string) (*RepoInfo, error) GlobalSearch(ctx context.Context, query string) (*GlobalSearchResult, error) } @@ -253,61 +223,33 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.GlobalSearchResult.Repos(childComplexity), true - case "ImageInfo.Description": - if e.complexity.ImageInfo.Description == nil { + case "ImageSummary.ConfigDigest": + if e.complexity.ImageSummary.ConfigDigest == nil { break } - return e.complexity.ImageInfo.Description(childComplexity), true + return e.complexity.ImageSummary.ConfigDigest(childComplexity), true - case "ImageInfo.Labels": - if e.complexity.ImageInfo.Labels == nil { + case "ImageSummary.Description": + if e.complexity.ImageSummary.Description == nil { break } - return e.complexity.ImageInfo.Labels(childComplexity), true + return e.complexity.ImageSummary.Description(childComplexity), true - case "ImageInfo.LastUpdated": - if e.complexity.ImageInfo.LastUpdated == nil { + case "ImageSummary.Digest": + if e.complexity.ImageSummary.Digest == nil { break } - return e.complexity.ImageInfo.LastUpdated(childComplexity), true + return e.complexity.ImageSummary.Digest(childComplexity), true - case "ImageInfo.Latest": - if e.complexity.ImageInfo.Latest == nil { + case "ImageSummary.DownloadCount": + if e.complexity.ImageSummary.DownloadCount == nil { break } - return e.complexity.ImageInfo.Latest(childComplexity), true - - case "ImageInfo.Licenses": - if e.complexity.ImageInfo.Licenses == nil { - break - } - - return e.complexity.ImageInfo.Licenses(childComplexity), true - - case "ImageInfo.Name": - if e.complexity.ImageInfo.Name == nil { - break - } - - return e.complexity.ImageInfo.Name(childComplexity), true - - case "ImageInfo.Size": - if e.complexity.ImageInfo.Size == nil { - break - } - - return e.complexity.ImageInfo.Size(childComplexity), true - - case "ImageInfo.Vendor": - if e.complexity.ImageInfo.Vendor == nil { - break - } - - return e.complexity.ImageInfo.Vendor(childComplexity), true + return e.complexity.ImageSummary.DownloadCount(childComplexity), true case "ImageSummary.IsSigned": if e.complexity.ImageSummary.IsSigned == nil { @@ -316,6 +258,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ImageSummary.IsSigned(childComplexity), true + case "ImageSummary.Labels": + if e.complexity.ImageSummary.Labels == nil { + break + } + + return e.complexity.ImageSummary.Labels(childComplexity), true + case "ImageSummary.LastUpdated": if e.complexity.ImageSummary.LastUpdated == nil { break @@ -323,6 +272,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ImageSummary.LastUpdated(childComplexity), true + case "ImageSummary.Layers": + if e.complexity.ImageSummary.Layers == nil { + break + } + + return e.complexity.ImageSummary.Layers(childComplexity), true + + case "ImageSummary.Licenses": + if e.complexity.ImageSummary.Licenses == nil { + break + } + + return e.complexity.ImageSummary.Licenses(childComplexity), true + case "ImageSummary.Platform": if e.complexity.ImageSummary.Platform == nil { break @@ -365,55 +328,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ImageSummary.Vendor(childComplexity), true - case "ImgResultForCVE.Name": - if e.complexity.ImgResultForCVE.Name == nil { - break - } - - return e.complexity.ImgResultForCVE.Name(childComplexity), true - - case "ImgResultForCVE.Tags": - if e.complexity.ImgResultForCVE.Tags == nil { - break - } - - return e.complexity.ImgResultForCVE.Tags(childComplexity), true - - case "ImgResultForDigest.Name": - if e.complexity.ImgResultForDigest.Name == nil { - break - } - - return e.complexity.ImgResultForDigest.Name(childComplexity), true - - case "ImgResultForDigest.Tags": - if e.complexity.ImgResultForDigest.Tags == nil { - break - } - - return e.complexity.ImgResultForDigest.Tags(childComplexity), true - - case "ImgResultForFixedCVE.Tags": - if e.complexity.ImgResultForFixedCVE.Tags == nil { - break - } - - return e.complexity.ImgResultForFixedCVE.Tags(childComplexity), true - - case "LayerInfo.Digest": - if e.complexity.LayerInfo.Digest == nil { - break - } - - return e.complexity.LayerInfo.Digest(childComplexity), true - - case "LayerInfo.Size": - if e.complexity.LayerInfo.Size == nil { - break - } - - return e.complexity.LayerInfo.Size(childComplexity), true - case "LayerSummary.Digest": if e.complexity.LayerSummary.Digest == nil { break @@ -435,34 +349,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.LayerSummary.Size(childComplexity), true - case "ManifestInfo.Digest": - if e.complexity.ManifestInfo.Digest == nil { - break - } - - return e.complexity.ManifestInfo.Digest(childComplexity), true - - case "ManifestInfo.IsSigned": - if e.complexity.ManifestInfo.IsSigned == nil { - break - } - - return e.complexity.ManifestInfo.IsSigned(childComplexity), true - - case "ManifestInfo.Layers": - if e.complexity.ManifestInfo.Layers == nil { - break - } - - return e.complexity.ManifestInfo.Layers(childComplexity), true - - case "ManifestInfo.Tag": - if e.complexity.ManifestInfo.Tag == nil { - break - } - - return e.complexity.ManifestInfo.Tag(childComplexity), true - case "OsArch.Arch": if e.complexity.OsArch.Arch == nil { break @@ -534,6 +420,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.GlobalSearch(childComplexity, args["query"].(string)), true + case "Query.ImageList": + if e.complexity.Query.ImageList == nil { + break + } + + args, err := ec.field_Query_ImageList_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.ImageList(childComplexity, args["repo"].(string)), true + case "Query.ImageListForCVE": if e.complexity.Query.ImageListForCve == nil { break @@ -577,12 +475,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.ImageListWithLatestTag(childComplexity), true - case "RepoInfo.Manifests": - if e.complexity.RepoInfo.Manifests == nil { + case "RepoInfo.Images": + if e.complexity.RepoInfo.Images == nil { break } - return e.complexity.RepoInfo.Manifests(childComplexity), true + return e.complexity.RepoInfo.Images(childComplexity), true case "RepoInfo.Summary": if e.complexity.RepoInfo.Summary == nil { @@ -591,6 +489,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RepoInfo.Summary(childComplexity), true + case "RepoSummary.DownloadCount": + if e.complexity.RepoSummary.DownloadCount == nil { + break + } + + return e.complexity.RepoSummary.DownloadCount(childComplexity), true + + case "RepoSummary.IsBookmarked": + if e.complexity.RepoSummary.IsBookmarked == nil { + break + } + + return e.complexity.RepoSummary.IsBookmarked(childComplexity), true + case "RepoSummary.LastUpdated": if e.complexity.RepoSummary.LastUpdated == nil { break @@ -605,12 +517,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RepoSummary.Name(childComplexity), true - case "RepoSummary.NewestTag": - if e.complexity.RepoSummary.NewestTag == nil { + case "RepoSummary.NewestImage": + if e.complexity.RepoSummary.NewestImage == nil { break } - return e.complexity.RepoSummary.NewestTag(childComplexity), true + return e.complexity.RepoSummary.NewestImage(childComplexity), true case "RepoSummary.Platforms": if e.complexity.RepoSummary.Platforms == nil { @@ -633,6 +545,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RepoSummary.Size(childComplexity), true + case "RepoSummary.StarCount": + if e.complexity.RepoSummary.StarCount == nil { + break + } + + return e.complexity.RepoSummary.StarCount(childComplexity), true + case "RepoSummary.Vendors": if e.complexity.RepoSummary.Vendors == nil { break @@ -640,27 +559,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RepoSummary.Vendors(childComplexity), true - case "TagInfo.Digest": - if e.complexity.TagInfo.Digest == nil { - break - } - - return e.complexity.TagInfo.Digest(childComplexity), true - - case "TagInfo.Name": - if e.complexity.TagInfo.Name == nil { - break - } - - return e.complexity.TagInfo.Name(childComplexity), true - - case "TagInfo.Timestamp": - if e.complexity.TagInfo.Timestamp == nil { - break - } - - return e.complexity.TagInfo.Timestamp(childComplexity), true - } return 0, false } @@ -716,125 +614,93 @@ var sources = []*ast.Source{ {Name: "../schema.graphql", Input: `scalar Time type CVEResultForImage { - Tag: String - CVEList: [CVE] + Tag: String + CVEList: [CVE] } type CVE { - Id: String - Title: String - Description: String - Severity: String - PackageList: [PackageInfo] + Id: String + Title: String + Description: String + Severity: String + PackageList: [PackageInfo] } type PackageInfo { - Name: String - InstalledVersion: String - FixedVersion: String -} - -type ImgResultForCVE { - Name: String - Tags: [String] -} - -type ImgResultForFixedCVE { - Tags: [TagInfo] -} - -type ImgResultForDigest { - Name: String - Tags: [String] -} - -type TagInfo { - Name: String - Digest: String - Timestamp: Time -} - -type ImageInfo { - Name: String - Latest: String - LastUpdated: Time - Description: String - Licenses: String - Vendor: String - Size: String - Labels: String + Name: String + InstalledVersion: String + FixedVersion: String } type RepoInfo { - Manifests: [ManifestInfo] - Summary: RepoSummary -} - -type ManifestInfo { - Digest: String - Tag: String - IsSigned: Boolean - Layers: [LayerInfo] -} - -type LayerInfo { - Size: String # Int64 is not supported. - Digest: String + Images: [ImageSummary] + Summary: RepoSummary } # Search results in all repos/images/layers # There will be other more structures for more detailed information type GlobalSearchResult { - Images: [ImageSummary] - Repos: [RepoSummary] - Layers: [LayerSummary] + Images: [ImageSummary] + Repos: [RepoSummary] + Layers: [LayerSummary] } # Brief on a specific image to be used in queries returning a list of images # We define an image as a pairing or a repo and a tag belonging to that repo type ImageSummary { - RepoName: String - Tag: String - LastUpdated: Time - IsSigned: Boolean - Size: String - Platform: OsArch - Vendor: String - Score: Int + RepoName: String + Tag: String + Digest: String + ConfigDigest: String + LastUpdated: Time + IsSigned: Boolean + Size: String + Platform: OsArch + Vendor: String + Score: Int + DownloadCount: Int + Layers: [LayerSummary] + Description: String + Licenses: String + Labels: String } # Brief on a specific repo to be used in queries returning a list of repos type RepoSummary { - Name: String - LastUpdated: Time - Size: String - Platforms: [OsArch] - Vendors: [String] - Score: Int - NewestTag: ImageSummary + Name: String + LastUpdated: Time + Size: String + Platforms: [OsArch] + Vendors: [String] + Score: Int + NewestImage: ImageSummary + DownloadCount: Int + StarCount: Int + IsBookmarked: Boolean } # Currently the same as LayerInfo, we can refactor later # For detailed information on the layer a ImageListForDigest call can be made type LayerSummary { - Size: String # Int64 is not supported. - Digest: String - Score: Int + Size: String # Int64 is not supported. + Digest: String + Score: Int } type OsArch { - Os: String - Arch: String + Os: String + Arch: String } type Query { - CVEListForImage(image: String!) :CVEResultForImage - ImageListForCVE(id: String!) :[ImgResultForCVE] - ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE - ImageListForDigest(id: String!) :[ImgResultForDigest] - ImageListWithLatestTag:[ImageInfo] - ExpandedRepoInfo(repo: String!):RepoInfo - GlobalSearch(query: String!): GlobalSearchResult + CVEListForImage(image: String!): CVEResultForImage! + ImageListForCVE(id: String!): [ImageSummary!] + ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!] + ImageListForDigest(id: String!): [ImageSummary!] + ImageListWithLatestTag: [ImageSummary!] + ImageList(repo: String!): [ImageSummary!] + ExpandedRepoInfo(repo: String!): RepoInfo! + GlobalSearch(query: String!): GlobalSearchResult! } `, BuiltIn: false}, } @@ -943,6 +809,21 @@ func (ec *executionContext) field_Query_ImageListWithCVEFixed_args(ctx context.C return args, nil } +func (ec *executionContext) field_Query_ImageList_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["repo"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("repo")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["repo"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1343,6 +1224,10 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.C return ec.fieldContext_ImageSummary_RepoName(ctx, field) case "Tag": return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) case "LastUpdated": return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) case "IsSigned": @@ -1355,6 +1240,16 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.C return ec.fieldContext_ImageSummary_Vendor(ctx, field) case "Score": return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) + case "Layers": + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) + case "Labels": + return ec.fieldContext_ImageSummary_Labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -1410,8 +1305,14 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Repos(ctx context.Co return ec.fieldContext_RepoSummary_Vendors(ctx, field) case "Score": return ec.fieldContext_RepoSummary_Score(ctx, field) - case "NewestTag": - return ec.fieldContext_RepoSummary_NewestTag(ctx, field) + case "NewestImage": + return ec.fieldContext_RepoSummary_NewestImage(ctx, field) + case "DownloadCount": + return ec.fieldContext_RepoSummary_DownloadCount(ctx, field) + case "StarCount": + return ec.fieldContext_RepoSummary_StarCount(ctx, field) + case "IsBookmarked": + return ec.fieldContext_RepoSummary_IsBookmarked(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type RepoSummary", field.Name) }, @@ -1468,334 +1369,6 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Layers(ctx context.C return fc, nil } -func (ec *executionContext) _ImageInfo_Name(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_Name(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Name, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_Name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImageInfo_Latest(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_Latest(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Latest, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_Latest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImageInfo_LastUpdated(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_LastUpdated(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.LastUpdated, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*time.Time) - fc.Result = res - return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_LastUpdated(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Time does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImageInfo_Description(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_Description(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Description, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_Description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImageInfo_Licenses(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_Licenses(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Licenses, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_Licenses(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImageInfo_Vendor(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_Vendor(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Vendor, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_Vendor(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImageInfo_Size(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_Size(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Size, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_Size(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImageInfo_Labels(ctx context.Context, field graphql.CollectedField, obj *ImageInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImageInfo_Labels(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Labels, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImageInfo_Labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImageInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _ImageSummary_RepoName(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ImageSummary_RepoName(ctx, field) if err != nil { @@ -1878,6 +1451,88 @@ func (ec *executionContext) fieldContext_ImageSummary_Tag(ctx context.Context, f return fc, nil } +func (ec *executionContext) _ImageSummary_Digest(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_Digest(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Digest, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ImageSummary_Digest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageSummary", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageSummary_ConfigDigest(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ConfigDigest, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ImageSummary_ConfigDigest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageSummary", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _ImageSummary_LastUpdated(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ImageSummary_LastUpdated(ctx, field) if err != nil { @@ -2130,8 +1785,8 @@ func (ec *executionContext) fieldContext_ImageSummary_Score(ctx context.Context, return fc, nil } -func (ec *executionContext) _ImgResultForCVE_Name(ctx context.Context, field graphql.CollectedField, obj *ImgResultForCve) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImgResultForCVE_Name(ctx, field) +func (ec *executionContext) _ImageSummary_DownloadCount(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_DownloadCount(ctx, field) if err != nil { return graphql.Null } @@ -2144,7 +1799,7 @@ func (ec *executionContext) _ImgResultForCVE_Name(ctx context.Context, field gra }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Name, nil + return obj.DownloadCount, nil }) if err != nil { ec.Error(ctx, err) @@ -2153,26 +1808,26 @@ func (ec *executionContext) _ImgResultForCVE_Name(ctx context.Context, field gra if resTmp == nil { return graphql.Null } - res := resTmp.(*string) + res := resTmp.(*int) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalOInt2ᚖint(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_ImgResultForCVE_Name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_ImageSummary_DownloadCount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "ImgResultForCVE", + Object: "ImageSummary", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type Int does not have child fields") }, } return fc, nil } -func (ec *executionContext) _ImgResultForCVE_Tags(ctx context.Context, field graphql.CollectedField, obj *ImgResultForCve) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImgResultForCVE_Tags(ctx, field) +func (ec *executionContext) _ImageSummary_Layers(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_Layers(ctx, field) if err != nil { return graphql.Null } @@ -2185,7 +1840,7 @@ func (ec *executionContext) _ImgResultForCVE_Tags(ctx context.Context, field gra }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Tags, nil + return obj.Layers, nil }) if err != nil { ec.Error(ctx, err) @@ -2194,157 +1849,34 @@ func (ec *executionContext) _ImgResultForCVE_Tags(ctx context.Context, field gra if resTmp == nil { return graphql.Null } - res := resTmp.([]*string) + res := resTmp.([]*LayerSummary) fc.Result = res - return ec.marshalOString2ᚕᚖstring(ctx, field.Selections, res) + return ec.marshalOLayerSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐLayerSummary(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_ImgResultForCVE_Tags(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_ImageSummary_Layers(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "ImgResultForCVE", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImgResultForDigest_Name(ctx context.Context, field graphql.CollectedField, obj *ImgResultForDigest) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImgResultForDigest_Name(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Name, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImgResultForDigest_Name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImgResultForDigest", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImgResultForDigest_Tags(ctx context.Context, field graphql.CollectedField, obj *ImgResultForDigest) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImgResultForDigest_Tags(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Tags, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.([]*string) - fc.Result = res - return ec.marshalOString2ᚕᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImgResultForDigest_Tags(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImgResultForDigest", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ImgResultForFixedCVE_Tags(ctx context.Context, field graphql.CollectedField, obj *ImgResultForFixedCve) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ImgResultForFixedCVE_Tags(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Tags, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.([]*TagInfo) - fc.Result = res - return ec.marshalOTagInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐTagInfo(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ImgResultForFixedCVE_Tags(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ImgResultForFixedCVE", + Object: "ImageSummary", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "Name": - return ec.fieldContext_TagInfo_Name(ctx, field) + case "Size": + return ec.fieldContext_LayerSummary_Size(ctx, field) case "Digest": - return ec.fieldContext_TagInfo_Digest(ctx, field) - case "Timestamp": - return ec.fieldContext_TagInfo_Timestamp(ctx, field) + return ec.fieldContext_LayerSummary_Digest(ctx, field) + case "Score": + return ec.fieldContext_LayerSummary_Score(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type TagInfo", field.Name) + return nil, fmt.Errorf("no field named %q was found under type LayerSummary", field.Name) }, } return fc, nil } -func (ec *executionContext) _LayerInfo_Size(ctx context.Context, field graphql.CollectedField, obj *LayerInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_LayerInfo_Size(ctx, field) +func (ec *executionContext) _ImageSummary_Description(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_Description(ctx, field) if err != nil { return graphql.Null } @@ -2357,7 +1889,7 @@ func (ec *executionContext) _LayerInfo_Size(ctx context.Context, field graphql.C }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Size, nil + return obj.Description, nil }) if err != nil { ec.Error(ctx, err) @@ -2371,9 +1903,9 @@ func (ec *executionContext) _LayerInfo_Size(ctx context.Context, field graphql.C return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_LayerInfo_Size(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_ImageSummary_Description(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "LayerInfo", + Object: "ImageSummary", Field: field, IsMethod: false, IsResolver: false, @@ -2384,8 +1916,8 @@ func (ec *executionContext) fieldContext_LayerInfo_Size(ctx context.Context, fie return fc, nil } -func (ec *executionContext) _LayerInfo_Digest(ctx context.Context, field graphql.CollectedField, obj *LayerInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_LayerInfo_Digest(ctx, field) +func (ec *executionContext) _ImageSummary_Licenses(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_Licenses(ctx, field) if err != nil { return graphql.Null } @@ -2398,7 +1930,7 @@ func (ec *executionContext) _LayerInfo_Digest(ctx context.Context, field graphql }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Digest, nil + return obj.Licenses, nil }) if err != nil { ec.Error(ctx, err) @@ -2412,9 +1944,50 @@ func (ec *executionContext) _LayerInfo_Digest(ctx context.Context, field graphql return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_LayerInfo_Digest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_ImageSummary_Licenses(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "LayerInfo", + Object: "ImageSummary", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageSummary_Labels(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageSummary_Labels(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Labels, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ImageSummary_Labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageSummary", Field: field, IsMethod: false, IsResolver: false, @@ -2548,176 +2121,6 @@ func (ec *executionContext) fieldContext_LayerSummary_Score(ctx context.Context, return fc, nil } -func (ec *executionContext) _ManifestInfo_Digest(ctx context.Context, field graphql.CollectedField, obj *ManifestInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ManifestInfo_Digest(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Digest, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ManifestInfo_Digest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ManifestInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ManifestInfo_Tag(ctx context.Context, field graphql.CollectedField, obj *ManifestInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ManifestInfo_Tag(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Tag, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ManifestInfo_Tag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ManifestInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ManifestInfo_IsSigned(ctx context.Context, field graphql.CollectedField, obj *ManifestInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ManifestInfo_IsSigned(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.IsSigned, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*bool) - fc.Result = res - return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ManifestInfo_IsSigned(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ManifestInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Boolean does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _ManifestInfo_Layers(ctx context.Context, field graphql.CollectedField, obj *ManifestInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_ManifestInfo_Layers(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Layers, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.([]*LayerInfo) - fc.Result = res - return ec.marshalOLayerInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐLayerInfo(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_ManifestInfo_Layers(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "ManifestInfo", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "Size": - return ec.fieldContext_LayerInfo_Size(ctx, field) - case "Digest": - return ec.fieldContext_LayerInfo_Digest(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type LayerInfo", field.Name) - }, - } - return fc, nil -} - func (ec *executionContext) _OsArch_Os(ctx context.Context, field graphql.CollectedField, obj *OsArch) (ret graphql.Marshaler) { fc, err := ec.fieldContext_OsArch_Os(ctx, field) if err != nil { @@ -2944,11 +2347,14 @@ func (ec *executionContext) _Query_CVEListForImage(ctx context.Context, field gr return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } res := resTmp.(*CVEResultForImage) fc.Result = res - return ec.marshalOCVEResultForImage2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEResultForImage(ctx, field.Selections, res) + return ec.marshalNCVEResultForImage2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEResultForImage(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_CVEListForImage(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3004,9 +2410,9 @@ func (ec *executionContext) _Query_ImageListForCVE(ctx context.Context, field gr if resTmp == nil { return graphql.Null } - res := resTmp.([]*ImgResultForCve) + res := resTmp.([]*ImageSummary) fc.Result = res - return ec.marshalOImgResultForCVE2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForCve(ctx, field.Selections, res) + return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_ImageListForCVE(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3017,12 +2423,38 @@ func (ec *executionContext) fieldContext_Query_ImageListForCVE(ctx context.Conte IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "Name": - return ec.fieldContext_ImgResultForCVE_Name(ctx, field) - case "Tags": - return ec.fieldContext_ImgResultForCVE_Tags(ctx, field) + case "RepoName": + return ec.fieldContext_ImageSummary_RepoName(ctx, field) + case "Tag": + return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) + case "LastUpdated": + return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) + case "IsSigned": + return ec.fieldContext_ImageSummary_IsSigned(ctx, field) + case "Size": + return ec.fieldContext_ImageSummary_Size(ctx, field) + case "Platform": + return ec.fieldContext_ImageSummary_Platform(ctx, field) + case "Vendor": + return ec.fieldContext_ImageSummary_Vendor(ctx, field) + case "Score": + return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) + case "Layers": + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) + case "Labels": + return ec.fieldContext_ImageSummary_Labels(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type ImgResultForCVE", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, } defer func() { @@ -3062,9 +2494,9 @@ func (ec *executionContext) _Query_ImageListWithCVEFixed(ctx context.Context, fi if resTmp == nil { return graphql.Null } - res := resTmp.(*ImgResultForFixedCve) + res := resTmp.([]*ImageSummary) fc.Result = res - return ec.marshalOImgResultForFixedCVE2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForFixedCve(ctx, field.Selections, res) + return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_ImageListWithCVEFixed(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3075,10 +2507,38 @@ func (ec *executionContext) fieldContext_Query_ImageListWithCVEFixed(ctx context IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "Tags": - return ec.fieldContext_ImgResultForFixedCVE_Tags(ctx, field) + case "RepoName": + return ec.fieldContext_ImageSummary_RepoName(ctx, field) + case "Tag": + return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) + case "LastUpdated": + return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) + case "IsSigned": + return ec.fieldContext_ImageSummary_IsSigned(ctx, field) + case "Size": + return ec.fieldContext_ImageSummary_Size(ctx, field) + case "Platform": + return ec.fieldContext_ImageSummary_Platform(ctx, field) + case "Vendor": + return ec.fieldContext_ImageSummary_Vendor(ctx, field) + case "Score": + return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) + case "Layers": + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) + case "Labels": + return ec.fieldContext_ImageSummary_Labels(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type ImgResultForFixedCVE", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, } defer func() { @@ -3118,9 +2578,9 @@ func (ec *executionContext) _Query_ImageListForDigest(ctx context.Context, field if resTmp == nil { return graphql.Null } - res := resTmp.([]*ImgResultForDigest) + res := resTmp.([]*ImageSummary) fc.Result = res - return ec.marshalOImgResultForDigest2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForDigest(ctx, field.Selections, res) + return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_ImageListForDigest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3131,12 +2591,38 @@ func (ec *executionContext) fieldContext_Query_ImageListForDigest(ctx context.Co IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "Name": - return ec.fieldContext_ImgResultForDigest_Name(ctx, field) - case "Tags": - return ec.fieldContext_ImgResultForDigest_Tags(ctx, field) + case "RepoName": + return ec.fieldContext_ImageSummary_RepoName(ctx, field) + case "Tag": + return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) + case "LastUpdated": + return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) + case "IsSigned": + return ec.fieldContext_ImageSummary_IsSigned(ctx, field) + case "Size": + return ec.fieldContext_ImageSummary_Size(ctx, field) + case "Platform": + return ec.fieldContext_ImageSummary_Platform(ctx, field) + case "Vendor": + return ec.fieldContext_ImageSummary_Vendor(ctx, field) + case "Score": + return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) + case "Layers": + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) + case "Labels": + return ec.fieldContext_ImageSummary_Labels(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type ImgResultForDigest", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, } defer func() { @@ -3176,9 +2662,9 @@ func (ec *executionContext) _Query_ImageListWithLatestTag(ctx context.Context, f if resTmp == nil { return graphql.Null } - res := resTmp.([]*ImageInfo) + res := resTmp.([]*ImageSummary) fc.Result = res - return ec.marshalOImageInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageInfo(ctx, field.Selections, res) + return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_ImageListWithLatestTag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3189,29 +2675,127 @@ func (ec *executionContext) fieldContext_Query_ImageListWithLatestTag(ctx contex IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "Name": - return ec.fieldContext_ImageInfo_Name(ctx, field) - case "Latest": - return ec.fieldContext_ImageInfo_Latest(ctx, field) + case "RepoName": + return ec.fieldContext_ImageSummary_RepoName(ctx, field) + case "Tag": + return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) case "LastUpdated": - return ec.fieldContext_ImageInfo_LastUpdated(ctx, field) - case "Description": - return ec.fieldContext_ImageInfo_Description(ctx, field) - case "Licenses": - return ec.fieldContext_ImageInfo_Licenses(ctx, field) - case "Vendor": - return ec.fieldContext_ImageInfo_Vendor(ctx, field) + return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) + case "IsSigned": + return ec.fieldContext_ImageSummary_IsSigned(ctx, field) case "Size": - return ec.fieldContext_ImageInfo_Size(ctx, field) + return ec.fieldContext_ImageSummary_Size(ctx, field) + case "Platform": + return ec.fieldContext_ImageSummary_Platform(ctx, field) + case "Vendor": + return ec.fieldContext_ImageSummary_Vendor(ctx, field) + case "Score": + return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) + case "Layers": + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) case "Labels": - return ec.fieldContext_ImageInfo_Labels(ctx, field) + return ec.fieldContext_ImageSummary_Labels(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type ImageInfo", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, } return fc, nil } +func (ec *executionContext) _Query_ImageList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_ImageList(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().ImageList(rctx, fc.Args["repo"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*ImageSummary) + fc.Result = res + return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_ImageList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "RepoName": + return ec.fieldContext_ImageSummary_RepoName(ctx, field) + case "Tag": + return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) + case "LastUpdated": + return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) + case "IsSigned": + return ec.fieldContext_ImageSummary_IsSigned(ctx, field) + case "Size": + return ec.fieldContext_ImageSummary_Size(ctx, field) + case "Platform": + return ec.fieldContext_ImageSummary_Platform(ctx, field) + case "Vendor": + return ec.fieldContext_ImageSummary_Vendor(ctx, field) + case "Score": + return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) + case "Layers": + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) + case "Labels": + return ec.fieldContext_ImageSummary_Labels(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_ImageList_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return + } + return fc, nil +} + func (ec *executionContext) _Query_ExpandedRepoInfo(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_ExpandedRepoInfo(ctx, field) if err != nil { @@ -3233,11 +2817,14 @@ func (ec *executionContext) _Query_ExpandedRepoInfo(ctx context.Context, field g return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } res := resTmp.(*RepoInfo) fc.Result = res - return ec.marshalORepoInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoInfo(ctx, field.Selections, res) + return ec.marshalNRepoInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoInfo(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_ExpandedRepoInfo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3248,8 +2835,8 @@ func (ec *executionContext) fieldContext_Query_ExpandedRepoInfo(ctx context.Cont IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "Manifests": - return ec.fieldContext_RepoInfo_Manifests(ctx, field) + case "Images": + return ec.fieldContext_RepoInfo_Images(ctx, field) case "Summary": return ec.fieldContext_RepoInfo_Summary(ctx, field) } @@ -3291,11 +2878,14 @@ func (ec *executionContext) _Query_GlobalSearch(ctx context.Context, field graph return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } res := resTmp.(*GlobalSearchResult) fc.Result = res - return ec.marshalOGlobalSearchResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐGlobalSearchResult(ctx, field.Selections, res) + return ec.marshalNGlobalSearchResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐGlobalSearchResult(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_GlobalSearch(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3459,8 +3049,8 @@ func (ec *executionContext) fieldContext_Query___schema(ctx context.Context, fie return fc, nil } -func (ec *executionContext) _RepoInfo_Manifests(ctx context.Context, field graphql.CollectedField, obj *RepoInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_RepoInfo_Manifests(ctx, field) +func (ec *executionContext) _RepoInfo_Images(ctx context.Context, field graphql.CollectedField, obj *RepoInfo) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_RepoInfo_Images(ctx, field) if err != nil { return graphql.Null } @@ -3473,7 +3063,7 @@ func (ec *executionContext) _RepoInfo_Manifests(ctx context.Context, field graph }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Manifests, nil + return obj.Images, nil }) if err != nil { ec.Error(ctx, err) @@ -3482,12 +3072,12 @@ func (ec *executionContext) _RepoInfo_Manifests(ctx context.Context, field graph if resTmp == nil { return graphql.Null } - res := resTmp.([]*ManifestInfo) + res := resTmp.([]*ImageSummary) fc.Result = res - return ec.marshalOManifestInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐManifestInfo(ctx, field.Selections, res) + return ec.marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_RepoInfo_Manifests(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_RepoInfo_Images(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "RepoInfo", Field: field, @@ -3495,16 +3085,38 @@ func (ec *executionContext) fieldContext_RepoInfo_Manifests(ctx context.Context, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "Digest": - return ec.fieldContext_ManifestInfo_Digest(ctx, field) + case "RepoName": + return ec.fieldContext_ImageSummary_RepoName(ctx, field) case "Tag": - return ec.fieldContext_ManifestInfo_Tag(ctx, field) + return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) + case "LastUpdated": + return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) case "IsSigned": - return ec.fieldContext_ManifestInfo_IsSigned(ctx, field) + return ec.fieldContext_ImageSummary_IsSigned(ctx, field) + case "Size": + return ec.fieldContext_ImageSummary_Size(ctx, field) + case "Platform": + return ec.fieldContext_ImageSummary_Platform(ctx, field) + case "Vendor": + return ec.fieldContext_ImageSummary_Vendor(ctx, field) + case "Score": + return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) case "Layers": - return ec.fieldContext_ManifestInfo_Layers(ctx, field) + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) + case "Labels": + return ec.fieldContext_ImageSummary_Labels(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type ManifestInfo", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, } return fc, nil @@ -3558,8 +3170,14 @@ func (ec *executionContext) fieldContext_RepoInfo_Summary(ctx context.Context, f return ec.fieldContext_RepoSummary_Vendors(ctx, field) case "Score": return ec.fieldContext_RepoSummary_Score(ctx, field) - case "NewestTag": - return ec.fieldContext_RepoSummary_NewestTag(ctx, field) + case "NewestImage": + return ec.fieldContext_RepoSummary_NewestImage(ctx, field) + case "DownloadCount": + return ec.fieldContext_RepoSummary_DownloadCount(ctx, field) + case "StarCount": + return ec.fieldContext_RepoSummary_StarCount(ctx, field) + case "IsBookmarked": + return ec.fieldContext_RepoSummary_IsBookmarked(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type RepoSummary", field.Name) }, @@ -3819,8 +3437,8 @@ func (ec *executionContext) fieldContext_RepoSummary_Score(ctx context.Context, return fc, nil } -func (ec *executionContext) _RepoSummary_NewestTag(ctx context.Context, field graphql.CollectedField, obj *RepoSummary) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_RepoSummary_NewestTag(ctx, field) +func (ec *executionContext) _RepoSummary_NewestImage(ctx context.Context, field graphql.CollectedField, obj *RepoSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_RepoSummary_NewestImage(ctx, field) if err != nil { return graphql.Null } @@ -3833,7 +3451,7 @@ func (ec *executionContext) _RepoSummary_NewestTag(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.NewestTag, nil + return obj.NewestImage, nil }) if err != nil { ec.Error(ctx, err) @@ -3847,7 +3465,7 @@ func (ec *executionContext) _RepoSummary_NewestTag(ctx context.Context, field gr return ec.marshalOImageSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_RepoSummary_NewestTag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_RepoSummary_NewestImage(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "RepoSummary", Field: field, @@ -3859,6 +3477,10 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestTag(ctx context.Conte return ec.fieldContext_ImageSummary_RepoName(ctx, field) case "Tag": return ec.fieldContext_ImageSummary_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageSummary_Digest(ctx, field) + case "ConfigDigest": + return ec.fieldContext_ImageSummary_ConfigDigest(ctx, field) case "LastUpdated": return ec.fieldContext_ImageSummary_LastUpdated(ctx, field) case "IsSigned": @@ -3871,6 +3493,16 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestTag(ctx context.Conte return ec.fieldContext_ImageSummary_Vendor(ctx, field) case "Score": return ec.fieldContext_ImageSummary_Score(ctx, field) + case "DownloadCount": + return ec.fieldContext_ImageSummary_DownloadCount(ctx, field) + case "Layers": + return ec.fieldContext_ImageSummary_Layers(ctx, field) + case "Description": + return ec.fieldContext_ImageSummary_Description(ctx, field) + case "Licenses": + return ec.fieldContext_ImageSummary_Licenses(ctx, field) + case "Labels": + return ec.fieldContext_ImageSummary_Labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name) }, @@ -3878,8 +3510,8 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestTag(ctx context.Conte return fc, nil } -func (ec *executionContext) _TagInfo_Name(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_TagInfo_Name(ctx, field) +func (ec *executionContext) _RepoSummary_DownloadCount(ctx context.Context, field graphql.CollectedField, obj *RepoSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_RepoSummary_DownloadCount(ctx, field) if err != nil { return graphql.Null } @@ -3892,7 +3524,7 @@ func (ec *executionContext) _TagInfo_Name(ctx context.Context, field graphql.Col }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Name, nil + return obj.DownloadCount, nil }) if err != nil { ec.Error(ctx, err) @@ -3901,26 +3533,26 @@ func (ec *executionContext) _TagInfo_Name(ctx context.Context, field graphql.Col if resTmp == nil { return graphql.Null } - res := resTmp.(*string) + res := resTmp.(*int) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalOInt2ᚖint(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_TagInfo_Name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_RepoSummary_DownloadCount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "TagInfo", + Object: "RepoSummary", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type Int does not have child fields") }, } return fc, nil } -func (ec *executionContext) _TagInfo_Digest(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_TagInfo_Digest(ctx, field) +func (ec *executionContext) _RepoSummary_StarCount(ctx context.Context, field graphql.CollectedField, obj *RepoSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_RepoSummary_StarCount(ctx, field) if err != nil { return graphql.Null } @@ -3933,7 +3565,7 @@ func (ec *executionContext) _TagInfo_Digest(ctx context.Context, field graphql.C }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Digest, nil + return obj.StarCount, nil }) if err != nil { ec.Error(ctx, err) @@ -3942,26 +3574,26 @@ func (ec *executionContext) _TagInfo_Digest(ctx context.Context, field graphql.C if resTmp == nil { return graphql.Null } - res := resTmp.(*string) + res := resTmp.(*int) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalOInt2ᚖint(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_TagInfo_Digest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_RepoSummary_StarCount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "TagInfo", + Object: "RepoSummary", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type Int does not have child fields") }, } return fc, nil } -func (ec *executionContext) _TagInfo_Timestamp(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_TagInfo_Timestamp(ctx, field) +func (ec *executionContext) _RepoSummary_IsBookmarked(ctx context.Context, field graphql.CollectedField, obj *RepoSummary) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_RepoSummary_IsBookmarked(ctx, field) if err != nil { return graphql.Null } @@ -3974,7 +3606,7 @@ func (ec *executionContext) _TagInfo_Timestamp(ctx context.Context, field graphq }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Timestamp, nil + return obj.IsBookmarked, nil }) if err != nil { ec.Error(ctx, err) @@ -3983,19 +3615,19 @@ func (ec *executionContext) _TagInfo_Timestamp(ctx context.Context, field graphq if resTmp == nil { return graphql.Null } - res := resTmp.(*time.Time) + res := resTmp.(*bool) fc.Result = res - return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res) + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_TagInfo_Timestamp(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_RepoSummary_IsBookmarked(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "TagInfo", + Object: "RepoSummary", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Time does not have child fields") + return nil, errors.New("field of type Boolean does not have child fields") }, } return fc, nil @@ -5885,59 +5517,6 @@ func (ec *executionContext) _GlobalSearchResult(ctx context.Context, sel ast.Sel return out } -var imageInfoImplementors = []string{"ImageInfo"} - -func (ec *executionContext) _ImageInfo(ctx context.Context, sel ast.SelectionSet, obj *ImageInfo) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, imageInfoImplementors) - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("ImageInfo") - case "Name": - - out.Values[i] = ec._ImageInfo_Name(ctx, field, obj) - - case "Latest": - - out.Values[i] = ec._ImageInfo_Latest(ctx, field, obj) - - case "LastUpdated": - - out.Values[i] = ec._ImageInfo_LastUpdated(ctx, field, obj) - - case "Description": - - out.Values[i] = ec._ImageInfo_Description(ctx, field, obj) - - case "Licenses": - - out.Values[i] = ec._ImageInfo_Licenses(ctx, field, obj) - - case "Vendor": - - out.Values[i] = ec._ImageInfo_Vendor(ctx, field, obj) - - case "Size": - - out.Values[i] = ec._ImageInfo_Size(ctx, field, obj) - - case "Labels": - - out.Values[i] = ec._ImageInfo_Labels(ctx, field, obj) - - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} - var imageSummaryImplementors = []string{"ImageSummary"} func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.SelectionSet, obj *ImageSummary) graphql.Marshaler { @@ -5956,6 +5535,14 @@ func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.Selection out.Values[i] = ec._ImageSummary_Tag(ctx, field, obj) + case "Digest": + + out.Values[i] = ec._ImageSummary_Digest(ctx, field, obj) + + case "ConfigDigest": + + out.Values[i] = ec._ImageSummary_ConfigDigest(ctx, field, obj) + case "LastUpdated": out.Values[i] = ec._ImageSummary_LastUpdated(ctx, field, obj) @@ -5980,117 +5567,25 @@ func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.Selection out.Values[i] = ec._ImageSummary_Score(ctx, field, obj) - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} + case "DownloadCount": -var imgResultForCVEImplementors = []string{"ImgResultForCVE"} + out.Values[i] = ec._ImageSummary_DownloadCount(ctx, field, obj) -func (ec *executionContext) _ImgResultForCVE(ctx context.Context, sel ast.SelectionSet, obj *ImgResultForCve) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, imgResultForCVEImplementors) - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("ImgResultForCVE") - case "Name": + case "Layers": - out.Values[i] = ec._ImgResultForCVE_Name(ctx, field, obj) + out.Values[i] = ec._ImageSummary_Layers(ctx, field, obj) - case "Tags": + case "Description": - out.Values[i] = ec._ImgResultForCVE_Tags(ctx, field, obj) + out.Values[i] = ec._ImageSummary_Description(ctx, field, obj) - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} + case "Licenses": -var imgResultForDigestImplementors = []string{"ImgResultForDigest"} + out.Values[i] = ec._ImageSummary_Licenses(ctx, field, obj) -func (ec *executionContext) _ImgResultForDigest(ctx context.Context, sel ast.SelectionSet, obj *ImgResultForDigest) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, imgResultForDigestImplementors) - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("ImgResultForDigest") - case "Name": + case "Labels": - out.Values[i] = ec._ImgResultForDigest_Name(ctx, field, obj) - - case "Tags": - - out.Values[i] = ec._ImgResultForDigest_Tags(ctx, field, obj) - - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} - -var imgResultForFixedCVEImplementors = []string{"ImgResultForFixedCVE"} - -func (ec *executionContext) _ImgResultForFixedCVE(ctx context.Context, sel ast.SelectionSet, obj *ImgResultForFixedCve) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, imgResultForFixedCVEImplementors) - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("ImgResultForFixedCVE") - case "Tags": - - out.Values[i] = ec._ImgResultForFixedCVE_Tags(ctx, field, obj) - - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} - -var layerInfoImplementors = []string{"LayerInfo"} - -func (ec *executionContext) _LayerInfo(ctx context.Context, sel ast.SelectionSet, obj *LayerInfo) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, layerInfoImplementors) - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("LayerInfo") - case "Size": - - out.Values[i] = ec._LayerInfo_Size(ctx, field, obj) - - case "Digest": - - out.Values[i] = ec._LayerInfo_Digest(ctx, field, obj) + out.Values[i] = ec._ImageSummary_Labels(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) @@ -6136,43 +5631,6 @@ func (ec *executionContext) _LayerSummary(ctx context.Context, sel ast.Selection return out } -var manifestInfoImplementors = []string{"ManifestInfo"} - -func (ec *executionContext) _ManifestInfo(ctx context.Context, sel ast.SelectionSet, obj *ManifestInfo) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, manifestInfoImplementors) - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("ManifestInfo") - case "Digest": - - out.Values[i] = ec._ManifestInfo_Digest(ctx, field, obj) - - case "Tag": - - out.Values[i] = ec._ManifestInfo_Tag(ctx, field, obj) - - case "IsSigned": - - out.Values[i] = ec._ManifestInfo_IsSigned(ctx, field, obj) - - case "Layers": - - out.Values[i] = ec._ManifestInfo_Layers(ctx, field, obj) - - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} - var osArchImplementors = []string{"OsArch"} func (ec *executionContext) _OsArch(ctx context.Context, sel ast.SelectionSet, obj *OsArch) graphql.Marshaler { @@ -6264,6 +5722,9 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_CVEListForImage(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } return res } @@ -6351,6 +5812,26 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) } + out.Concurrently(i, func() graphql.Marshaler { + return rrm(innerCtx) + }) + case "ImageList": + field := field + + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_ImageList(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) + } + out.Concurrently(i, func() graphql.Marshaler { return rrm(innerCtx) }) @@ -6364,6 +5845,9 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_ExpandedRepoInfo(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } return res } @@ -6384,6 +5868,9 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_GlobalSearch(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } return res } @@ -6427,9 +5914,9 @@ func (ec *executionContext) _RepoInfo(ctx context.Context, sel ast.SelectionSet, switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("RepoInfo") - case "Manifests": + case "Images": - out.Values[i] = ec._RepoInfo_Manifests(ctx, field, obj) + out.Values[i] = ec._RepoInfo_Images(ctx, field, obj) case "Summary": @@ -6480,42 +5967,21 @@ func (ec *executionContext) _RepoSummary(ctx context.Context, sel ast.SelectionS out.Values[i] = ec._RepoSummary_Score(ctx, field, obj) - case "NewestTag": + case "NewestImage": - out.Values[i] = ec._RepoSummary_NewestTag(ctx, field, obj) + out.Values[i] = ec._RepoSummary_NewestImage(ctx, field, obj) - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch() - if invalids > 0 { - return graphql.Null - } - return out -} + case "DownloadCount": -var tagInfoImplementors = []string{"TagInfo"} + out.Values[i] = ec._RepoSummary_DownloadCount(ctx, field, obj) -func (ec *executionContext) _TagInfo(ctx context.Context, sel ast.SelectionSet, obj *TagInfo) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, tagInfoImplementors) - out := graphql.NewFieldSet(fields) - var invalids uint32 - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("TagInfo") - case "Name": + case "StarCount": - out.Values[i] = ec._TagInfo_Name(ctx, field, obj) + out.Values[i] = ec._RepoSummary_StarCount(ctx, field, obj) - case "Digest": + case "IsBookmarked": - out.Values[i] = ec._TagInfo_Digest(ctx, field, obj) - - case "Timestamp": - - out.Values[i] = ec._TagInfo_Timestamp(ctx, field, obj) + out.Values[i] = ec._RepoSummary_IsBookmarked(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) @@ -6861,6 +6327,58 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) marshalNCVEResultForImage2zotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEResultForImage(ctx context.Context, sel ast.SelectionSet, v CVEResultForImage) graphql.Marshaler { + return ec._CVEResultForImage(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCVEResultForImage2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEResultForImage(ctx context.Context, sel ast.SelectionSet, v *CVEResultForImage) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._CVEResultForImage(ctx, sel, v) +} + +func (ec *executionContext) marshalNGlobalSearchResult2zotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐGlobalSearchResult(ctx context.Context, sel ast.SelectionSet, v GlobalSearchResult) graphql.Marshaler { + return ec._GlobalSearchResult(ctx, sel, &v) +} + +func (ec *executionContext) marshalNGlobalSearchResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐGlobalSearchResult(ctx context.Context, sel ast.SelectionSet, v *GlobalSearchResult) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._GlobalSearchResult(ctx, sel, v) +} + +func (ec *executionContext) marshalNImageSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx context.Context, sel ast.SelectionSet, v *ImageSummary) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._ImageSummary(ctx, sel, v) +} + +func (ec *executionContext) marshalNRepoInfo2zotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoInfo(ctx context.Context, sel ast.SelectionSet, v RepoInfo) graphql.Marshaler { + return ec._RepoInfo(ctx, sel, &v) +} + +func (ec *executionContext) marshalNRepoInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoInfo(ctx context.Context, sel ast.SelectionSet, v *RepoInfo) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._RepoInfo(ctx, sel, v) +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -7203,68 +6721,6 @@ func (ec *executionContext) marshalOCVE2ᚖzotregistryᚗioᚋzotᚋpkgᚋextens return ec._CVE(ctx, sel, v) } -func (ec *executionContext) marshalOCVEResultForImage2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEResultForImage(ctx context.Context, sel ast.SelectionSet, v *CVEResultForImage) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._CVEResultForImage(ctx, sel, v) -} - -func (ec *executionContext) marshalOGlobalSearchResult2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐGlobalSearchResult(ctx context.Context, sel ast.SelectionSet, v *GlobalSearchResult) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._GlobalSearchResult(ctx, sel, v) -} - -func (ec *executionContext) marshalOImageInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageInfo(ctx context.Context, sel ast.SelectionSet, v []*ImageInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalOImageInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageInfo(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - - return ret -} - -func (ec *executionContext) marshalOImageInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageInfo(ctx context.Context, sel ast.SelectionSet, v *ImageInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._ImageInfo(ctx, sel, v) -} - func (ec *executionContext) marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx context.Context, sel ast.SelectionSet, v []*ImageSummary) graphql.Marshaler { if v == nil { return graphql.Null @@ -7306,6 +6762,53 @@ func (ec *executionContext) marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋ return ret } +func (ec *executionContext) marshalOImageSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummaryᚄ(ctx context.Context, sel ast.SelectionSet, v []*ImageSummary) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNImageSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalOImageSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx context.Context, sel ast.SelectionSet, v *ImageSummary) graphql.Marshaler { if v == nil { return graphql.Null @@ -7313,109 +6816,6 @@ func (ec *executionContext) marshalOImageSummary2ᚖzotregistryᚗioᚋzotᚋpkg return ec._ImageSummary(ctx, sel, v) } -func (ec *executionContext) marshalOImgResultForCVE2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForCve(ctx context.Context, sel ast.SelectionSet, v []*ImgResultForCve) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalOImgResultForCVE2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForCve(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - - return ret -} - -func (ec *executionContext) marshalOImgResultForCVE2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForCve(ctx context.Context, sel ast.SelectionSet, v *ImgResultForCve) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._ImgResultForCVE(ctx, sel, v) -} - -func (ec *executionContext) marshalOImgResultForDigest2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForDigest(ctx context.Context, sel ast.SelectionSet, v []*ImgResultForDigest) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalOImgResultForDigest2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForDigest(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - - return ret -} - -func (ec *executionContext) marshalOImgResultForDigest2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForDigest(ctx context.Context, sel ast.SelectionSet, v *ImgResultForDigest) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._ImgResultForDigest(ctx, sel, v) -} - -func (ec *executionContext) marshalOImgResultForFixedCVE2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImgResultForFixedCve(ctx context.Context, sel ast.SelectionSet, v *ImgResultForFixedCve) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._ImgResultForFixedCVE(ctx, sel, v) -} - func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v interface{}) (*int, error) { if v == nil { return nil, nil @@ -7432,54 +6832,6 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele return res } -func (ec *executionContext) marshalOLayerInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐLayerInfo(ctx context.Context, sel ast.SelectionSet, v []*LayerInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalOLayerInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐLayerInfo(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - - return ret -} - -func (ec *executionContext) marshalOLayerInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐLayerInfo(ctx context.Context, sel ast.SelectionSet, v *LayerInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._LayerInfo(ctx, sel, v) -} - func (ec *executionContext) marshalOLayerSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐLayerSummary(ctx context.Context, sel ast.SelectionSet, v []*LayerSummary) graphql.Marshaler { if v == nil { return graphql.Null @@ -7528,54 +6880,6 @@ func (ec *executionContext) marshalOLayerSummary2ᚖzotregistryᚗioᚋzotᚋpkg return ec._LayerSummary(ctx, sel, v) } -func (ec *executionContext) marshalOManifestInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐManifestInfo(ctx context.Context, sel ast.SelectionSet, v []*ManifestInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalOManifestInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐManifestInfo(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - - return ret -} - -func (ec *executionContext) marshalOManifestInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐManifestInfo(ctx context.Context, sel ast.SelectionSet, v *ManifestInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._ManifestInfo(ctx, sel, v) -} - func (ec *executionContext) marshalOOsArch2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐOsArch(ctx context.Context, sel ast.SelectionSet, v []*OsArch) graphql.Marshaler { if v == nil { return graphql.Null @@ -7672,13 +6976,6 @@ func (ec *executionContext) marshalOPackageInfo2ᚖzotregistryᚗioᚋzotᚋpkg return ec._PackageInfo(ctx, sel, v) } -func (ec *executionContext) marshalORepoInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoInfo(ctx context.Context, sel ast.SelectionSet, v *RepoInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._RepoInfo(ctx, sel, v) -} - func (ec *executionContext) marshalORepoSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐRepoSummary(ctx context.Context, sel ast.SelectionSet, v []*RepoSummary) graphql.Marshaler { if v == nil { return graphql.Null @@ -7775,54 +7072,6 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as return res } -func (ec *executionContext) marshalOTagInfo2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐTagInfo(ctx context.Context, sel ast.SelectionSet, v []*TagInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalOTagInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐTagInfo(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - - } - wg.Wait() - - return ret -} - -func (ec *executionContext) marshalOTagInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐTagInfo(ctx context.Context, sel ast.SelectionSet, v *TagInfo) graphql.Marshaler { - if v == nil { - return graphql.Null - } - return ec._TagInfo(ctx, sel, v) -} - func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v interface{}) (*time.Time, error) { if v == nil { return nil, nil diff --git a/pkg/extensions/search/gql_generated/models_gen.go b/pkg/extensions/search/gql_generated/models_gen.go index d0a4ab35..0c09c3a2 100644 --- a/pkg/extensions/search/gql_generated/models_gen.go +++ b/pkg/extensions/search/gql_generated/models_gen.go @@ -25,45 +25,22 @@ type GlobalSearchResult struct { Layers []*LayerSummary `json:"Layers"` } -type ImageInfo struct { - Name *string `json:"Name"` - Latest *string `json:"Latest"` - LastUpdated *time.Time `json:"LastUpdated"` - Description *string `json:"Description"` - Licenses *string `json:"Licenses"` - Vendor *string `json:"Vendor"` - Size *string `json:"Size"` - Labels *string `json:"Labels"` -} - type ImageSummary struct { - RepoName *string `json:"RepoName"` - Tag *string `json:"Tag"` - LastUpdated *time.Time `json:"LastUpdated"` - IsSigned *bool `json:"IsSigned"` - Size *string `json:"Size"` - Platform *OsArch `json:"Platform"` - Vendor *string `json:"Vendor"` - Score *int `json:"Score"` -} - -type ImgResultForCve struct { - Name *string `json:"Name"` - Tags []*string `json:"Tags"` -} - -type ImgResultForDigest struct { - Name *string `json:"Name"` - Tags []*string `json:"Tags"` -} - -type ImgResultForFixedCve struct { - Tags []*TagInfo `json:"Tags"` -} - -type LayerInfo struct { - Size *string `json:"Size"` - Digest *string `json:"Digest"` + RepoName *string `json:"RepoName"` + Tag *string `json:"Tag"` + Digest *string `json:"Digest"` + ConfigDigest *string `json:"ConfigDigest"` + LastUpdated *time.Time `json:"LastUpdated"` + IsSigned *bool `json:"IsSigned"` + Size *string `json:"Size"` + Platform *OsArch `json:"Platform"` + Vendor *string `json:"Vendor"` + Score *int `json:"Score"` + DownloadCount *int `json:"DownloadCount"` + Layers []*LayerSummary `json:"Layers"` + Description *string `json:"Description"` + Licenses *string `json:"Licenses"` + Labels *string `json:"Labels"` } type LayerSummary struct { @@ -72,13 +49,6 @@ type LayerSummary struct { Score *int `json:"Score"` } -type ManifestInfo struct { - Digest *string `json:"Digest"` - Tag *string `json:"Tag"` - IsSigned *bool `json:"IsSigned"` - Layers []*LayerInfo `json:"Layers"` -} - type OsArch struct { Os *string `json:"Os"` Arch *string `json:"Arch"` @@ -91,22 +61,19 @@ type PackageInfo struct { } type RepoInfo struct { - Manifests []*ManifestInfo `json:"Manifests"` - Summary *RepoSummary `json:"Summary"` + Images []*ImageSummary `json:"Images"` + Summary *RepoSummary `json:"Summary"` } type RepoSummary struct { - Name *string `json:"Name"` - LastUpdated *time.Time `json:"LastUpdated"` - Size *string `json:"Size"` - Platforms []*OsArch `json:"Platforms"` - Vendors []*string `json:"Vendors"` - Score *int `json:"Score"` - NewestTag *ImageSummary `json:"NewestTag"` -} - -type TagInfo struct { - Name *string `json:"Name"` - Digest *string `json:"Digest"` - Timestamp *time.Time `json:"Timestamp"` + Name *string `json:"Name"` + LastUpdated *time.Time `json:"LastUpdated"` + Size *string `json:"Size"` + Platforms []*OsArch `json:"Platforms"` + Vendors []*string `json:"Vendors"` + Score *int `json:"Score"` + NewestImage *ImageSummary `json:"NewestImage"` + DownloadCount *int `json:"DownloadCount"` + StarCount *int `json:"StarCount"` + IsBookmarked *bool `json:"IsBookmarked"` } diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index a5b37a44..22609ab1 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" + v1 "github.com/google/go-containerregistry/pkg/v1" godigest "github.com/opencontainers/go-digest" "zotregistry.io/zot/pkg/log" // nolint: gci @@ -60,60 +61,56 @@ func GetResolverConfig(log log.Logger, storeController storage.StoreController, func (r *queryResolver) getImageListForCVE(repoList []string, cvid string, imgStore storage.ImageStore, trivyCtx *cveinfo.TrivyCtx, -) ([]*gql_generated.ImgResultForCve, error) { - cveResult := []*gql_generated.ImgResultForCve{} +) ([]*gql_generated.ImageSummary, error) { + cveResult := []*gql_generated.ImageSummary{} for _, repo := range repoList { r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo") - name := repo - - tags, err := r.cveInfo.GetImageListForCVE(repo, cvid, imgStore, trivyCtx) + imageListByCVE, err := r.cveInfo.GetImageListForCVE(repo, cvid, imgStore, trivyCtx) if err != nil { r.log.Error().Err(err).Msg("error getting tag") return cveResult, err } - if len(tags) != 0 { - cveResult = append(cveResult, &gql_generated.ImgResultForCve{Name: &name, Tags: tags}) + for _, imageByCVE := range imageListByCVE { + cveResult = append( + cveResult, + buildImageInfo(repo, imageByCVE.Tag, imageByCVE.Digest, imageByCVE.Manifest), + ) } } return cveResult, nil } -func (r *queryResolver) getImageListForDigest(repoList []string, - digest string, -) ([]*gql_generated.ImgResultForDigest, error) { - imgResultForDigest := []*gql_generated.ImgResultForDigest{} +func (r *queryResolver) getImageListForDigest(repoList []string, digest string) ([]*gql_generated.ImageSummary, error) { + imgResultForDigest := []*gql_generated.ImageSummary{} var errResult error for _, repo := range repoList { r.log.Info().Str("repo", repo).Msg("filtering list of tags in image repo by digest") - tags, err := r.digestInfo.GetImageTagsByDigest(repo, digest) + imgTags, err := r.digestInfo.GetImageTagsByDigest(repo, digest) if err != nil { r.log.Error().Err(err).Msg("unable to get filtered list of image tags") - errResult = err - - continue + return []*gql_generated.ImageSummary{}, err } - if len(tags) != 0 { - name := repo - - imgResultForDigest = append(imgResultForDigest, &gql_generated.ImgResultForDigest{Name: &name, Tags: tags}) + for _, imageInfo := range imgTags { + imageInfo := buildImageInfo(repo, imageInfo.Tag, imageInfo.Digest, imageInfo.Manifest) + imgResultForDigest = append(imgResultForDigest, imageInfo) } } return imgResultForDigest, errResult } -func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*gql_generated.ImageInfo, error) { - results := make([]*gql_generated.ImageInfo, 0) +func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*gql_generated.ImageSummary, error) { + results := make([]*gql_generated.ImageSummary, 0) repoList, err := store.GetRepositories() if err != nil { @@ -167,7 +164,6 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]* labels := imageConfig.Config.Labels // Read Description - desc := common.GetDescription(labels) // Read licenses @@ -179,8 +175,8 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]* // Read categories categories := common.GetCategories(labels) - results = append(results, &gql_generated.ImageInfo{ - Name: &name, Latest: &latestTag.Name, + results = append(results, &gql_generated.ImageSummary{ + RepoName: &name, Tag: &latestTag.Name, Description: &desc, Licenses: &license, Vendor: &vendor, Labels: &categories, Size: &size, LastUpdated: &latestTag.Timestamp, }) @@ -336,7 +332,7 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils Platforms: repoPlatforms, Vendors: repoVendors, Score: &index, - NewestTag: &lastUpdatedImageSummary, + NewestImage: &lastUpdatedImageSummary, }) } } @@ -382,15 +378,94 @@ func calculateImageMatchingScore(artefactName string, index int, matchesTag bool return score } -func getGraphqlCompatibleTags(fixedTags []common.TagInfo) []*gql_generated.TagInfo { - finalTagList := make([]*gql_generated.TagInfo, 0) +func (r *queryResolver) getImageList(store storage.ImageStore, imageName string) ( + []*gql_generated.ImageSummary, error, +) { + results := make([]*gql_generated.ImageSummary, 0) - for _, tag := range fixedTags { - fixTag := tag + repoList, err := store.GetRepositories() + if err != nil { + r.log.Error().Err(err).Msg("extension api: error extracting repositories list") - finalTagList = append(finalTagList, - &gql_generated.TagInfo{Name: &fixTag.Name, Digest: &fixTag.Digest, Timestamp: &fixTag.Timestamp}) + return results, err } - return finalTagList + layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log) + + for _, repo := range repoList { + if (imageName != "" && repo == imageName) || imageName == "" { + tagsInfo, err := layoutUtils.GetImageTagsWithTimestamp(repo) + if err != nil { + r.log.Error().Err(err).Msg("extension api: error getting tag timestamp info") + + return results, nil + } + + if len(tagsInfo) == 0 { + r.log.Info().Str("no tagsinfo found for repo", repo).Msg(" continuing traversing") + + continue + } + + for i := range tagsInfo { + // using a loop variable called tag would be reassigned after each iteration, using the same memory address + // directly access the value at the current index in the slice as ImageInfo requires pointers to tag fields + tag := tagsInfo[i] + + digest := godigest.Digest(tag.Digest) + + manifest, err := layoutUtils.GetImageBlobManifest(repo, digest) + if err != nil { + r.log.Error().Err(err).Msg("extension api: error reading manifest") + + return results, err + } + + imageInfo := buildImageInfo(repo, tag.Name, digest, manifest) + + results = append(results, imageInfo) + } + } + } + + if len(results) == 0 { + r.log.Info().Msg("no repositories found") + } + + return results, nil +} + +func buildImageInfo(repo string, tag string, tagDigest godigest.Digest, + manifest v1.Manifest, +) *gql_generated.ImageSummary { + layers := []*gql_generated.LayerSummary{} + size := int64(0) + + for _, entry := range manifest.Layers { + size += entry.Size + digest := entry.Digest.Hex + layerSize := strconv.FormatInt(entry.Size, 10) + + layers = append( + layers, + &gql_generated.LayerSummary{ + Size: &layerSize, + Digest: &digest, + }, + ) + } + + formattedSize := strconv.FormatInt(size, 10) + formattedTagDigest := tagDigest.Hex() + + imageInfo := &gql_generated.ImageSummary{ + RepoName: &repo, + Tag: &tag, + Digest: &formattedTagDigest, + ConfigDigest: &manifest.Config.Digest.Hex, + Size: &formattedSize, + Layers: layers, + } + + return imageInfo } diff --git a/pkg/extensions/search/resolver_test.go b/pkg/extensions/search/resolver_test.go index b3422861..38e237fd 100644 --- a/pkg/extensions/search/resolver_test.go +++ b/pkg/extensions/search/resolver_test.go @@ -139,7 +139,7 @@ func TestGlobalSearch(t *testing.T) { mockOlum := mocks.OciLayoutUtilsMock{ GetExpandedRepoInfoFn: func(name string) (common.RepoInfo, error) { return common.RepoInfo{ - Manifests: []common.Manifest{ + Images: []common.Image{ { Tag: "latest", Layers: []common.Layer{ diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index 8657535c..91ed1c71 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -1,123 +1,91 @@ scalar Time type CVEResultForImage { - Tag: String - CVEList: [CVE] + Tag: String + CVEList: [CVE] } type CVE { - Id: String - Title: String - Description: String - Severity: String - PackageList: [PackageInfo] + Id: String + Title: String + Description: String + Severity: String + PackageList: [PackageInfo] } type PackageInfo { - Name: String - InstalledVersion: String - FixedVersion: String -} - -type ImgResultForCVE { - Name: String - Tags: [String] -} - -type ImgResultForFixedCVE { - Tags: [TagInfo] -} - -type ImgResultForDigest { - Name: String - Tags: [String] -} - -type TagInfo { - Name: String - Digest: String - Timestamp: Time -} - -type ImageInfo { - Name: String - Latest: String - LastUpdated: Time - Description: String - Licenses: String - Vendor: String - Size: String - Labels: String + Name: String + InstalledVersion: String + FixedVersion: String } type RepoInfo { - Manifests: [ManifestInfo] - Summary: RepoSummary -} - -type ManifestInfo { - Digest: String - Tag: String - IsSigned: Boolean - Layers: [LayerInfo] -} - -type LayerInfo { - Size: String # Int64 is not supported. - Digest: String + Images: [ImageSummary] + Summary: RepoSummary } # Search results in all repos/images/layers # There will be other more structures for more detailed information type GlobalSearchResult { - Images: [ImageSummary] - Repos: [RepoSummary] - Layers: [LayerSummary] + Images: [ImageSummary] + Repos: [RepoSummary] + Layers: [LayerSummary] } # Brief on a specific image to be used in queries returning a list of images # We define an image as a pairing or a repo and a tag belonging to that repo type ImageSummary { - RepoName: String - Tag: String - LastUpdated: Time - IsSigned: Boolean - Size: String - Platform: OsArch - Vendor: String - Score: Int + RepoName: String + Tag: String + Digest: String + ConfigDigest: String + LastUpdated: Time + IsSigned: Boolean + Size: String + Platform: OsArch + Vendor: String + Score: Int + DownloadCount: Int + Layers: [LayerSummary] + Description: String + Licenses: String + Labels: String } # Brief on a specific repo to be used in queries returning a list of repos type RepoSummary { - Name: String - LastUpdated: Time - Size: String - Platforms: [OsArch] - Vendors: [String] - Score: Int - NewestTag: ImageSummary + Name: String + LastUpdated: Time + Size: String + Platforms: [OsArch] + Vendors: [String] + Score: Int + NewestImage: ImageSummary + DownloadCount: Int + StarCount: Int + IsBookmarked: Boolean } # Currently the same as LayerInfo, we can refactor later # For detailed information on the layer a ImageListForDigest call can be made type LayerSummary { - Size: String # Int64 is not supported. - Digest: String - Score: Int + Size: String # Int64 is not supported. + Digest: String + Score: Int } type OsArch { - Os: String - Arch: String + Os: String + Arch: String } type Query { - CVEListForImage(image: String!) :CVEResultForImage - ImageListForCVE(id: String!) :[ImgResultForCVE] - ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE - ImageListForDigest(id: String!) :[ImgResultForDigest] - ImageListWithLatestTag:[ImageInfo] - ExpandedRepoInfo(repo: String!):RepoInfo - GlobalSearch(query: String!): GlobalSearchResult + CVEListForImage(image: String!): CVEResultForImage! + ImageListForCVE(id: String!): [ImageSummary!] + ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!] + ImageListForDigest(id: String!): [ImageSummary!] + ImageListWithLatestTag: [ImageSummary!] + ImageList(repo: String!): [ImageSummary!] + ExpandedRepoInfo(repo: String!): RepoInfo! + GlobalSearch(query: String!): GlobalSearchResult! } diff --git a/pkg/extensions/search/schema.resolvers.go b/pkg/extensions/search/schema.resolvers.go index 9cd4c8ce..5fc04cac 100644 --- a/pkg/extensions/search/schema.resolvers.go +++ b/pkg/extensions/search/schema.resolvers.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + godigest "github.com/opencontainers/go-digest" "zotregistry.io/zot/pkg/extensions/search/common" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" "zotregistry.io/zot/pkg/extensions/search/gql_generated" @@ -101,8 +102,8 @@ func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*gql } // ImageListForCve is the resolver for the ImageListForCVE field. -func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_generated.ImgResultForCve, error) { - finalCveResult := []*gql_generated.ImgResultForCve{} +func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) { + finalCveResult := []*gql_generated.ImageSummary{} r.log.Info().Msg("extracting repositories") @@ -154,8 +155,8 @@ func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_ } // ImageListWithCVEFixed is the resolver for the ImageListWithCVEFixed field. -func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) (*gql_generated.ImgResultForFixedCve, error) { - imgResultForFixedCVE := &gql_generated.ImgResultForFixedCve{} +func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*gql_generated.ImageSummary, error) { + tagListForCVE := []*gql_generated.ImageSummary{} r.log.Info().Str("image", image).Msg("extracting list of tags available in image") @@ -163,7 +164,7 @@ func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, im if err != nil { r.log.Error().Err(err).Msg("unable to read image tags") - return imgResultForFixedCVE, err + return tagListForCVE, err } infectedTags := make([]common.TagInfo, 0) @@ -213,28 +214,34 @@ func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, im } } - var finalTagList []*gql_generated.TagInfo - if len(infectedTags) != 0 { r.log.Info().Msg("comparing fixed tags timestamp") - fixedTags := common.GetFixedTags(tagsInfo, infectedTags) - - finalTagList = getGraphqlCompatibleTags(fixedTags) + tagsInfo = common.GetFixedTags(tagsInfo, infectedTags) } else { r.log.Info().Str("image", image).Str("cve-id", id).Msg("image does not contain any tag that have given cve") - - finalTagList = getGraphqlCompatibleTags(tagsInfo) } - imgResultForFixedCVE = &gql_generated.ImgResultForFixedCve{Tags: finalTagList} + for _, tag := range tagsInfo { + digest := godigest.Digest(tag.Digest) - return imgResultForFixedCVE, nil + manifest, err := r.cveInfo.LayoutUtils.GetImageBlobManifest(image, digest) + if err != nil { + r.log.Error().Err(err).Msg("extension api: error reading manifest") + + return []*gql_generated.ImageSummary{}, err + } + + imageInfo := buildImageInfo(image, tag.Name, digest, manifest) + tagListForCVE = append(tagListForCVE, imageInfo) + } + + return tagListForCVE, nil } // ImageListForDigest is the resolver for the ImageListForDigest field. -func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*gql_generated.ImgResultForDigest, error) { - imgResultForDigest := []*gql_generated.ImgResultForDigest{} +func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) { + imgResultForDigest := []*gql_generated.ImageSummary{} r.log.Info().Msg("extracting repositories") @@ -281,10 +288,10 @@ func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*g } // ImageListWithLatestTag is the resolver for the ImageListWithLatestTag field. -func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*gql_generated.ImageInfo, error) { +func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*gql_generated.ImageSummary, error) { r.log.Info().Msg("extension api: finding image list") - imageList := make([]*gql_generated.ImageInfo, 0) + imageList := make([]*gql_generated.ImageSummary, 0) defaultStore := r.storeController.DefaultStore @@ -317,6 +324,43 @@ func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*gql_gene return imageList, nil } +// ImageList is the resolver for the ImageList field. +func (r *queryResolver) ImageList(ctx context.Context, repo string) ([]*gql_generated.ImageSummary, error) { + r.log.Info().Msg("extension api: getting a list of all images") + + imageList := make([]*gql_generated.ImageSummary, 0) + + defaultStore := r.storeController.DefaultStore + + dsImageList, err := r.getImageList(defaultStore, repo) + if err != nil { + r.log.Error().Err(err).Msg("extension api: error extracting default store image list") + + return imageList, err + } + + if len(dsImageList) != 0 { + imageList = append(imageList, dsImageList...) + } + + subStore := r.storeController.SubStore + + for _, store := range subStore { + ssImageList, err := r.getImageList(store, repo) + if err != nil { + r.log.Error().Err(err).Msg("extension api: error extracting substore image list") + + return imageList, err + } + + if len(ssImageList) != 0 { + imageList = append(imageList, ssImageList...) + } + } + + return imageList, nil +} + // ExpandedRepoInfo is the resolver for the ExpandedRepoInfo field. func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql_generated.RepoInfo, error) { olu := common.NewBaseOciLayoutUtils(r.storeController, r.log) @@ -331,7 +375,7 @@ func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql // repos type is of common deep copy this to search repoInfo := &gql_generated.RepoInfo{} - manifests := make([]*gql_generated.ManifestInfo, 0) + images := make([]*gql_generated.ImageSummary, 0) summary := &gql_generated.RepoSummary{} @@ -358,34 +402,34 @@ func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql score := -1 // score not relevant for this query summary.Score = &score - for _, manifest := range origRepoInfo.Manifests { - tag := manifest.Tag + for _, image := range origRepoInfo.Images { + tag := image.Tag - digest := manifest.Digest + digest := image.Digest - isSigned := manifest.IsSigned + isSigned := image.IsSigned - manifestInfo := &gql_generated.ManifestInfo{Tag: &tag, Digest: &digest, IsSigned: &isSigned} + imageSummary := &gql_generated.ImageSummary{Tag: &tag, Digest: &digest, IsSigned: &isSigned} - layers := make([]*gql_generated.LayerInfo, 0) + layers := make([]*gql_generated.LayerSummary, 0) - for _, l := range manifest.Layers { + for _, l := range image.Layers { size := l.Size digest := l.Digest - layerInfo := &gql_generated.LayerInfo{Digest: &digest, Size: &size} + layerInfo := &gql_generated.LayerSummary{Digest: &digest, Size: &size} layers = append(layers, layerInfo) } - manifestInfo.Layers = layers + imageSummary.Layers = layers - manifests = append(manifests, manifestInfo) + images = append(images, imageSummary) } repoInfo.Summary = summary - repoInfo.Manifests = manifests + repoInfo.Images = images return repoInfo, nil }