mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
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 <roxana.nemulescu@gmail.com>
This commit is contained in:
parent
eb77307b63
commit
ab9a20c1ae
23 changed files with 3080 additions and 2188 deletions
|
@ -50,6 +50,7 @@ linters-settings:
|
||||||
- github.com/containers/image/v5
|
- github.com/containers/image/v5
|
||||||
- github.com/opencontainers/image-spec
|
- github.com/opencontainers/image-spec
|
||||||
- github.com/open-policy-agent/opa
|
- github.com/open-policy-agent/opa
|
||||||
|
- github.com/vektah/gqlparser/v2
|
||||||
- go.opentelemetry.io/otel
|
- go.opentelemetry.io/otel
|
||||||
- go.opentelemetry.io/otel/exporters/otlp
|
- go.opentelemetry.io/otel/exporters/otlp
|
||||||
- go.opentelemetry.io/otel/metric
|
- go.opentelemetry.io/otel/metric
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -283,16 +284,12 @@ func (p *requestsPool) doJob(ctx context.Context, job *manifestJob) {
|
||||||
|
|
||||||
image := &imageStruct{}
|
image := &imageStruct{}
|
||||||
image.verbose = *job.config.verbose
|
image.verbose = *job.config.verbose
|
||||||
image.Name = job.imageName
|
image.RepoName = job.imageName
|
||||||
image.Tags = []tags{
|
image.Tag = job.tagName
|
||||||
{
|
image.Digest = digest
|
||||||
Name: job.tagName,
|
image.Size = strconv.Itoa(int(size))
|
||||||
Digest: digest,
|
image.ConfigDigest = configDigest
|
||||||
Size: size,
|
image.Layers = layers
|
||||||
ConfigDigest: configDigest,
|
|
||||||
Layers: layers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := image.string(*job.config.outputFormat)
|
str, err := image.string(*job.config.outputFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/api"
|
"zotregistry.io/zot/pkg/api"
|
||||||
"zotregistry.io/zot/pkg/api/config"
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
"zotregistry.io/zot/pkg/api/constants"
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
|
extConf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,6 +70,11 @@ func TestTLSWithAuth(t *testing.T) {
|
||||||
CACert: CACert,
|
CACert: CACert,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enable := true
|
||||||
|
conf.Extensions = &extConf.ExtensionConfig{
|
||||||
|
Search: &extConf.SearchConfig{Enable: &enable},
|
||||||
|
}
|
||||||
|
|
||||||
ctlr := api.NewController(conf)
|
ctlr := api.NewController(conf)
|
||||||
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -161,6 +167,11 @@ func TestTLSWithoutAuth(t *testing.T) {
|
||||||
CACert: CACert,
|
CACert: CACert,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enable := true
|
||||||
|
conf.Extensions = &extConf.ExtensionConfig{
|
||||||
|
Search: &extConf.SearchConfig{Enable: &enable},
|
||||||
|
}
|
||||||
|
|
||||||
ctlr := api.NewController(conf)
|
ctlr := api.NewController(conf)
|
||||||
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -4,13 +4,18 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/resty.v1"
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zotErrors "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCveCommand(searchService SearchService) *cobra.Command {
|
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 := 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
|
verbose = false
|
||||||
|
|
||||||
|
@ -112,7 +118,7 @@ func NewCveCommand(searchService SearchService) *cobra.Command {
|
||||||
|
|
||||||
func setupCveFlags(cveCmd *cobra.Command, variables cveFlagVariables) {
|
func setupCveFlags(cveCmd *cobra.Command, variables cveFlagVariables) {
|
||||||
variables.searchCveParams["imageName"] = cveCmd.Flags().StringP("image", "I", "", "List CVEs by IMAGENAME[:TAG]")
|
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().StringVar(variables.servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
||||||
cveCmd.Flags().StringVarP(variables.user, "user", "u", "", `User Credentials of `+
|
cveCmd.Flags().StringVarP(variables.user, "user", "u", "", `User Credentials of `+
|
||||||
|
@ -131,8 +137,80 @@ type cveFlagVariables struct {
|
||||||
fixedFlag *bool
|
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 {
|
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)
|
found, err := searcher.search(searchConfig)
|
||||||
if found {
|
if found {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zotErrors "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api"
|
"zotregistry.io/zot/pkg/api"
|
||||||
|
@ -51,6 +52,7 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test CVE no url", t, func() {
|
Convey("Test CVE no url", t, func() {
|
||||||
args := []string{"cvetest", "-i", "cveIdRandom"}
|
args := []string{"cvetest", "-i", "cveIdRandom"}
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
||||||
|
@ -136,10 +138,8 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test CVE url from config", t, func() {
|
Convey("Test CVE url from config", t, func() {
|
||||||
args := []string{"cvetest", "--image", "dummyImageName:tag"}
|
args := []string{"cvetest", "--image", "dummyImageName:tag"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","url":"https://test-url.com","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","url":"https://test-url.com","showspinner":false}]}`)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewCveCommand(new(mockService))
|
cmd := NewCveCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -162,10 +162,10 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
cveCmd.SetErr(buff)
|
cveCmd.SetErr(buff)
|
||||||
cveCmd.SetArgs(args)
|
cveCmd.SetArgs(args)
|
||||||
err := cveCmd.Execute()
|
err := cveCmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE dummyImageName tag DigestsA 123kB")
|
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE dummyImageName tag DigestsA 123kB")
|
||||||
So(err, ShouldBeNil)
|
|
||||||
Convey("using shorthand", func() {
|
Convey("using shorthand", func() {
|
||||||
args := []string{"cvetest", "-I", "dummyImageName", "--cve-id", "aCVEID", "--url", "someURL"}
|
args := []string{"cvetest", "-I", "dummyImageName", "--cve-id", "aCVEID", "--url", "someURL"}
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
|
@ -176,11 +176,10 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
cveCmd.SetErr(buff)
|
cveCmd.SetErr(buff)
|
||||||
cveCmd.SetArgs(args)
|
cveCmd.SetArgs(args)
|
||||||
err := cveCmd.Execute()
|
err := cveCmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE dummyImageName tag DigestsA 123kB")
|
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(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE anImage tag DigestsA 123kB")
|
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE anImage tag DigestsA 123kB")
|
||||||
So(err, ShouldBeNil)
|
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() {
|
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(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE fixedImage tag DigestsA 123kB")
|
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()
|
port := test.GetFreePort()
|
||||||
url := test.GetBaseURL(port)
|
url := test.GetBaseURL(port)
|
||||||
conf := config.New()
|
conf := config.New()
|
||||||
|
@ -351,6 +392,7 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
|
So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
|
||||||
So(str, ShouldContainSubstring, "CVE")
|
So(str, ShouldContainSubstring, "CVE")
|
||||||
|
|
||||||
Convey("invalid image", func() {
|
Convey("invalid image", func() {
|
||||||
args := []string{"cvetest", "--image", "invalid:0.0.1"}
|
args := []string{"cvetest", "--image", "invalid:0.0.1"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
||||||
|
@ -363,6 +405,33 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
err = cveCmd.Execute()
|
err = cveCmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
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() {
|
Convey("Test images by CVE ID", t, func() {
|
||||||
|
@ -380,6 +449,7 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
str = strings.TrimSpace(str)
|
str = strings.TrimSpace(str)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(str, ShouldEqual, "IMAGE NAME TAG DIGEST SIZE zot-cve-test 0.0.1 63a795ca 75MB")
|
So(str, ShouldEqual, "IMAGE NAME TAG DIGEST SIZE zot-cve-test 0.0.1 63a795ca 75MB")
|
||||||
|
|
||||||
Convey("invalid CVE ID", func() {
|
Convey("invalid CVE ID", func() {
|
||||||
args := []string{"cvetest", "--cve-id", "invalid"}
|
args := []string{"cvetest", "--cve-id", "invalid"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
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(err, ShouldBeNil)
|
||||||
So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIZE")
|
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() {
|
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)
|
str = strings.TrimSpace(str)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(str, ShouldEqual, "")
|
So(str, ShouldEqual, "")
|
||||||
|
|
||||||
Convey("random cve", func() {
|
Convey("random cve", func() {
|
||||||
args := []string{"cvetest", "--cve-id", "random", "--image", "zot-cve-test", "--fixed"}
|
args := []string{"cvetest", "--cve-id", "random", "--image", "zot-cve-test", "--fixed"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
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")
|
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"}
|
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))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
@ -446,6 +531,23 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIZE")
|
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() {
|
Convey("Test CVE by name and CVE ID", t, func() {
|
||||||
|
@ -462,7 +564,8 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIZE zot-cve-test 0.0.1 63a795ca 75MB")
|
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"}
|
args := []string{"cvetest", "--image", "test", "--cve-id", "CVE-20807"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
@ -477,5 +580,451 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIZE")
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -129,7 +129,15 @@ func setupImageFlags(imageCmd *cobra.Command, searchImageParams map[string]*stri
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchImage(searchConfig searchConfig) error {
|
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)
|
found, err := searcher.search(searchConfig)
|
||||||
if found {
|
if found {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -21,10 +21,12 @@ import (
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zotErrors "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api"
|
"zotregistry.io/zot/pkg/api"
|
||||||
"zotregistry.io/zot/pkg/api/config"
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
)
|
)
|
||||||
|
@ -56,6 +58,7 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test image no url", t, func() {
|
Convey("Test image no url", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyIdRandom"}
|
args := []string{"imagetest", "--name", "dummyIdRandom"}
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
|
||||||
|
@ -126,6 +129,7 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
||||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test image invalid url port", t, func() {
|
Convey("Test image invalid url port", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName", "--url", "http://localhost:99999"}
|
args := []string{"imagetest", "--name", "dummyImageName", "--url", "http://localhost:99999"}
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
|
||||||
|
@ -153,6 +157,7 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
So(buff.String(), ShouldContainSubstring, "invalid port")
|
So(buff.String(), ShouldContainSubstring, "invalid port")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test image unreachable", t, func() {
|
Convey("Test image unreachable", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName", "--url", "http://localhost:9999"}
|
args := []string{"imagetest", "--name", "dummyImageName", "--url", "http://localhost:9999"}
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
|
||||||
|
@ -168,10 +173,8 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test image url from config", t, func() {
|
Convey("Test image url from config", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName"}
|
args := []string{"imagetest", "--name", "dummyImageName"}
|
||||||
|
|
||||||
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)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewImageCommand(new(mockService))
|
cmd := NewImageCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -215,15 +218,44 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
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) {
|
func TestListRepos(t *testing.T) {
|
||||||
Convey("Test listing repositories", t, func() {
|
Convey("Test listing repositories", t, func() {
|
||||||
args := []string{"config-test"}
|
args := []string{"config-test"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(mockService))
|
cmd := NewRepoCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -264,11 +296,9 @@ func TestListRepos(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test listing repositories error", t, func() {
|
Convey("Test listing repositories error", t, func() {
|
||||||
args := []string{"config-test"}
|
args := []string{"config-test"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"config-test",
|
configPath := makeConfigFile(`{"configs":[{"_name":"config-test",
|
||||||
"url":"https://invalid.invalid","showspinner":false}]}`)
|
"url":"https://invalid.invalid","showspinner":false}]}`)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(searchService))
|
cmd := NewRepoCommand(new(searchService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -280,10 +310,8 @@ func TestListRepos(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test unable to get config value", t, func() {
|
Convey("Test unable to get config value", t, func() {
|
||||||
args := []string{"config-test-inexistent"}
|
args := []string{"config-test-inexistent"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(mockService))
|
cmd := NewRepoCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -295,10 +323,8 @@ func TestListRepos(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test error - no url provided", t, func() {
|
Convey("Test error - no url provided", t, func() {
|
||||||
args := []string{"config-test"}
|
args := []string{"config-test"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(mockService))
|
cmd := NewRepoCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -310,10 +336,8 @@ func TestListRepos(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test error - no args provided", t, func() {
|
Convey("Test error - no args provided", t, func() {
|
||||||
var args []string
|
var args []string
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(mockService))
|
cmd := NewRepoCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -325,11 +349,9 @@ func TestListRepos(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test error - spinner config invalid", t, func() {
|
Convey("Test error - spinner config invalid", t, func() {
|
||||||
args := []string{"config-test"}
|
args := []string{"config-test"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"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)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(mockService))
|
cmd := NewRepoCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -341,11 +363,9 @@ func TestListRepos(t *testing.T) {
|
||||||
|
|
||||||
Convey("Test error - verifyTLSConfig fails", t, func() {
|
Convey("Test error - verifyTLSConfig fails", t, func() {
|
||||||
args := []string{"config-test"}
|
args := []string{"config-test"}
|
||||||
|
configPath := makeConfigFile(`{"configs":[{"_name":"config-test",
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"config-test",
|
"verify-tls":"invalid", "url":"https://test-url.com","showspinner":false}]}`)
|
||||||
"verify-tls":"invalid", "url":"https://test-url.com","showspinner":false}]}`)
|
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(mockService))
|
cmd := NewRepoCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -359,10 +379,8 @@ func TestListRepos(t *testing.T) {
|
||||||
func TestOutputFormat(t *testing.T) {
|
func TestOutputFormat(t *testing.T) {
|
||||||
Convey("Test text", t, func() {
|
Convey("Test text", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName", "-o", "text"}
|
args := []string{"imagetest", "--name", "dummyImageName", "-o", "text"}
|
||||||
|
|
||||||
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)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewImageCommand(new(mockService))
|
cmd := NewImageCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -375,12 +393,12 @@ func TestOutputFormat(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// get image config functia
|
||||||
|
|
||||||
Convey("Test json", t, func() {
|
Convey("Test json", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName", "-o", "json"}
|
args := []string{"imagetest", "--name", "dummyImageName", "-o", "json"}
|
||||||
|
|
||||||
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)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewImageCommand(new(mockService))
|
cmd := NewImageCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -389,17 +407,15 @@ func TestOutputFormat(t *testing.T) {
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(strings.TrimSpace(str), ShouldEqual, `{ "name": "dummyImageName", "tags": [ { "name":`+
|
So(strings.TrimSpace(str), ShouldEqual, `{ "repoName": "dummyImageName", "tag": "tag", `+
|
||||||
` "tag", "size": 123445, "digest": "DigestsAreReallyLong", "configDigest": "", "layerDigests": null } ] }`)
|
`"configDigest": "", "digest": "DigestsAreReallyLong", "layers": null, "size": "123445" }`)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test yaml", t, func() {
|
Convey("Test yaml", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName", "-o", "yaml"}
|
args := []string{"imagetest", "--name", "dummyImageName", "-o", "yaml"}
|
||||||
|
|
||||||
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)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewImageCommand(new(mockService))
|
cmd := NewImageCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -408,16 +424,21 @@ func TestOutputFormat(t *testing.T) {
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(strings.TrimSpace(str), ShouldEqual, `name: dummyImageName tags: -`+
|
So(
|
||||||
` name: tag size: 123445 digest: DigestsAreReallyLong configdigest: "" layers: []`)
|
strings.TrimSpace(str),
|
||||||
|
ShouldEqual,
|
||||||
|
`reponame: dummyImageName tag: tag configdigest: "" `+
|
||||||
|
`digest: DigestsAreReallyLong layers: [] size: "123445"`,
|
||||||
|
)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
Convey("Test yml", func() {
|
Convey("Test yml", func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName", "-o", "yml"}
|
args := []string{"imagetest", "--name", "dummyImageName", "-o", "yml"}
|
||||||
|
configPath := makeConfigFile(
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`)
|
`{"configs":[{"_name":"imagetest",` +
|
||||||
|
`"url":"https://test-url.com","showspinner":false}]}`,
|
||||||
|
)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewImageCommand(new(mockService))
|
cmd := NewImageCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
|
@ -426,18 +447,20 @@ func TestOutputFormat(t *testing.T) {
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(strings.TrimSpace(str), ShouldEqual, `name: dummyImageName tags: -`+
|
So(
|
||||||
` name: tag size: 123445 digest: DigestsAreReallyLong configdigest: "" layers: []`)
|
strings.TrimSpace(str),
|
||||||
|
ShouldEqual,
|
||||||
|
`reponame: dummyImageName tag: tag configdigest: "" `+
|
||||||
|
`digest: DigestsAreReallyLong layers: [] size: "123445"`,
|
||||||
|
)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test invalid", t, func() {
|
Convey("Test invalid", t, func() {
|
||||||
args := []string{"imagetest", "--name", "dummyImageName", "-o", "random"}
|
args := []string{"imagetest", "--name", "dummyImageName", "-o", "random"}
|
||||||
|
|
||||||
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)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewImageCommand(new(mockService))
|
cmd := NewImageCommand(new(mockService))
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
cmd.SetOut(buff)
|
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() {
|
Convey("Test from real server", t, func() {
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
url := test.GetBaseURL(port)
|
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))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
cmd := NewImageCommand(new(searchService))
|
cmd := NewImageCommand(new(searchService))
|
||||||
// buff := bytes.NewBufferString("")
|
|
||||||
buff := &bytes.Buffer{}
|
buff := &bytes.Buffer{}
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
cmd.SetErr(buff)
|
cmd.SetErr(buff)
|
||||||
|
@ -504,6 +526,19 @@ func TestServerResponse(t *testing.T) {
|
||||||
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIZE")
|
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIZE")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B")
|
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:1.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() {
|
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:2.0 883fc0c5 15B")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:1.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() {
|
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, "IMAGE NAME TAG DIGEST SIZE")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B")
|
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 15B")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B")
|
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 15B")
|
||||||
|
|
||||||
Convey("with shorthand", func() {
|
Convey("with shorthand", func() {
|
||||||
args := []string{"imagetest", "-d", "883fc0c5"}
|
args := []string{"imagetest", "-d", "883fc0c5"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
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:2.0 883fc0c5 15B")
|
||||||
So(actual, ShouldContainSubstring, "repo7 test:1.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"}
|
args := []string{"imagetest", "--name", "repo777"}
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
@ -620,16 +698,15 @@ func TestServerResponse(t *testing.T) {
|
||||||
cmd.SetErr(buff)
|
cmd.SetErr(buff)
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldBeNil)
|
||||||
actual := buff.String()
|
So(len(buff.String()), ShouldEqual, 0)
|
||||||
So(actual, ShouldContainSubstring, "unknown")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test list repos error", func() {
|
Convey("Test list repos error", func() {
|
||||||
args := []string{"config-test"}
|
args := []string{"config-test"}
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"config-test",
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"config-test",
|
||||||
"url":"%s","showspinner":false}]}`, url))
|
"url":"%s","showspinner":false}]}`, url))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
cmd := NewRepoCommand(new(searchService))
|
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 {
|
func uploadManifest(url string) error {
|
||||||
// create a blob/layer
|
// create a blob/layer
|
||||||
resp, _ := resty.R().Post(url + "/v2/repo7/blobs/uploads/")
|
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}
|
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,
|
func (service mockService) getAllImages(ctx context.Context, config searchConfig, username, password string,
|
||||||
channel chan stringResult, wtgrp *sync.WaitGroup,
|
channel chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
) {
|
) {
|
||||||
|
@ -748,14 +1305,10 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig
|
||||||
defer close(channel)
|
defer close(channel)
|
||||||
|
|
||||||
image := &imageStruct{}
|
image := &imageStruct{}
|
||||||
image.Name = "randomimageName"
|
image.RepoName = "randomimageName"
|
||||||
image.Tags = []tags{
|
image.Tag = "tag"
|
||||||
{
|
image.Digest = "DigestsAreReallyLong"
|
||||||
Name: "tag",
|
image.Size = "123445"
|
||||||
Digest: "DigestsAreReallyLong",
|
|
||||||
Size: 123445,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := image.string(*config.outputFormat)
|
str, err := image.string(*config.outputFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -774,14 +1327,10 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf
|
||||||
defer close(channel)
|
defer close(channel)
|
||||||
|
|
||||||
image := &imageStruct{}
|
image := &imageStruct{}
|
||||||
image.Name = imageName
|
image.RepoName = imageName
|
||||||
image.Tags = []tags{
|
image.Tag = "tag"
|
||||||
{
|
image.Digest = "DigestsAreReallyLong"
|
||||||
Name: "tag",
|
image.Size = "123445"
|
||||||
Digest: "DigestsAreReallyLong",
|
|
||||||
Size: 123445,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := image.string(*config.outputFormat)
|
str, err := image.string(*config.outputFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -831,6 +1380,18 @@ func (service mockService) getCveByImage(ctx context.Context, config searchConfi
|
||||||
rch <- stringResult{str, nil}
|
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,
|
func (service mockService) getImagesByCveID(ctx context.Context, config searchConfig, username, password, cvid string,
|
||||||
rch chan stringResult, wtgrp *sync.WaitGroup,
|
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)
|
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 {
|
func makeConfigFile(content string) string {
|
||||||
os.Setenv("HOME", os.TempDir())
|
os.Setenv("HOME", os.TempDir())
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,27 @@ func getCveSearchers() []searcher {
|
||||||
return searchers
|
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 {
|
type searcher interface {
|
||||||
search(searchConfig searchConfig) (bool, error)
|
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{}
|
type imageByNameSearcher struct{}
|
||||||
|
|
||||||
func (search imageByNameSearcher) search(config searchConfig) (bool, error) {
|
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{}
|
type imagesByDigestSearcher struct{}
|
||||||
|
|
||||||
func (search imagesByDigestSearcher) search(config searchConfig) (bool, error) {
|
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{}
|
type cveByImageSearcher struct{}
|
||||||
|
|
||||||
func (search cveByImageSearcher) search(config searchConfig) (bool, error) {
|
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{}
|
type imagesByCVEIDSearcher struct{}
|
||||||
|
|
||||||
func (search imagesByCVEIDSearcher) search(config searchConfig) (bool, error) {
|
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
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +334,7 @@ func (search imagesByCVEIDSearcher) search(config searchConfig) (bool, error) {
|
||||||
|
|
||||||
wg.Add(1)
|
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)
|
wg.Add(1)
|
||||||
|
|
||||||
errCh := make(chan error, 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{}
|
type tagsByImageNameAndCVEIDSearcher struct{}
|
||||||
|
|
||||||
func (search tagsByImageNameAndCVEIDSearcher) search(config searchConfig) (bool, error) {
|
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
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +394,7 @@ func (search tagsByImageNameAndCVEIDSearcher) search(config searchConfig) (bool,
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
go config.searchService.getImageByNameAndCVEID(ctx, config, username, password, *config.params["imageName"],
|
go config.searchService.getImageByNameAndCVEID(ctx, config, username, password, *config.params["imageName"],
|
||||||
*config.params["cvid"], strErr, &wg)
|
*config.params["cveID"], strErr, &wg)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
errCh := make(chan error, 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{}
|
type fixedTagsSearcher struct{}
|
||||||
|
|
||||||
func (search fixedTagsSearcher) search(config searchConfig) (bool, error) {
|
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
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +458,7 @@ func (search fixedTagsSearcher) search(config searchConfig) (bool, error) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
go config.searchService.getFixedTagsForCVE(ctx, config, username, password, *config.params["imageName"],
|
go config.searchService.getFixedTagsForCVE(ctx, config, username, password, *config.params["imageName"],
|
||||||
*config.params["cvid"], strErr, &wg)
|
*config.params["cveID"], strErr, &wg)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
errCh := make(chan error, 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,
|
func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan stringResult,
|
||||||
cancel context.CancelFunc, printHeader printHeader, errCh chan error,
|
cancel context.CancelFunc, printHeader printHeader, errCh chan error,
|
||||||
) {
|
) {
|
||||||
|
@ -376,12 +585,14 @@ type spinnerState struct {
|
||||||
enabled bool
|
enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint
|
||||||
func (spinner *spinnerState) startSpinner() {
|
func (spinner *spinnerState) startSpinner() {
|
||||||
if spinner.enabled {
|
if spinner.enabled {
|
||||||
spinner.spinner.Start()
|
spinner.spinner.Start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint
|
||||||
func (spinner *spinnerState) stopSpinner() {
|
func (spinner *spinnerState) stopSpinner() {
|
||||||
if spinner.enabled && spinner.spinner.Active() {
|
if spinner.enabled && spinner.spinner.Active() {
|
||||||
spinner.spinner.Stop()
|
spinner.spinner.Stop()
|
||||||
|
@ -397,14 +608,14 @@ func getEmptyStruct() struct{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSet(initialValues ...string) *set {
|
func newSet(initialValues ...string) *set {
|
||||||
ret := &set{}
|
setValues := &set{}
|
||||||
ret.m = make(map[string]struct{})
|
setValues.m = make(map[string]struct{})
|
||||||
|
|
||||||
for _, val := range initialValues {
|
for _, val := range initialValues {
|
||||||
ret.m[val] = getEmptyStruct()
|
setValues.m[val] = getEmptyStruct()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return setValues
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *set) contains(value string) bool {
|
func (s *set) contains(value string) bool {
|
||||||
|
@ -413,6 +624,10 @@ func (s *set) contains(value string) bool {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
waitTimeout = httpTimeout + 5*time.Second
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrCannotSearch = errors.New("cannot search with these parameters")
|
ErrCannotSearch = errors.New("cannot search with these parameters")
|
||||||
ErrInvalidOutputFormat = errors.New("invalid output format")
|
ErrInvalidOutputFormat = errors.New("invalid output format")
|
||||||
|
@ -438,7 +653,7 @@ func printImageTableHeader(writer io.Writer, verbose bool) {
|
||||||
table.SetColMinWidth(colLayersIndex, layersWidth)
|
table.SetColMinWidth(colLayersIndex, layersWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
row := make([]string, 6) //nolint:gomnd
|
row := make([]string, 6) // nolint:gomnd
|
||||||
|
|
||||||
row[colImageNameIndex] = "IMAGE NAME"
|
row[colImageNameIndex] = "IMAGE NAME"
|
||||||
row[colTagIndex] = "TAG"
|
row[colTagIndex] = "TAG"
|
||||||
|
@ -465,9 +680,28 @@ func printCVETableHeader(writer io.Writer, verbose bool) {
|
||||||
table.Render()
|
table.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func printResult(config searchConfig, imageList []imageStruct) error {
|
||||||
waitTimeout = httpTimeout + 5*time.Second
|
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 (
|
var (
|
||||||
errInvalidImageNameAndTag = errors.New("cli: Invalid input format. Expected IMAGENAME:TAG")
|
errInvalidImageNameAndTag = errors.New("cli: Invalid input format. Expected IMAGENAME:TAG")
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
@ -22,22 +22,35 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type SearchService interface {
|
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,
|
getAllImages(ctx context.Context, config searchConfig, username, password string,
|
||||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
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,
|
getCveByImage(ctx context.Context, config searchConfig, username, password, imageName string,
|
||||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||||
getImagesByCveID(ctx context.Context, config searchConfig, username, password, cvid string,
|
getImagesByCveID(ctx context.Context, config searchConfig, username, password, cvid string,
|
||||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||||
getImagesByDigest(ctx context.Context, config searchConfig, username, password, digest string,
|
getImagesByDigest(ctx context.Context, config searchConfig, username, password, digest string,
|
||||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
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,
|
getFixedTagsForCVE(ctx context.Context, config searchConfig, username, password, imageName, cvid string,
|
||||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||||
getRepos(ctx context.Context, config searchConfig, username, password string,
|
getRepos(ctx context.Context, config searchConfig, username, password string,
|
||||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
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{}
|
type searchService struct{}
|
||||||
|
@ -46,6 +59,116 @@ func NewSearchService() SearchService {
|
||||||
return 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,
|
func (service searchService) getImageByName(ctx context.Context, config searchConfig,
|
||||||
username, password, imageName string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tagsList := &tagListResp{}
|
tagList := &tagListResp{}
|
||||||
_, err = makeGETRequest(ctx, tagListEndpoint, username, password, *config.verifyTLS, &tagsList)
|
_, err = makeGETRequest(ctx, tagListEndpoint, username, password, *config.verifyTLS, &tagList)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isContextDone(ctx) {
|
if isContextDone(ctx) {
|
||||||
|
@ -138,7 +261,7 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range tagsList.Tags {
|
for _, tag := range tagList.Tags {
|
||||||
wtgrp.Add(1)
|
wtgrp.Add(1)
|
||||||
|
|
||||||
go addManifestCallToPool(ctx, config, pool, username, password, imageName, tag, rch, wtgrp)
|
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)
|
defer close(rch)
|
||||||
|
|
||||||
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+`
|
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+`
|
||||||
Name Tags }
|
RepoName Tag Digest Size}
|
||||||
}`,
|
}`,
|
||||||
cvid)
|
cvid)
|
||||||
result := &imagesForCve{}
|
result := &imagesForCve{}
|
||||||
|
@ -189,12 +312,10 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search
|
||||||
|
|
||||||
go rlim.startRateLimiter(ctx)
|
go rlim.startRateLimiter(ctx)
|
||||||
|
|
||||||
for _, image := range result.Data.ImageListForCVE {
|
for _, image := range result.Data.ImageList {
|
||||||
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()
|
localWg.Wait()
|
||||||
|
@ -207,7 +328,7 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc
|
||||||
defer close(rch)
|
defer close(rch)
|
||||||
|
|
||||||
query := fmt.Sprintf(`{ImageListForDigest(id: "%s") {`+`
|
query := fmt.Sprintf(`{ImageListForDigest(id: "%s") {`+`
|
||||||
Name Tags }
|
RepoName Tag Digest ConfigDigest Size Layers {Size Digest}}
|
||||||
}`,
|
}`,
|
||||||
digest)
|
digest)
|
||||||
result := &imagesForDigest{}
|
result := &imagesForDigest{}
|
||||||
|
@ -244,12 +365,10 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc
|
||||||
|
|
||||||
go rlim.startRateLimiter(ctx)
|
go rlim.startRateLimiter(ctx)
|
||||||
|
|
||||||
for _, image := range result.Data.ImageListForDigest {
|
for _, image := range result.Data.ImageList {
|
||||||
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()
|
localWg.Wait()
|
||||||
|
@ -262,7 +381,7 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config
|
||||||
defer close(rch)
|
defer close(rch)
|
||||||
|
|
||||||
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+`
|
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") {`+`
|
||||||
Name Tags }
|
RepoName Tag Digest ConfigDigest Size Layers {Size Digest}}
|
||||||
}`,
|
}`,
|
||||||
cvid)
|
cvid)
|
||||||
result := &imagesForCve{}
|
result := &imagesForCve{}
|
||||||
|
@ -299,16 +418,14 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config
|
||||||
|
|
||||||
go rlim.startRateLimiter(ctx)
|
go rlim.startRateLimiter(ctx)
|
||||||
|
|
||||||
for _, image := range result.Data.ImageListForCVE {
|
for _, image := range result.Data.ImageList {
|
||||||
if !strings.EqualFold(imageName, image.Name) {
|
if !strings.EqualFold(imageName, image.RepoName) {
|
||||||
continue
|
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()
|
localWg.Wait()
|
||||||
|
@ -368,6 +485,59 @@ func (service searchService) getCveByImage(ctx context.Context, config searchCon
|
||||||
rch <- stringResult{str, nil}
|
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 {
|
func groupCVEsBySeverity(cveList []cve) []cve {
|
||||||
var (
|
var (
|
||||||
unknown = make([]cve, 0)
|
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
|
// Query using JQL, the query string is passed as a parameter
|
||||||
// errors are returned in the stringResult channel, the unmarshalled payload is in resultPtr.
|
// errors are returned in the stringResult channel, the unmarshalled payload is in resultPtr.
|
||||||
func (service searchService) makeGraphQLQuery(ctx context.Context, config searchConfig,
|
func (service searchService) makeGraphQLQuery(ctx context.Context,
|
||||||
username, password, query string,
|
config searchConfig, username, password, query string,
|
||||||
resultPtr interface{},
|
resultPtr interface{},
|
||||||
) error {
|
) error {
|
||||||
endPoint, err := combineServerAndEndpointURL(*config.servURL, constants.ExtSearchPrefix)
|
endPoint, err := combineServerAndEndpointURL(*config.servURL, constants.ExtSearchPrefix)
|
||||||
|
@ -493,6 +610,34 @@ func (service searchService) makeGraphQLQuery(ctx context.Context, config search
|
||||||
return nil
|
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,
|
func addManifestCallToPool(ctx context.Context, config searchConfig, pool *requestsPool,
|
||||||
username, password, imageName, tagName string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
username, password, imageName, tagName string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
) {
|
) {
|
||||||
|
@ -533,6 +678,11 @@ type errorGraphQL struct {
|
||||||
Path []string `json:"path"`
|
Path []string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tagListResp struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
//nolint:tagliatelle // graphQL schema
|
||||||
type packageList struct {
|
type packageList struct {
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
|
@ -579,7 +729,7 @@ func (cve cveResult) stringPlainText() (string, error) {
|
||||||
table := getCVETableWriter(&builder)
|
table := getCVETableWriter(&builder)
|
||||||
|
|
||||||
for _, c := range cve.Data.CVEListForImage.CVEList {
|
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)
|
title := ellipsize(c.Title, cveTitleWidth, ellipsis)
|
||||||
severity := ellipsize(c.Severity, cveSeverityWidth, ellipsis)
|
severity := ellipsize(c.Severity, cveSeverityWidth, ellipsis)
|
||||||
row := make([]string, 3) //nolint:gomnd
|
row := make([]string, 3) //nolint:gomnd
|
||||||
|
@ -618,51 +768,50 @@ func (cve cveResult) stringYAML() (string, error) {
|
||||||
type fixedTags struct {
|
type fixedTags struct {
|
||||||
Errors []errorGraphQL `json:"errors"`
|
Errors []errorGraphQL `json:"errors"`
|
||||||
Data struct {
|
Data struct {
|
||||||
//nolint:tagliatelle // graphQL schema
|
ImageList []imageStruct `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema
|
||||||
ImageListWithCVEFixed struct {
|
|
||||||
Tags []struct {
|
|
||||||
Name string `json:"Name"`
|
|
||||||
Timestamp time.Time `json:"Timestamp"`
|
|
||||||
} `json:"Tags"`
|
|
||||||
} `json:"ImageListWithCVEFixed"`
|
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type imagesForCve struct {
|
type imagesForCve struct {
|
||||||
Errors []errorGraphQL `json:"errors"`
|
Errors []errorGraphQL `json:"errors"`
|
||||||
Data struct {
|
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"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type imagesForDigest struct {
|
type imagesForDigest struct {
|
||||||
Errors []errorGraphQL `json:"errors"`
|
Errors []errorGraphQL `json:"errors"`
|
||||||
Data struct {
|
Data struct {
|
||||||
ImageListForDigest []tagListResp `json:"ImageListForDigest"` //nolint:tagliatelle // graphQL schema
|
ImageList []imageStruct `json:"ImageListForDigest"` //nolint:tagliatelle // graphQL schema
|
||||||
} `json:"data"`
|
} `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 {
|
type layer struct {
|
||||||
Size uint64 `json:"size"`
|
Size uint64 `json:"size,string"`
|
||||||
Digest string `json:"digest"`
|
Digest string `json:"digest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,41 +842,41 @@ func (img imageStruct) stringPlainText() (string, error) {
|
||||||
table.SetColMinWidth(colLayersIndex, layersWidth)
|
table.SetColMinWidth(colLayersIndex, layersWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range img.Tags {
|
imageName := ellipsize(img.RepoName, imageNameWidth, ellipsis)
|
||||||
imageName := ellipsize(img.Name, imageNameWidth, ellipsis)
|
tagName := ellipsize(img.Tag, tagWidth, ellipsis)
|
||||||
tagName := ellipsize(tag.Name, tagWidth, ellipsis)
|
digest := ellipsize(img.Digest, digestWidth, "")
|
||||||
digest := ellipsize(tag.Digest, digestWidth, "")
|
imgSize, _ := strconv.ParseUint(img.Size, 10, 64)
|
||||||
size := ellipsize(strings.ReplaceAll(humanize.Bytes(tag.Size), " ", ""), sizeWidth, ellipsis)
|
size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis)
|
||||||
config := ellipsize(tag.ConfigDigest, configWidth, "")
|
config := ellipsize(img.ConfigDigest, configWidth, "")
|
||||||
row := make([]string, 6) //nolint:gomnd
|
row := make([]string, 6) // nolint:gomnd
|
||||||
|
|
||||||
row[colImageNameIndex] = imageName
|
row[colImageNameIndex] = imageName
|
||||||
row[colTagIndex] = tagName
|
row[colTagIndex] = tagName
|
||||||
row[colDigestIndex] = digest
|
row[colDigestIndex] = digest
|
||||||
row[colSizeIndex] = size
|
row[colSizeIndex] = size
|
||||||
|
|
||||||
if img.verbose {
|
if img.verbose {
|
||||||
row[colConfigIndex] = config
|
row[colConfigIndex] = config
|
||||||
row[colLayersIndex] = ""
|
row[colLayersIndex] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Append(row)
|
table.Append(row)
|
||||||
|
|
||||||
if img.verbose {
|
if img.verbose {
|
||||||
for _, entry := range tag.Layers {
|
for _, entry := range img.Layers {
|
||||||
layerSize := ellipsize(strings.ReplaceAll(humanize.Bytes(entry.Size), " ", ""), sizeWidth, ellipsis)
|
layerSize := entry.Size
|
||||||
layerDigest := ellipsize(entry.Digest, digestWidth, "")
|
size := ellipsize(strings.ReplaceAll(humanize.Bytes(layerSize), " ", ""), sizeWidth, ellipsis)
|
||||||
|
layerDigest := ellipsize(entry.Digest, digestWidth, "")
|
||||||
|
|
||||||
layerRow := make([]string, 6) //nolint:gomnd
|
layerRow := make([]string, 6) // nolint:gomnd
|
||||||
layerRow[colImageNameIndex] = ""
|
layerRow[colImageNameIndex] = ""
|
||||||
layerRow[colTagIndex] = ""
|
layerRow[colTagIndex] = ""
|
||||||
layerRow[colDigestIndex] = ""
|
layerRow[colDigestIndex] = ""
|
||||||
layerRow[colSizeIndex] = layerSize
|
layerRow[colSizeIndex] = size
|
||||||
layerRow[colConfigIndex] = ""
|
layerRow[colConfigIndex] = ""
|
||||||
layerRow[colLayersIndex] = layerDigest
|
layerRow[colLayersIndex] = layerDigest
|
||||||
|
|
||||||
table.Append(layerRow)
|
table.Append(layerRow)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -760,6 +909,7 @@ type catalogResponse struct {
|
||||||
Repositories []string `json:"repositories"`
|
Repositories []string `json:"repositories"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:tagliatelle
|
||||||
type manifestResponse struct {
|
type manifestResponse struct {
|
||||||
Layers []struct {
|
Layers []struct {
|
||||||
MediaType string `json:"mediaType"`
|
MediaType string `json:"mediaType"`
|
||||||
|
@ -767,8 +917,8 @@ type manifestResponse struct {
|
||||||
Size uint64 `json:"size"`
|
Size uint64 `json:"size"`
|
||||||
} `json:"layers"`
|
} `json:"layers"`
|
||||||
Annotations struct {
|
Annotations struct {
|
||||||
WsTychoStackerStackerYaml string `json:"ws.tycho.stacker.stacker_yaml"` //nolint:tagliatelle // custom annotation
|
WsTychoStackerStackerYaml string `json:"ws.tycho.stacker.stacker_yaml"`
|
||||||
WsTychoStackerGitVersion string `json:"ws.tycho.stacker.git_version"` //nolint:tagliatelle // custom annotation
|
WsTychoStackerGitVersion string `json:"ws.tycho.stacker.git_version"`
|
||||||
} `json:"annotations"`
|
} `json:"annotations"`
|
||||||
Config struct {
|
Config struct {
|
||||||
Size int `json:"size"`
|
Size int `json:"size"`
|
||||||
|
@ -836,7 +986,7 @@ func getCVETableWriter(writer io.Writer) *tablewriter.Table {
|
||||||
table.SetBorder(false)
|
table.SetBorder(false)
|
||||||
table.SetTablePadding(" ")
|
table.SetTablePadding(" ")
|
||||||
table.SetNoWhiteSpace(true)
|
table.SetNoWhiteSpace(true)
|
||||||
table.SetColMinWidth(colCVEIDIndex, cvidWidth)
|
table.SetColMinWidth(colCVEIDIndex, cveIDWidth)
|
||||||
table.SetColMinWidth(colCVESeverityIndex, cveSeverityWidth)
|
table.SetColMinWidth(colCVESeverityIndex, cveSeverityWidth)
|
||||||
table.SetColMinWidth(colCVETitleIndex, cveTitleWidth)
|
table.SetColMinWidth(colCVETitleIndex, cveTitleWidth)
|
||||||
|
|
||||||
|
@ -895,7 +1045,7 @@ const (
|
||||||
colLayersIndex = 4
|
colLayersIndex = 4
|
||||||
colSizeIndex = 5
|
colSizeIndex = 5
|
||||||
|
|
||||||
cvidWidth = 16
|
cveIDWidth = 16
|
||||||
cveSeverityWidth = 8
|
cveSeverityWidth = 8
|
||||||
cveTitleWidth = 48
|
cveTitleWidth = 48
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -25,14 +24,6 @@ type TagInfo struct {
|
||||||
Timestamp time.Time
|
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 {
|
func GetRootDir(image string, storeController storage.StoreController) string {
|
||||||
var rootDir string
|
var rootDir string
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ type RepoSummary struct {
|
||||||
Platforms []OsArch `json:"platforms"`
|
Platforms []OsArch `json:"platforms"`
|
||||||
Vendors []string `json:"vendors"`
|
Vendors []string `json:"vendors"`
|
||||||
Score int `json:"score"`
|
Score int `json:"score"`
|
||||||
NewestTag ImageSummary `json:"newestTag"`
|
NewestImage ImageSummary `json:"newestImage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayerSummary struct {
|
type LayerSummary struct {
|
||||||
|
@ -126,8 +126,8 @@ type ErrorGQL struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageInfo struct {
|
type ImageInfo struct {
|
||||||
Name string
|
RepoName string
|
||||||
Latest string
|
Tag string
|
||||||
LastUpdated time.Time
|
LastUpdated time.Time
|
||||||
Description string
|
Description string
|
||||||
Licenses string
|
Licenses string
|
||||||
|
@ -377,7 +377,7 @@ func TestLatestTagSearchHTTP(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
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(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -388,9 +388,9 @@ func TestLatestTagSearchHTTP(t *testing.T) {
|
||||||
So(len(responseStruct.ImgListWithLatestTag.Images), ShouldEqual, 4)
|
So(len(responseStruct.ImgListWithLatestTag.Images), ShouldEqual, 4)
|
||||||
|
|
||||||
images := responseStruct.ImgListWithLatestTag.Images
|
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(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
@ -399,7 +399,7 @@ func TestLatestTagSearchHTTP(t *testing.T) {
|
||||||
panic(err)
|
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(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -423,7 +423,7 @@ func TestLatestTagSearchHTTP(t *testing.T) {
|
||||||
panic(err)
|
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(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -434,7 +434,7 @@ func TestLatestTagSearchHTTP(t *testing.T) {
|
||||||
panic(err)
|
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(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -444,7 +444,7 @@ func TestLatestTagSearchHTTP(t *testing.T) {
|
||||||
panic(err)
|
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(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -455,7 +455,7 @@ func TestLatestTagSearchHTTP(t *testing.T) {
|
||||||
panic(err)
|
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(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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.Name, ShouldEqual, "zot-cve-test")
|
||||||
So(responseStruct.ExpandedRepoInfo.RepoInfo.Summary.Score, ShouldEqual, -1)
|
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)
|
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
|
@ -543,10 +543,10 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
|
|
||||||
err = json.Unmarshal(resp.Body(), responseStruct)
|
err = json.Unmarshal(resp.Body(), responseStruct)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0)
|
||||||
found := false
|
found := false
|
||||||
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
|
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images {
|
||||||
if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" {
|
if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" {
|
||||||
found = true
|
found = true
|
||||||
So(m.IsSigned, ShouldEqual, false)
|
So(m.IsSigned, ShouldEqual, false)
|
||||||
|
@ -564,10 +564,10 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
|
|
||||||
err = json.Unmarshal(resp.Body(), responseStruct)
|
err = json.Unmarshal(resp.Body(), responseStruct)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0)
|
||||||
found = false
|
found = false
|
||||||
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
|
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images {
|
||||||
if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" {
|
if m.Digest == "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29" {
|
||||||
found = true
|
found = true
|
||||||
So(m.IsSigned, ShouldEqual, true)
|
So(m.IsSigned, ShouldEqual, true)
|
||||||
|
@ -575,14 +575,14 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
}
|
}
|
||||||
So(found, ShouldEqual, true)
|
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)
|
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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)
|
resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + query)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
@ -590,10 +590,10 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
|
|
||||||
err = json.Unmarshal(resp.Body(), responseStruct)
|
err = json.Unmarshal(resp.Body(), responseStruct)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0)
|
||||||
found = false
|
found = false
|
||||||
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
|
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images {
|
||||||
if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" {
|
if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" {
|
||||||
found = true
|
found = true
|
||||||
So(m.IsSigned, ShouldEqual, false)
|
So(m.IsSigned, ShouldEqual, false)
|
||||||
|
@ -611,10 +611,10 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
|
|
||||||
err = json.Unmarshal(resp.Body(), responseStruct)
|
err = json.Unmarshal(resp.Body(), responseStruct)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images), ShouldNotEqual, 0)
|
||||||
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Manifests[0].Layers), ShouldNotEqual, 0)
|
So(len(responseStruct.ExpandedRepoInfo.RepoInfo.Images[0].Layers), ShouldNotEqual, 0)
|
||||||
found = false
|
found = false
|
||||||
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Manifests {
|
for _, m := range responseStruct.ExpandedRepoInfo.RepoInfo.Images {
|
||||||
if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" {
|
if m.Digest == "2bacca16b9df395fc855c14ccf50b12b58d35d468b8e7f25758aff90f89bf396" {
|
||||||
found = true
|
found = true
|
||||||
So(m.IsSigned, ShouldEqual, true)
|
So(m.IsSigned, ShouldEqual, true)
|
||||||
|
@ -832,7 +832,7 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
Vendors
|
Vendors
|
||||||
Score
|
Score
|
||||||
NewestTag {
|
NewestImage {
|
||||||
RepoName
|
RepoName
|
||||||
Tag
|
Tag
|
||||||
LastUpdated
|
LastUpdated
|
||||||
|
@ -911,14 +911,14 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
So(repo.Vendors[0], ShouldEqual, image.Vendor)
|
So(repo.Vendors[0], ShouldEqual, image.Vendor)
|
||||||
So(repo.Platforms[0].Os, ShouldEqual, image.Platform.Os)
|
So(repo.Platforms[0].Os, ShouldEqual, image.Platform.Os)
|
||||||
So(repo.Platforms[0].Arch, ShouldEqual, image.Platform.Arch)
|
So(repo.Platforms[0].Arch, ShouldEqual, image.Platform.Arch)
|
||||||
So(repo.NewestTag.RepoName, ShouldEqual, image.RepoName)
|
So(repo.NewestImage.RepoName, ShouldEqual, image.RepoName)
|
||||||
So(repo.NewestTag.Tag, ShouldEqual, image.Tag)
|
So(repo.NewestImage.Tag, ShouldEqual, image.Tag)
|
||||||
So(repo.NewestTag.LastUpdated, ShouldEqual, image.LastUpdated)
|
So(repo.NewestImage.LastUpdated, ShouldEqual, image.LastUpdated)
|
||||||
So(repo.NewestTag.Size, ShouldEqual, image.Size)
|
So(repo.NewestImage.Size, ShouldEqual, image.Size)
|
||||||
So(repo.NewestTag.IsSigned, ShouldEqual, image.IsSigned)
|
So(repo.NewestImage.IsSigned, ShouldEqual, image.IsSigned)
|
||||||
So(repo.NewestTag.Vendor, ShouldEqual, image.Vendor)
|
So(repo.NewestImage.Vendor, ShouldEqual, image.Vendor)
|
||||||
So(repo.NewestTag.Platform.Os, ShouldEqual, image.Platform.Os)
|
So(repo.NewestImage.Platform.Os, ShouldEqual, image.Platform.Os)
|
||||||
So(repo.NewestTag.Platform.Arch, ShouldEqual, image.Platform.Arch)
|
So(repo.NewestImage.Platform.Arch, ShouldEqual, image.Platform.Arch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepositories fail
|
// GetRepositories fail
|
||||||
|
|
|
@ -43,11 +43,11 @@ type BaseOciLayoutUtils struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoInfo 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"`
|
Tag string `json:"tag"`
|
||||||
Digest string `json:"digest"`
|
Digest string `json:"digest"`
|
||||||
IsSigned bool `json:"isSigned"`
|
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
|
// made up of all manifests, configs and image layers
|
||||||
repoSize := int64(0)
|
repoSize := int64(0)
|
||||||
|
|
||||||
manifests := make([]Manifest, 0)
|
manifests := make([]Image, 0)
|
||||||
|
|
||||||
tagsInfo, err := olu.GetImageTagsWithTimestamp(name)
|
tagsInfo, err := olu.GetImageTagsWithTimestamp(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -376,7 +376,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
|
||||||
repoVendors := make([]string, 0, len(manifestList))
|
repoVendors := make([]string, 0, len(manifestList))
|
||||||
|
|
||||||
for _, man := range manifestList {
|
for _, man := range manifestList {
|
||||||
manifestInfo := Manifest{}
|
manifestInfo := Image{}
|
||||||
|
|
||||||
manifestInfo.Digest = man.Digest.Encoded()
|
manifestInfo.Digest = man.Digest.Encoded()
|
||||||
|
|
||||||
|
@ -441,7 +441,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
|
||||||
manifests = append(manifests, manifestInfo)
|
manifests = append(manifests, manifestInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.Manifests = manifests
|
repo.Images = manifests
|
||||||
|
|
||||||
lastUpdate, err := olu.GetRepoLastUpdated(name)
|
lastUpdate, err := olu.GetRepoLastUpdated(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"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,
|
func (cveinfo CveInfo) GetImageListForCVE(repo, cvid string, imgStore storage.ImageStore,
|
||||||
trivyCtx *TrivyCtx,
|
trivyCtx *TrivyCtx,
|
||||||
) ([]*string, error) {
|
) ([]ImageInfoByCVE, error) {
|
||||||
tags := make([]*string, 0)
|
imgList := make([]ImageInfoByCVE, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
rootDir := imgStore.RootDir()
|
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)
|
image := fmt.Sprintf("%s:%s", repo, tag)
|
||||||
|
|
||||||
trivyCtx.Input = path.Join(rootDir, image)
|
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 _, result := range report.Results {
|
||||||
for _, vulnerability := range result.Vulnerabilities {
|
for _, vulnerability := range result.Vulnerabilities {
|
||||||
if vulnerability.VulnerabilityID == cvid {
|
if vulnerability.VulnerabilityID == cvid {
|
||||||
copyImgTag := tag
|
digest := manifest.Digest
|
||||||
tags = append(tags, ©ImgTag)
|
|
||||||
|
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
|
break
|
||||||
}
|
}
|
||||||
|
@ -186,5 +201,5 @@ func (cveinfo CveInfo) GetImageListForCVE(repo, cvid string, imgStore storage.Im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags, nil
|
return imgList, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,23 +46,14 @@ type CveResult struct {
|
||||||
ImgList ImgList `json:"data"`
|
ImgList ImgList `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImgWithFixedCVE struct {
|
|
||||||
ImgResults ImgResults `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
//nolint:tagliatelle // graphQL schema
|
||||||
type ImgResults struct {
|
type ImgListWithCVEFixed struct {
|
||||||
ImgResultForFixedCVE ImgResultForFixedCVE `json:"ImgResultForFixedCVE"`
|
Images []ImageInfo `json:"ImageListWithCVEFixed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
type ImageInfo struct {
|
||||||
type ImgResultForFixedCVE struct {
|
RepoName string
|
||||||
Tags []TagInfo `json:"Tags"`
|
LastUpdated time.Time
|
||||||
}
|
|
||||||
|
|
||||||
type TagInfo struct {
|
|
||||||
Name string
|
|
||||||
Timestamp time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
//nolint:tagliatelle // graphQL schema
|
||||||
|
@ -470,24 +461,24 @@ func TestCVESearch(t *testing.T) {
|
||||||
|
|
||||||
cvid := cveResult.ImgList.CVEResultForImage.CVEList[0].ID
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
var imgFixedCVEResult ImgWithFixedCVE
|
var imgListWithCVEFixed ImgListWithCVEFixed
|
||||||
err = json.Unmarshal(resp.Body(), &imgFixedCVEResult)
|
err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed)
|
||||||
So(err, ShouldBeNil)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
err = json.Unmarshal(resp.Body(), &imgFixedCVEResult)
|
err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed)
|
||||||
So(err, ShouldBeNil)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -504,7 +495,7 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -512,7 +503,7 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -520,11 +511,11 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -532,7 +523,7 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -544,7 +535,7 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -585,11 +576,11 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
So(resp.StatusCode(), ShouldEqual, 422)
|
||||||
|
|
||||||
|
@ -601,7 +592,7 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
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, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
package cveinfo
|
package cveinfo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
|
@ -25,3 +27,9 @@ type TrivyCtx struct {
|
||||||
Input string
|
Input string
|
||||||
Ctx *cli.Context
|
Ctx *cli.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImageInfoByCVE struct {
|
||||||
|
Tag string
|
||||||
|
Digest digest.Digest
|
||||||
|
Manifest v1.Manifest
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package digestinfo
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
|
@ -15,6 +17,12 @@ type DigestInfo struct {
|
||||||
LayoutUtils *common.BaseOciLayoutUtils
|
LayoutUtils *common.BaseOciLayoutUtils
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImageInfoByDigest struct {
|
||||||
|
Tag string
|
||||||
|
Digest digest.Digest
|
||||||
|
Manifest v1.Manifest
|
||||||
|
}
|
||||||
|
|
||||||
// NewDigestInfo initializes a new DigestInfo object.
|
// NewDigestInfo initializes a new DigestInfo object.
|
||||||
func NewDigestInfo(storeController storage.StoreController, log log.Logger) *DigestInfo {
|
func NewDigestInfo(storeController storage.StoreController, log log.Logger) *DigestInfo {
|
||||||
layoutUtils := common.NewBaseOciLayoutUtils(storeController, log)
|
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.
|
// FilterImagesByDigest returns a list of image tags in a repository matching a specific divest.
|
||||||
func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]*string, error) {
|
func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]ImageInfoByDigest, error) {
|
||||||
uniqueTags := []*string{}
|
imageTags := []ImageInfoByDigest{}
|
||||||
|
|
||||||
manifests, err := digestinfo.LayoutUtils.GetImageManifests(repo)
|
manifests, err := digestinfo.LayoutUtils.GetImageManifests(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
digestinfo.Log.Error().Err(err).Msg("unable to read image manifests")
|
digestinfo.Log.Error().Err(err).Msg("unable to read image manifests")
|
||||||
|
|
||||||
return uniqueTags, err
|
return imageTags, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, manifest := range manifests {
|
for _, manifest := range manifests {
|
||||||
|
@ -42,7 +50,7 @@ func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]*strin
|
||||||
if err != nil {
|
if err != nil {
|
||||||
digestinfo.Log.Error().Err(err).Msg("unable to read image blob manifest")
|
digestinfo.Log.Error().Err(err).Msg("unable to read image blob manifest")
|
||||||
|
|
||||||
return uniqueTags, err
|
return imageTags, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := []*string{}
|
tags := []*string{}
|
||||||
|
@ -71,12 +79,12 @@ func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]*strin
|
||||||
|
|
||||||
for _, entry := range tags {
|
for _, entry := range tags {
|
||||||
if _, value := keys[*entry]; !value {
|
if _, value := keys[*entry]; !value {
|
||||||
uniqueTags = append(uniqueTags, entry)
|
imageTags = append(imageTags, ImageInfoByDigest{Tag: *entry, Digest: imageDigest, Manifest: imageBlobManifest})
|
||||||
keys[*entry] = true
|
keys[*entry] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniqueTags, nil
|
return imageTags, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
"zotregistry.io/zot/pkg/api"
|
"zotregistry.io/zot/pkg/api"
|
||||||
|
@ -45,8 +44,11 @@ type ImgListForDigest struct {
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
//nolint:tagliatelle // graphQL schema
|
||||||
type ImgInfo struct {
|
type ImgInfo struct {
|
||||||
Name string `json:"Name"`
|
RepoName string `json:"RepoName"`
|
||||||
Tags []string `json:"Tags"`
|
Tag string `json:"Tag"`
|
||||||
|
ConfigDigest string `json:"ConfigDigest"`
|
||||||
|
Digest string `json:"Digest"`
|
||||||
|
Size string `json:"Size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorGQL struct {
|
type ErrorGQL struct {
|
||||||
|
@ -97,15 +99,10 @@ func testSetup() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := config.New()
|
|
||||||
conf.Extensions = &extconf.ExtensionConfig{}
|
|
||||||
conf.Extensions.Lint = &extconf.LintConfig{}
|
|
||||||
|
|
||||||
log := log.NewLogger("debug", "")
|
log := log.NewLogger("debug", "")
|
||||||
metrics := monitoring.NewMetricsServer(false, log)
|
metrics := monitoring.NewMetricsServer(false, log)
|
||||||
storeController := storage.StoreController{
|
storeController := storage.StoreController{
|
||||||
DefaultStore: storage.NewImageStore(rootDir, false, storage.DefaultGCDelay,
|
DefaultStore: storage.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil),
|
||||||
false, false, log, metrics, nil),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
digestInfo = digestinfo.NewDigestInfo(storeController, log)
|
digestInfo = digestinfo.NewDigestInfo(storeController, log)
|
||||||
|
@ -115,33 +112,31 @@ func testSetup() error {
|
||||||
|
|
||||||
func TestDigestInfo(t *testing.T) {
|
func TestDigestInfo(t *testing.T) {
|
||||||
Convey("Test image tag", t, func() {
|
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
|
// Search by manifest digest
|
||||||
var (
|
imageTags, err := digestInfo.GetImageTagsByDigest("zot-cve-test", "63a795ca")
|
||||||
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))
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(imageTags), ShouldEqual, 1)
|
So(len(imageTags), ShouldEqual, 1)
|
||||||
So(*imageTags[0], ShouldEqual, "0.0.1")
|
So(imageTags[0].Tag, ShouldEqual, "0.0.1")
|
||||||
|
|
||||||
// Search by config digest
|
// Search by config digest
|
||||||
_, configDigest, _ = GetOciLayoutDigests("../../../../test/data/zot-test")
|
imageTags, err = digestInfo.GetImageTagsByDigest("zot-test", "adf3bb6c")
|
||||||
|
|
||||||
imageTags, err = digestInfo.GetImageTagsByDigest("zot-test", string(configDigest))
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(imageTags), ShouldEqual, 1)
|
So(len(imageTags), ShouldEqual, 1)
|
||||||
So(*imageTags[0], ShouldEqual, "0.0.1")
|
So(imageTags[0].Tag, ShouldEqual, "0.0.1")
|
||||||
|
|
||||||
// Search by layer digest
|
// 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(err, ShouldBeNil)
|
||||||
So(len(imageTags), ShouldEqual, 1)
|
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
|
// Search by non-existent image
|
||||||
imageTags, err = digestInfo.GetImageTagsByDigest("zot-tes", "63a795ca")
|
imageTags, err = digestInfo.GetImageTagsByDigest("zot-tes", "63a795ca")
|
||||||
|
@ -202,8 +197,10 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
So(resp.StatusCode(), ShouldEqual, 422)
|
||||||
|
|
||||||
// "sha" should match all digests in all images
|
// "sha" should match all digests in all images
|
||||||
resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix +
|
resp, err = resty.R().Get(
|
||||||
"?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}")
|
baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"sha")` +
|
||||||
|
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
|
||||||
|
)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -213,16 +210,14 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.Errors), ShouldEqual, 0)
|
So(len(responseStruct.Errors), ShouldEqual, 0)
|
||||||
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 2)
|
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 2)
|
||||||
So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1)
|
So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
|
||||||
So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1)
|
|
||||||
|
|
||||||
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}}
|
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}}
|
||||||
var layerDigest digest.Digest
|
// "2bacca16" should match the manifest of 1 image
|
||||||
var manifestDigest digest.Digest
|
resp, err = resty.R().Get(
|
||||||
manifestDigest, _, layerDigest = GetOciLayoutDigests("../../../../test/data/zot-test")
|
baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"2bacca16")` +
|
||||||
|
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
|
||||||
resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForDigest(id:\"" +
|
)
|
||||||
string(layerDigest) + "\"){Name%20Tags}}")
|
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -231,15 +226,14 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.Errors), ShouldEqual, 0)
|
So(len(responseStruct.Errors), ShouldEqual, 0)
|
||||||
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
|
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
|
||||||
So(responseStruct.ImgListForDigest.Images[0].Name, ShouldEqual, "zot-test")
|
So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-test")
|
||||||
So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1)
|
So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
|
||||||
So(responseStruct.ImgListForDigest.Images[0].Tags[0], ShouldEqual, "0.0.1")
|
|
||||||
|
|
||||||
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-test","Tags":["0.0.1"]}]}}
|
// "adf3bb6c" should match the config of 1 image
|
||||||
|
resp, err = resty.R().Get(
|
||||||
resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix +
|
baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"adf3bb6c")` +
|
||||||
"?query={ImageListForDigest(id:\"" +
|
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
|
||||||
string(manifestDigest) + "\"){Name%20Tags}}")
|
)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -248,15 +242,15 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.Errors), ShouldEqual, 0)
|
So(len(responseStruct.Errors), ShouldEqual, 0)
|
||||||
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
|
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
|
||||||
So(responseStruct.ImgListForDigest.Images[0].Name, ShouldEqual, "zot-test")
|
So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-test")
|
||||||
So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1)
|
So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
|
||||||
So(responseStruct.ImgListForDigest.Images[0].Tags[0], ShouldEqual, "0.0.1")
|
|
||||||
|
|
||||||
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-cve-test","Tags":["0.0.1"]}]}}
|
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-cve-test","Tags":["0.0.1"]}]}}
|
||||||
|
// "7a0437f0" should match the layer of 1 image
|
||||||
_, _, layerDigest = GetOciLayoutDigests("../../../../test/data/zot-cve-test")
|
resp, err = resty.R().Get(
|
||||||
resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix + "?query={ImageListForDigest(id:\"" +
|
baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"7a0437f0")` +
|
||||||
string(layerDigest) + "\"){Name%20Tags}}")
|
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
|
||||||
|
)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -265,14 +259,15 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(responseStruct.Errors), ShouldEqual, 0)
|
So(len(responseStruct.Errors), ShouldEqual, 0)
|
||||||
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
|
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 1)
|
||||||
So(responseStruct.ImgListForDigest.Images[0].Name, ShouldEqual, "zot-cve-test")
|
So(responseStruct.ImgListForDigest.Images[0].RepoName, ShouldEqual, "zot-cve-test")
|
||||||
So(len(responseStruct.ImgListForDigest.Images[0].Tags), ShouldEqual, 1)
|
So(responseStruct.ImgListForDigest.Images[0].Tag, ShouldEqual, "0.0.1")
|
||||||
So(responseStruct.ImgListForDigest.Images[0].Tags[0], ShouldEqual, "0.0.1")
|
|
||||||
|
|
||||||
// Call should return {"data":{"ImageListForDigest":[]}}
|
// Call should return {"data":{"ImageListForDigest":[]}}
|
||||||
// "1111111" should match 0 images
|
// "1111111" should match 0 images
|
||||||
resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix +
|
resp, err = resty.R().Get(
|
||||||
"?query={ImageListForDigest(id:\"1111111\"){Name%20Tags}}")
|
baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"1111111")` +
|
||||||
|
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
|
||||||
|
)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
@ -283,8 +278,10 @@ func TestDigestSearchHTTP(t *testing.T) {
|
||||||
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 0)
|
So(len(responseStruct.ImgListForDigest.Images), ShouldEqual, 0)
|
||||||
|
|
||||||
// Call should return {"errors": [{....}]", data":null}}
|
// Call should return {"errors": [{....}]", data":null}}
|
||||||
resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix +
|
resp, err = resty.R().Get(
|
||||||
"?query={ImageListForDigest(id:\"1111111\"){Name%20Tag343s}}")
|
baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"1111111")` +
|
||||||
|
`{RepoName%20Tag343s}}`,
|
||||||
|
)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
So(resp.StatusCode(), ShouldEqual, 422)
|
||||||
|
@ -354,8 +351,10 @@ func TestDigestSearchHTTPSubPaths(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 422)
|
So(resp.StatusCode(), ShouldEqual, 422)
|
||||||
|
|
||||||
resp, err = resty.R().Get(baseURL + constants.ExtSearchPrefix +
|
resp, err = resty.R().Get(
|
||||||
"?query={ImageListForDigest(id:\"sha\"){Name%20Tags}}")
|
baseURL + constants.ExtSearchPrefix + `?query={ImageListForDigest(id:"sha")` +
|
||||||
|
`{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}`,
|
||||||
|
)
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,45 +25,22 @@ type GlobalSearchResult struct {
|
||||||
Layers []*LayerSummary `json:"Layers"`
|
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 {
|
type ImageSummary struct {
|
||||||
RepoName *string `json:"RepoName"`
|
RepoName *string `json:"RepoName"`
|
||||||
Tag *string `json:"Tag"`
|
Tag *string `json:"Tag"`
|
||||||
LastUpdated *time.Time `json:"LastUpdated"`
|
Digest *string `json:"Digest"`
|
||||||
IsSigned *bool `json:"IsSigned"`
|
ConfigDigest *string `json:"ConfigDigest"`
|
||||||
Size *string `json:"Size"`
|
LastUpdated *time.Time `json:"LastUpdated"`
|
||||||
Platform *OsArch `json:"Platform"`
|
IsSigned *bool `json:"IsSigned"`
|
||||||
Vendor *string `json:"Vendor"`
|
Size *string `json:"Size"`
|
||||||
Score *int `json:"Score"`
|
Platform *OsArch `json:"Platform"`
|
||||||
}
|
Vendor *string `json:"Vendor"`
|
||||||
|
Score *int `json:"Score"`
|
||||||
type ImgResultForCve struct {
|
DownloadCount *int `json:"DownloadCount"`
|
||||||
Name *string `json:"Name"`
|
Layers []*LayerSummary `json:"Layers"`
|
||||||
Tags []*string `json:"Tags"`
|
Description *string `json:"Description"`
|
||||||
}
|
Licenses *string `json:"Licenses"`
|
||||||
|
Labels *string `json:"Labels"`
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayerSummary struct {
|
type LayerSummary struct {
|
||||||
|
@ -72,13 +49,6 @@ type LayerSummary struct {
|
||||||
Score *int `json:"Score"`
|
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 {
|
type OsArch struct {
|
||||||
Os *string `json:"Os"`
|
Os *string `json:"Os"`
|
||||||
Arch *string `json:"Arch"`
|
Arch *string `json:"Arch"`
|
||||||
|
@ -91,22 +61,19 @@ type PackageInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoInfo struct {
|
type RepoInfo struct {
|
||||||
Manifests []*ManifestInfo `json:"Manifests"`
|
Images []*ImageSummary `json:"Images"`
|
||||||
Summary *RepoSummary `json:"Summary"`
|
Summary *RepoSummary `json:"Summary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoSummary struct {
|
type RepoSummary struct {
|
||||||
Name *string `json:"Name"`
|
Name *string `json:"Name"`
|
||||||
LastUpdated *time.Time `json:"LastUpdated"`
|
LastUpdated *time.Time `json:"LastUpdated"`
|
||||||
Size *string `json:"Size"`
|
Size *string `json:"Size"`
|
||||||
Platforms []*OsArch `json:"Platforms"`
|
Platforms []*OsArch `json:"Platforms"`
|
||||||
Vendors []*string `json:"Vendors"`
|
Vendors []*string `json:"Vendors"`
|
||||||
Score *int `json:"Score"`
|
Score *int `json:"Score"`
|
||||||
NewestTag *ImageSummary `json:"NewestTag"`
|
NewestImage *ImageSummary `json:"NewestImage"`
|
||||||
}
|
DownloadCount *int `json:"DownloadCount"`
|
||||||
|
StarCount *int `json:"StarCount"`
|
||||||
type TagInfo struct {
|
IsBookmarked *bool `json:"IsBookmarked"`
|
||||||
Name *string `json:"Name"`
|
|
||||||
Digest *string `json:"Digest"`
|
|
||||||
Timestamp *time.Time `json:"Timestamp"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
"zotregistry.io/zot/pkg/log" // nolint: gci
|
"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,
|
func (r *queryResolver) getImageListForCVE(repoList []string, cvid string, imgStore storage.ImageStore,
|
||||||
trivyCtx *cveinfo.TrivyCtx,
|
trivyCtx *cveinfo.TrivyCtx,
|
||||||
) ([]*gql_generated.ImgResultForCve, error) {
|
) ([]*gql_generated.ImageSummary, error) {
|
||||||
cveResult := []*gql_generated.ImgResultForCve{}
|
cveResult := []*gql_generated.ImageSummary{}
|
||||||
|
|
||||||
for _, repo := range repoList {
|
for _, repo := range repoList {
|
||||||
r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo")
|
r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo")
|
||||||
|
|
||||||
name := repo
|
imageListByCVE, err := r.cveInfo.GetImageListForCVE(repo, cvid, imgStore, trivyCtx)
|
||||||
|
|
||||||
tags, err := r.cveInfo.GetImageListForCVE(repo, cvid, imgStore, trivyCtx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.log.Error().Err(err).Msg("error getting tag")
|
r.log.Error().Err(err).Msg("error getting tag")
|
||||||
|
|
||||||
return cveResult, err
|
return cveResult, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tags) != 0 {
|
for _, imageByCVE := range imageListByCVE {
|
||||||
cveResult = append(cveResult, &gql_generated.ImgResultForCve{Name: &name, Tags: tags})
|
cveResult = append(
|
||||||
|
cveResult,
|
||||||
|
buildImageInfo(repo, imageByCVE.Tag, imageByCVE.Digest, imageByCVE.Manifest),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cveResult, nil
|
return cveResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) getImageListForDigest(repoList []string,
|
func (r *queryResolver) getImageListForDigest(repoList []string, digest string) ([]*gql_generated.ImageSummary, error) {
|
||||||
digest string,
|
imgResultForDigest := []*gql_generated.ImageSummary{}
|
||||||
) ([]*gql_generated.ImgResultForDigest, error) {
|
|
||||||
imgResultForDigest := []*gql_generated.ImgResultForDigest{}
|
|
||||||
|
|
||||||
var errResult error
|
var errResult error
|
||||||
|
|
||||||
for _, repo := range repoList {
|
for _, repo := range repoList {
|
||||||
r.log.Info().Str("repo", repo).Msg("filtering list of tags in image repo by digest")
|
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 {
|
if err != nil {
|
||||||
r.log.Error().Err(err).Msg("unable to get filtered list of image tags")
|
r.log.Error().Err(err).Msg("unable to get filtered list of image tags")
|
||||||
|
|
||||||
errResult = err
|
return []*gql_generated.ImageSummary{}, err
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tags) != 0 {
|
for _, imageInfo := range imgTags {
|
||||||
name := repo
|
imageInfo := buildImageInfo(repo, imageInfo.Tag, imageInfo.Digest, imageInfo.Manifest)
|
||||||
|
imgResultForDigest = append(imgResultForDigest, imageInfo)
|
||||||
imgResultForDigest = append(imgResultForDigest, &gql_generated.ImgResultForDigest{Name: &name, Tags: tags})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return imgResultForDigest, errResult
|
return imgResultForDigest, errResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*gql_generated.ImageInfo, error) {
|
func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*gql_generated.ImageSummary, error) {
|
||||||
results := make([]*gql_generated.ImageInfo, 0)
|
results := make([]*gql_generated.ImageSummary, 0)
|
||||||
|
|
||||||
repoList, err := store.GetRepositories()
|
repoList, err := store.GetRepositories()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -167,7 +164,6 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*
|
||||||
labels := imageConfig.Config.Labels
|
labels := imageConfig.Config.Labels
|
||||||
|
|
||||||
// Read Description
|
// Read Description
|
||||||
|
|
||||||
desc := common.GetDescription(labels)
|
desc := common.GetDescription(labels)
|
||||||
|
|
||||||
// Read licenses
|
// Read licenses
|
||||||
|
@ -179,8 +175,8 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*
|
||||||
// Read categories
|
// Read categories
|
||||||
categories := common.GetCategories(labels)
|
categories := common.GetCategories(labels)
|
||||||
|
|
||||||
results = append(results, &gql_generated.ImageInfo{
|
results = append(results, &gql_generated.ImageSummary{
|
||||||
Name: &name, Latest: &latestTag.Name,
|
RepoName: &name, Tag: &latestTag.Name,
|
||||||
Description: &desc, Licenses: &license, Vendor: &vendor,
|
Description: &desc, Licenses: &license, Vendor: &vendor,
|
||||||
Labels: &categories, Size: &size, LastUpdated: &latestTag.Timestamp,
|
Labels: &categories, Size: &size, LastUpdated: &latestTag.Timestamp,
|
||||||
})
|
})
|
||||||
|
@ -336,7 +332,7 @@ func globalSearch(repoList []string, name, tag string, olu common.OciLayoutUtils
|
||||||
Platforms: repoPlatforms,
|
Platforms: repoPlatforms,
|
||||||
Vendors: repoVendors,
|
Vendors: repoVendors,
|
||||||
Score: &index,
|
Score: &index,
|
||||||
NewestTag: &lastUpdatedImageSummary,
|
NewestImage: &lastUpdatedImageSummary,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,15 +378,94 @@ func calculateImageMatchingScore(artefactName string, index int, matchesTag bool
|
||||||
return score
|
return score
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGraphqlCompatibleTags(fixedTags []common.TagInfo) []*gql_generated.TagInfo {
|
func (r *queryResolver) getImageList(store storage.ImageStore, imageName string) (
|
||||||
finalTagList := make([]*gql_generated.TagInfo, 0)
|
[]*gql_generated.ImageSummary, error,
|
||||||
|
) {
|
||||||
|
results := make([]*gql_generated.ImageSummary, 0)
|
||||||
|
|
||||||
for _, tag := range fixedTags {
|
repoList, err := store.GetRepositories()
|
||||||
fixTag := tag
|
if err != nil {
|
||||||
|
r.log.Error().Err(err).Msg("extension api: error extracting repositories list")
|
||||||
|
|
||||||
finalTagList = append(finalTagList,
|
return results, err
|
||||||
&gql_generated.TagInfo{Name: &fixTag.Name, Digest: &fixTag.Digest, Timestamp: &fixTag.Timestamp})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
mockOlum := mocks.OciLayoutUtilsMock{
|
mockOlum := mocks.OciLayoutUtilsMock{
|
||||||
GetExpandedRepoInfoFn: func(name string) (common.RepoInfo, error) {
|
GetExpandedRepoInfoFn: func(name string) (common.RepoInfo, error) {
|
||||||
return common.RepoInfo{
|
return common.RepoInfo{
|
||||||
Manifests: []common.Manifest{
|
Images: []common.Image{
|
||||||
{
|
{
|
||||||
Tag: "latest",
|
Tag: "latest",
|
||||||
Layers: []common.Layer{
|
Layers: []common.Layer{
|
||||||
|
|
|
@ -1,123 +1,91 @@
|
||||||
scalar Time
|
scalar Time
|
||||||
|
|
||||||
type CVEResultForImage {
|
type CVEResultForImage {
|
||||||
Tag: String
|
Tag: String
|
||||||
CVEList: [CVE]
|
CVEList: [CVE]
|
||||||
}
|
}
|
||||||
|
|
||||||
type CVE {
|
type CVE {
|
||||||
Id: String
|
Id: String
|
||||||
Title: String
|
Title: String
|
||||||
Description: String
|
Description: String
|
||||||
Severity: String
|
Severity: String
|
||||||
PackageList: [PackageInfo]
|
PackageList: [PackageInfo]
|
||||||
}
|
}
|
||||||
|
|
||||||
type PackageInfo {
|
type PackageInfo {
|
||||||
Name: String
|
Name: String
|
||||||
InstalledVersion: String
|
InstalledVersion: String
|
||||||
FixedVersion: 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoInfo {
|
type RepoInfo {
|
||||||
Manifests: [ManifestInfo]
|
Images: [ImageSummary]
|
||||||
Summary: RepoSummary
|
Summary: RepoSummary
|
||||||
}
|
|
||||||
|
|
||||||
type ManifestInfo {
|
|
||||||
Digest: String
|
|
||||||
Tag: String
|
|
||||||
IsSigned: Boolean
|
|
||||||
Layers: [LayerInfo]
|
|
||||||
}
|
|
||||||
|
|
||||||
type LayerInfo {
|
|
||||||
Size: String # Int64 is not supported.
|
|
||||||
Digest: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Search results in all repos/images/layers
|
# Search results in all repos/images/layers
|
||||||
# There will be other more structures for more detailed information
|
# There will be other more structures for more detailed information
|
||||||
type GlobalSearchResult {
|
type GlobalSearchResult {
|
||||||
Images: [ImageSummary]
|
Images: [ImageSummary]
|
||||||
Repos: [RepoSummary]
|
Repos: [RepoSummary]
|
||||||
Layers: [LayerSummary]
|
Layers: [LayerSummary]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Brief on a specific image to be used in queries returning a list of images
|
# 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
|
# We define an image as a pairing or a repo and a tag belonging to that repo
|
||||||
type ImageSummary {
|
type ImageSummary {
|
||||||
RepoName: String
|
RepoName: String
|
||||||
Tag: String
|
Tag: String
|
||||||
LastUpdated: Time
|
Digest: String
|
||||||
IsSigned: Boolean
|
ConfigDigest: String
|
||||||
Size: String
|
LastUpdated: Time
|
||||||
Platform: OsArch
|
IsSigned: Boolean
|
||||||
Vendor: String
|
Size: String
|
||||||
Score: Int
|
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
|
# Brief on a specific repo to be used in queries returning a list of repos
|
||||||
type RepoSummary {
|
type RepoSummary {
|
||||||
Name: String
|
Name: String
|
||||||
LastUpdated: Time
|
LastUpdated: Time
|
||||||
Size: String
|
Size: String
|
||||||
Platforms: [OsArch]
|
Platforms: [OsArch]
|
||||||
Vendors: [String]
|
Vendors: [String]
|
||||||
Score: Int
|
Score: Int
|
||||||
NewestTag: ImageSummary
|
NewestImage: ImageSummary
|
||||||
|
DownloadCount: Int
|
||||||
|
StarCount: Int
|
||||||
|
IsBookmarked: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
# Currently the same as LayerInfo, we can refactor later
|
# Currently the same as LayerInfo, we can refactor later
|
||||||
# For detailed information on the layer a ImageListForDigest call can be made
|
# For detailed information on the layer a ImageListForDigest call can be made
|
||||||
type LayerSummary {
|
type LayerSummary {
|
||||||
Size: String # Int64 is not supported.
|
Size: String # Int64 is not supported.
|
||||||
Digest: String
|
Digest: String
|
||||||
Score: Int
|
Score: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type OsArch {
|
type OsArch {
|
||||||
Os: String
|
Os: String
|
||||||
Arch: String
|
Arch: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
CVEListForImage(image: String!) :CVEResultForImage
|
CVEListForImage(image: String!): CVEResultForImage!
|
||||||
ImageListForCVE(id: String!) :[ImgResultForCVE]
|
ImageListForCVE(id: String!): [ImageSummary!]
|
||||||
ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE
|
ImageListWithCVEFixed(id: String!, image: String!): [ImageSummary!]
|
||||||
ImageListForDigest(id: String!) :[ImgResultForDigest]
|
ImageListForDigest(id: String!): [ImageSummary!]
|
||||||
ImageListWithLatestTag:[ImageInfo]
|
ImageListWithLatestTag: [ImageSummary!]
|
||||||
ExpandedRepoInfo(repo: String!):RepoInfo
|
ImageList(repo: String!): [ImageSummary!]
|
||||||
GlobalSearch(query: String!): GlobalSearchResult
|
ExpandedRepoInfo(repo: String!): RepoInfo!
|
||||||
|
GlobalSearch(query: String!): GlobalSearchResult!
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
godigest "github.com/opencontainers/go-digest"
|
||||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||||
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
"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.
|
// ImageListForCve is the resolver for the ImageListForCVE field.
|
||||||
func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_generated.ImgResultForCve, error) {
|
func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) {
|
||||||
finalCveResult := []*gql_generated.ImgResultForCve{}
|
finalCveResult := []*gql_generated.ImageSummary{}
|
||||||
|
|
||||||
r.log.Info().Msg("extracting repositories")
|
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.
|
// ImageListWithCVEFixed is the resolver for the ImageListWithCVEFixed field.
|
||||||
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) (*gql_generated.ImgResultForFixedCve, error) {
|
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*gql_generated.ImageSummary, error) {
|
||||||
imgResultForFixedCVE := &gql_generated.ImgResultForFixedCve{}
|
tagListForCVE := []*gql_generated.ImageSummary{}
|
||||||
|
|
||||||
r.log.Info().Str("image", image).Msg("extracting list of tags available in image")
|
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 {
|
if err != nil {
|
||||||
r.log.Error().Err(err).Msg("unable to read image tags")
|
r.log.Error().Err(err).Msg("unable to read image tags")
|
||||||
|
|
||||||
return imgResultForFixedCVE, err
|
return tagListForCVE, err
|
||||||
}
|
}
|
||||||
|
|
||||||
infectedTags := make([]common.TagInfo, 0)
|
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 {
|
if len(infectedTags) != 0 {
|
||||||
r.log.Info().Msg("comparing fixed tags timestamp")
|
r.log.Info().Msg("comparing fixed tags timestamp")
|
||||||
|
|
||||||
fixedTags := common.GetFixedTags(tagsInfo, infectedTags)
|
tagsInfo = common.GetFixedTags(tagsInfo, infectedTags)
|
||||||
|
|
||||||
finalTagList = getGraphqlCompatibleTags(fixedTags)
|
|
||||||
} else {
|
} else {
|
||||||
r.log.Info().Str("image", image).Str("cve-id", id).Msg("image does not contain any tag that have given cve")
|
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.
|
// ImageListForDigest is the resolver for the ImageListForDigest field.
|
||||||
func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*gql_generated.ImgResultForDigest, error) {
|
func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) {
|
||||||
imgResultForDigest := []*gql_generated.ImgResultForDigest{}
|
imgResultForDigest := []*gql_generated.ImageSummary{}
|
||||||
|
|
||||||
r.log.Info().Msg("extracting repositories")
|
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.
|
// 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")
|
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
|
defaultStore := r.storeController.DefaultStore
|
||||||
|
|
||||||
|
@ -317,6 +324,43 @@ func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*gql_gene
|
||||||
return imageList, nil
|
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.
|
// ExpandedRepoInfo is the resolver for the ExpandedRepoInfo field.
|
||||||
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql_generated.RepoInfo, error) {
|
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql_generated.RepoInfo, error) {
|
||||||
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
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
|
// repos type is of common deep copy this to search
|
||||||
repoInfo := &gql_generated.RepoInfo{}
|
repoInfo := &gql_generated.RepoInfo{}
|
||||||
|
|
||||||
manifests := make([]*gql_generated.ManifestInfo, 0)
|
images := make([]*gql_generated.ImageSummary, 0)
|
||||||
|
|
||||||
summary := &gql_generated.RepoSummary{}
|
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
|
score := -1 // score not relevant for this query
|
||||||
summary.Score = &score
|
summary.Score = &score
|
||||||
|
|
||||||
for _, manifest := range origRepoInfo.Manifests {
|
for _, image := range origRepoInfo.Images {
|
||||||
tag := manifest.Tag
|
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
|
size := l.Size
|
||||||
|
|
||||||
digest := l.Digest
|
digest := l.Digest
|
||||||
|
|
||||||
layerInfo := &gql_generated.LayerInfo{Digest: &digest, Size: &size}
|
layerInfo := &gql_generated.LayerSummary{Digest: &digest, Size: &size}
|
||||||
|
|
||||||
layers = append(layers, layerInfo)
|
layers = append(layers, layerInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestInfo.Layers = layers
|
imageSummary.Layers = layers
|
||||||
|
|
||||||
manifests = append(manifests, manifestInfo)
|
images = append(images, imageSummary)
|
||||||
}
|
}
|
||||||
|
|
||||||
repoInfo.Summary = summary
|
repoInfo.Summary = summary
|
||||||
repoInfo.Manifests = manifests
|
repoInfo.Images = images
|
||||||
|
|
||||||
return repoInfo, nil
|
return repoInfo, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue