From ab9a20c1ae5fa078def11330d3c66566a384f8f7 Mon Sep 17 00:00:00 2001 From: Roxana Nemulescu Date: Wed, 19 Jan 2022 17:57:10 +0200 Subject: [PATCH] Add GraphQL API for getting the information necessary to list images in the zot cli without download manifests. If this GraphQL API is available, try that first, else fallback to the slowpath. Signed-off-by: Roxana Nemulescu --- golangcilint.yaml | 1 + pkg/cli/client.go | 17 +- pkg/cli/client_test.go | 11 + pkg/cli/cve_cmd.go | 84 +- pkg/cli/cve_cmd_test.go | 565 +++- pkg/cli/image_cmd.go | 10 +- pkg/cli/image_cmd_test.go | 693 ++++- pkg/cli/searcher.go | 262 +- pkg/cli/service.go | 440 ++- pkg/extensions/search/common/common.go | 9 - pkg/extensions/search/common/common_test.go | 70 +- pkg/extensions/search/common/oci_layout.go | 12 +- pkg/extensions/search/cve/cve.go | 41 +- pkg/extensions/search/cve/cve_test.go | 53 +- pkg/extensions/search/cve/models.go | 8 + pkg/extensions/search/digest/digest.go | 20 +- pkg/extensions/search/digest/digest_test.go | 117 +- .../search/gql_generated/generated.go | 2385 ++++++----------- .../search/gql_generated/models_gen.go | 87 +- pkg/extensions/search/resolver.go | 139 +- pkg/extensions/search/resolver_test.go | 2 +- pkg/extensions/search/schema.graphql | 138 +- pkg/extensions/search/schema.resolvers.go | 104 +- 23 files changed, 3080 insertions(+), 2188 deletions(-) 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 }