mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
refactor(cli): added equivalent subcommands for each flag combination under every command (#1674)
- image command is now deprecated in favor of 'images' - cve command is now deprecated in favor of 'cves' Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
parent
2bd479edd7
commit
112fbec5b6
49 changed files with 3857 additions and 315 deletions
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
@ -19,6 +18,7 @@ import (
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api"
|
"zotregistry.io/zot/pkg/api"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
)
|
)
|
||||||
|
@ -32,7 +32,8 @@ func makeHTTPGetRequest(url string, resultPtr interface{}, client *resty.Client)
|
||||||
if resp.StatusCode() != http.StatusOK {
|
if resp.StatusCode() != http.StatusOK {
|
||||||
log.Printf("unable to make GET request on %s, response status code: %d", url, resp.StatusCode())
|
log.Printf("unable to make GET request on %s, response status code: %d", url, resp.StatusCode())
|
||||||
|
|
||||||
return errors.New(string(resp.Body())) //nolint: goerr113
|
return fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusOK,
|
||||||
|
resp.StatusCode(), string(resp.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(resp.Body(), resultPtr)
|
err = json.Unmarshal(resp.Body(), resultPtr)
|
||||||
|
@ -52,7 +53,8 @@ func makeHTTPDeleteRequest(url string, client *resty.Client) error {
|
||||||
if resp.StatusCode() != http.StatusAccepted {
|
if resp.StatusCode() != http.StatusAccepted {
|
||||||
log.Printf("unable to make DELETE request on %s, response status code: %d", url, resp.StatusCode())
|
log.Printf("unable to make DELETE request on %s, response status code: %d", url, resp.StatusCode())
|
||||||
|
|
||||||
return errors.New(string(resp.Body())) //nolint: goerr113
|
return fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusAccepted,
|
||||||
|
resp.StatusCode(), string(resp.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -336,7 +338,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
||||||
// request specific check
|
// request specific check
|
||||||
statusCode = resp.StatusCode()
|
statusCode = resp.StatusCode()
|
||||||
if statusCode != http.StatusAccepted {
|
if statusCode != http.StatusAccepted {
|
||||||
return nil, repos, errors.New(string(resp.Body())) //nolint: goerr113
|
return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusAccepted,
|
||||||
|
resp.StatusCode(), string(resp.Body())) //nolint: goerr113
|
||||||
}
|
}
|
||||||
|
|
||||||
loc := test.Location(url, resp)
|
loc := test.Location(url, resp)
|
||||||
|
@ -374,7 +377,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
||||||
// request specific check
|
// request specific check
|
||||||
statusCode = resp.StatusCode()
|
statusCode = resp.StatusCode()
|
||||||
if statusCode != http.StatusCreated {
|
if statusCode != http.StatusCreated {
|
||||||
return nil, repos, errors.New(string(resp.Body())) //nolint: goerr113
|
return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusCreated,
|
||||||
|
resp.StatusCode(), string(resp.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// upload image config blob
|
// upload image config blob
|
||||||
|
@ -388,7 +392,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
||||||
// request specific check
|
// request specific check
|
||||||
statusCode = resp.StatusCode()
|
statusCode = resp.StatusCode()
|
||||||
if statusCode != http.StatusAccepted {
|
if statusCode != http.StatusAccepted {
|
||||||
return nil, repos, errors.New(string(resp.Body())) //nolint: goerr113
|
return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusAccepted,
|
||||||
|
resp.StatusCode(), string(resp.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
loc = test.Location(url, resp)
|
loc = test.Location(url, resp)
|
||||||
|
@ -408,7 +413,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
||||||
// request specific check
|
// request specific check
|
||||||
statusCode = resp.StatusCode()
|
statusCode = resp.StatusCode()
|
||||||
if statusCode != http.StatusCreated {
|
if statusCode != http.StatusCreated {
|
||||||
return nil, repos, errors.New(string(resp.Body())) //nolint: goerr113
|
return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusCreated,
|
||||||
|
resp.StatusCode(), string(resp.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a manifest
|
// create a manifest
|
||||||
|
@ -451,7 +457,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
||||||
// request specific check
|
// request specific check
|
||||||
statusCode = resp.StatusCode()
|
statusCode = resp.StatusCode()
|
||||||
if statusCode != http.StatusCreated {
|
if statusCode != http.StatusCreated {
|
||||||
return nil, repos, errors.New(string(resp.Body())) //nolint: goerr113
|
return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusCreated,
|
||||||
|
resp.StatusCode(), string(resp.Body()))
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestHash[repo] = manifestTag
|
manifestHash[repo] = manifestTag
|
||||||
|
|
|
@ -74,7 +74,7 @@ var (
|
||||||
ErrInvalidArgs = errors.New("cli: Invalid Arguments")
|
ErrInvalidArgs = errors.New("cli: Invalid Arguments")
|
||||||
ErrInvalidFlagsCombination = errors.New("cli: Invalid combination of flags")
|
ErrInvalidFlagsCombination = errors.New("cli: Invalid combination of flags")
|
||||||
ErrInvalidURL = errors.New("cli: invalid URL format")
|
ErrInvalidURL = errors.New("cli: invalid URL format")
|
||||||
ErrExtensionNotEnabled = errors.New("cli: functionality is not built in current version")
|
ErrExtensionNotEnabled = errors.New("cli: functionality is not built/configured in the current server")
|
||||||
ErrUnauthorizedAccess = errors.New("auth: unauthorized access. check credentials")
|
ErrUnauthorizedAccess = errors.New("auth: unauthorized access. check credentials")
|
||||||
ErrCannotResetConfigKey = errors.New("cli: cannot reset given config key")
|
ErrCannotResetConfigKey = errors.New("cli: cannot reset given config key")
|
||||||
ErrConfigNotFound = errors.New("cli: config with the given name does not exist")
|
ErrConfigNotFound = errors.New("cli: config with the given name does not exist")
|
||||||
|
@ -115,7 +115,7 @@ var (
|
||||||
ErrEmptyRepoName = errors.New("metadb: repo name can't be empty string")
|
ErrEmptyRepoName = errors.New("metadb: repo name can't be empty string")
|
||||||
ErrEmptyTag = errors.New("metadb: tag can't be empty string")
|
ErrEmptyTag = errors.New("metadb: tag can't be empty string")
|
||||||
ErrEmptyDigest = errors.New("metadb: digest can't be empty string")
|
ErrEmptyDigest = errors.New("metadb: digest can't be empty string")
|
||||||
ErrInvalidRepoRefFormat = errors.New("invalid image reference format")
|
ErrInvalidRepoRefFormat = errors.New("invalid image reference format [repo:tag] or [repo@digest]")
|
||||||
ErrLimitIsNegative = errors.New("pageturner: limit has negative value")
|
ErrLimitIsNegative = errors.New("pageturner: limit has negative value")
|
||||||
ErrOffsetIsNegative = errors.New("pageturner: offset has negative value")
|
ErrOffsetIsNegative = errors.New("pageturner: offset has negative value")
|
||||||
ErrSortCriteriaNotSupported = errors.New("pageturner: the sort criteria is not supported")
|
ErrSortCriteriaNotSupported = errors.New("pageturner: the sort criteria is not supported")
|
||||||
|
@ -151,4 +151,8 @@ var (
|
||||||
ErrInvalidPublicKeyContent = errors.New("signatures: invalid public key content")
|
ErrInvalidPublicKeyContent = errors.New("signatures: invalid public key content")
|
||||||
ErrInvalidStateCookie = errors.New("auth: state cookie not present or differs from original state")
|
ErrInvalidStateCookie = errors.New("auth: state cookie not present or differs from original state")
|
||||||
ErrSyncNoURLsLeft = errors.New("sync: no valid registry urls left after filtering local ones")
|
ErrSyncNoURLsLeft = errors.New("sync: no valid registry urls left after filtering local ones")
|
||||||
|
ErrInvalidCLIParameter = errors.New("cli: invalid parameter")
|
||||||
|
ErrGQLEndpointNotFound = errors.New("cli: the server doesn't have a gql endpoint")
|
||||||
|
ErrGQLQueryNotSupported = errors.New("cli: query is not supported or has different arguments")
|
||||||
|
ErrBadHTTPStatusCode = errors.New("cli: the response doesn't contain the expected status code")
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,8 @@ const (
|
||||||
ExtCatalogPrefix = "/_catalog"
|
ExtCatalogPrefix = "/_catalog"
|
||||||
ExtOciDiscoverPrefix = "/_oci/ext/discover"
|
ExtOciDiscoverPrefix = "/_oci/ext/discover"
|
||||||
|
|
||||||
|
BaseExtension = "_zot"
|
||||||
|
|
||||||
// zot specific extensions.
|
// zot specific extensions.
|
||||||
BasePrefix = "/_zot"
|
BasePrefix = "/_zot"
|
||||||
ExtPrefix = BasePrefix + "/ext"
|
ExtPrefix = BasePrefix + "/ext"
|
||||||
|
|
|
@ -8491,7 +8491,7 @@ func TestDistSpecExtensions(t *testing.T) {
|
||||||
t.Log(extensionList.Extensions)
|
t.Log(extensionList.Extensions)
|
||||||
So(len(extensionList.Extensions), ShouldEqual, 1)
|
So(len(extensionList.Extensions), ShouldEqual, 1)
|
||||||
So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 5)
|
So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 5)
|
||||||
So(extensionList.Extensions[0].Name, ShouldEqual, "_zot")
|
So(extensionList.Extensions[0].Name, ShouldEqual, constants.BaseExtension)
|
||||||
So(extensionList.Extensions[0].URL, ShouldContainSubstring, "_zot.md")
|
So(extensionList.Extensions[0].URL, ShouldContainSubstring, "_zot.md")
|
||||||
So(extensionList.Extensions[0].Description, ShouldNotBeEmpty)
|
So(extensionList.Extensions[0].Description, ShouldNotBeEmpty)
|
||||||
// Verify the endpoints below are enabled by search
|
// Verify the endpoints below are enabled by search
|
||||||
|
@ -8539,7 +8539,7 @@ func TestDistSpecExtensions(t *testing.T) {
|
||||||
t.Log(extensionList.Extensions)
|
t.Log(extensionList.Extensions)
|
||||||
So(len(extensionList.Extensions), ShouldEqual, 1)
|
So(len(extensionList.Extensions), ShouldEqual, 1)
|
||||||
So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 2)
|
So(len(extensionList.Extensions[0].Endpoints), ShouldEqual, 2)
|
||||||
So(extensionList.Extensions[0].Name, ShouldEqual, "_zot")
|
So(extensionList.Extensions[0].Name, ShouldEqual, constants.BaseExtension)
|
||||||
So(extensionList.Extensions[0].URL, ShouldContainSubstring, "_zot.md")
|
So(extensionList.Extensions[0].URL, ShouldContainSubstring, "_zot.md")
|
||||||
So(extensionList.Extensions[0].Description, ShouldNotBeEmpty)
|
So(extensionList.Extensions[0].Description, ShouldNotBeEmpty)
|
||||||
// Verify the endpoints below are enabled by search
|
// Verify the endpoints below are enabled by search
|
||||||
|
|
|
@ -717,7 +717,7 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
|
||||||
rh.c.Log.Error().Err(err).Msg("unexpected error: performing cleanup")
|
rh.c.Log.Error().Err(err).Msg("unexpected error: performing cleanup")
|
||||||
|
|
||||||
if err = imgStore.DeleteImageManifest(name, reference, false); err != nil {
|
if err = imgStore.DeleteImageManifest(name, reference, false); err != nil {
|
||||||
// deletion of image manifest is important, but not critical for image repo consistancy
|
// deletion of image manifest is important, but not critical for image repo consistency
|
||||||
// in the worst scenario a partial manifest file written to disk will not affect the repo because
|
// in the worst scenario a partial manifest file written to disk will not affect the repo because
|
||||||
// the new manifest was not added to "index.json" file (it is possible that GC will take care of it)
|
// the new manifest was not added to "index.json" file (it is possible that GC will take care of it)
|
||||||
rh.c.Log.Error().Err(err).Str("repository", name).Str("reference", reference).
|
rh.c.Log.Error().Err(err).Str("repository", name).Str("reference", reference).
|
||||||
|
|
|
@ -8,7 +8,9 @@ import "github.com/spf13/cobra"
|
||||||
func enableCli(rootCmd *cobra.Command) {
|
func enableCli(rootCmd *cobra.Command) {
|
||||||
rootCmd.AddCommand(NewConfigCommand())
|
rootCmd.AddCommand(NewConfigCommand())
|
||||||
rootCmd.AddCommand(NewImageCommand(NewSearchService()))
|
rootCmd.AddCommand(NewImageCommand(NewSearchService()))
|
||||||
|
rootCmd.AddCommand(NewImagesCommand(NewSearchService()))
|
||||||
rootCmd.AddCommand(NewCveCommand(NewSearchService()))
|
rootCmd.AddCommand(NewCveCommand(NewSearchService()))
|
||||||
|
rootCmd.AddCommand(NewCVESCommand(NewSearchService()))
|
||||||
rootCmd.AddCommand(NewRepoCommand(NewSearchService()))
|
rootCmd.AddCommand(NewRepoCommand(NewSearchService()))
|
||||||
rootCmd.AddCommand(NewSearchCommand(NewSearchService()))
|
rootCmd.AddCommand(NewSearchCommand(NewSearchService()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -21,7 +20,7 @@ import (
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/sigstore/cosign/v2/pkg/oci/remote"
|
"github.com/sigstore/cosign/v2/pkg/oci/remote"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/common"
|
"zotregistry.io/zot/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,19 +123,20 @@ func doHTTPRequest(req *http.Request, verifyTLS bool, debug bool,
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
fmt.Fprintln(configWriter, "[debug] ", req.Method, req.URL, "[status] ",
|
fmt.Fprintln(configWriter, "[debug] ", req.Method, req.URL, "[status] ",
|
||||||
resp.StatusCode, " ", "[respoonse header] ", resp.Header)
|
resp.StatusCode, " ", "[response header] ", resp.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
if resp.StatusCode == http.StatusUnauthorized {
|
if resp.StatusCode == http.StatusUnauthorized {
|
||||||
return nil, zotErrors.ErrUnauthorizedAccess
|
return nil, zerr.ErrUnauthorizedAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
return nil, errors.New(string(bodyBytes)) //nolint: goerr113
|
return nil, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusOK,
|
||||||
|
resp.StatusCode, string(bodyBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
if resultsPtr == nil {
|
if resultsPtr == nil {
|
||||||
|
|
13
pkg/cli/cmdflags/flags.go
Normal file
13
pkg/cli/cmdflags/flags.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package cmdflags
|
||||||
|
|
||||||
|
const (
|
||||||
|
URLFlag = "url"
|
||||||
|
ConfigFlag = "config"
|
||||||
|
UserFlag = "user"
|
||||||
|
OutputFormatFlag = "format"
|
||||||
|
FixedFlag = "fixed"
|
||||||
|
VerboseFlag = "verbose"
|
||||||
|
VersionFlag = "version"
|
||||||
|
DebugFlag = "debug"
|
||||||
|
SearchedCVEID = "cve-id"
|
||||||
|
)
|
|
@ -40,7 +40,7 @@ func NewConfigCommand() *cobra.Command {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case noArgs:
|
case noArgs:
|
||||||
if isListing { // zot config -l
|
if isListing { // zot config -l
|
||||||
|
@ -115,7 +115,7 @@ func NewConfigAddCommand() *cobra.Command {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
// zot config add <config-name> <url>
|
// zot config add <config-name> <url>
|
||||||
err = addConfig(configPath, args[0], args[1])
|
err = addConfig(configPath, args[0], args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -145,7 +145,7 @@ func NewConfigRemoveCommand() *cobra.Command {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
// zot config add <config-name> <url>
|
// zot config add <config-name> <url>
|
||||||
err = removeConfig(configPath, args[0])
|
err = removeConfig(configPath, args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfigCmdBasics(t *testing.T) {
|
func TestConfigCmdBasics(t *testing.T) {
|
||||||
|
@ -146,7 +146,7 @@ func TestConfigCmdMain(t *testing.T) {
|
||||||
cmd.SetErr(buff)
|
cmd.SetErr(buff)
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldEqual, zotErrors.ErrCliBadConfig)
|
So(err, ShouldEqual, zerr.ErrCliBadConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test add config with invalid URL", t, func() {
|
Convey("Test add config with invalid URL", t, func() {
|
||||||
|
@ -160,7 +160,7 @@ func TestConfigCmdMain(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test remove config entry successfully", t, func() {
|
Convey("Test remove config entry successfully", t, func() {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -29,15 +29,15 @@ func NewCveCommand(searchService SearchService) *cobra.Command {
|
||||||
|
|
||||||
cveCmd := &cobra.Command{
|
cveCmd := &cobra.Command{
|
||||||
Use: "cve [config-name]",
|
Use: "cve [config-name]",
|
||||||
Short: "Lookup CVEs in images hosted on the zot registry",
|
Short: "DEPRECATED (see cves)",
|
||||||
Long: `List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
|
Long: `DEPRECATED (see cves)! List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
if servURL == "" {
|
if servURL == "" {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||||
|
@ -48,12 +48,12 @@ func NewCveCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if urlFromConfig == "" {
|
if urlFromConfig == "" {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
servURL = urlFromConfig
|
servURL = urlFromConfig
|
||||||
} else {
|
} else {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ func searchCve(searchConfig searchConfig) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(err.Error(), zotErrors.ErrCVEDBNotFound.Error()) {
|
if strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
|
||||||
// searches matches search config but CVE DB is not ready server side
|
// searches matches search config but CVE DB is not ready server side
|
||||||
// wait and retry a few more times
|
// wait and retry a few more times
|
||||||
fmt.Fprintln(searchConfig.resultWriter,
|
fmt.Fprintln(searchConfig.resultWriter,
|
||||||
|
@ -192,5 +192,5 @@ func searchCve(searchConfig searchConfig) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return zotErrors.ErrInvalidFlagsCombination
|
return zerr.ErrInvalidFlagsCombination
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,10 @@ import (
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "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/cli/cmdflags"
|
||||||
zcommon "zotregistry.io/zot/pkg/common"
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||||
|
@ -82,7 +83,7 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zotErrors.ErrNoURLProvided)
|
So(err, ShouldEqual, zerr.ErrNoURLProvided)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test CVE no params", t, func() {
|
Convey("Test CVE no params", t, func() {
|
||||||
|
@ -95,7 +96,7 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
cmd.SetErr(buff)
|
cmd.SetErr(buff)
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldEqual, zotErrors.ErrInvalidFlagsCombination)
|
So(err, ShouldEqual, zerr.ErrInvalidFlagsCombination)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test CVE invalid url", t, func() {
|
Convey("Test CVE invalid url", t, func() {
|
||||||
|
@ -109,7 +110,7 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -285,7 +286,7 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
err := cveCmd.Execute()
|
err := cveCmd.Execute()
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(strings.TrimSpace(str), ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB") //nolint:lll
|
So(strings.TrimSpace(str), ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB") //nolint:lll
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -355,7 +356,7 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
cveCmd.SetArgs(args)
|
cveCmd.SetArgs(args)
|
||||||
err := cveCmd.Execute()
|
err := cveCmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1058,6 +1059,330 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCVECommandGQL(t *testing.T) {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
defaultVal := true
|
||||||
|
conf.Extensions = &extconf.ExtensionConfig{
|
||||||
|
Search: &extconf.SearchConfig{
|
||||||
|
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("commands without gql", t, func() {
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
Convey("cveid", func() {
|
||||||
|
args := []string{"cveid", "CVE-1942"}
|
||||||
|
cmd := NewCVESCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
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 6e2f80bf false 123kB")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("cveid db download wait", func() {
|
||||||
|
count := 0
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
args := []string{"cveid", "CVE-12345"}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
cmd := NewCVESCommand(mockService{
|
||||||
|
getTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string) (*zcommon.ImagesForCve, error,
|
||||||
|
) {
|
||||||
|
if count == 0 {
|
||||||
|
count++
|
||||||
|
fmt.Println("Count:", count)
|
||||||
|
|
||||||
|
return &zcommon.ImagesForCve{}, zerr.ErrCVEDBNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return &zcommon.ImagesForCve{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("fixed", func() {
|
||||||
|
args := []string{"fixed", "image-name", "CVE-123"}
|
||||||
|
cmd := NewCVESCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
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 6e2f80bf false 123kB")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("fixed db download wait", func() {
|
||||||
|
count := 0
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
args := []string{"fixed", "repo", "CVE-2222"}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
cmd := NewCVESCommand(mockService{
|
||||||
|
getFixedTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string) (*zcommon.ImageListWithCVEFixedResponse, error,
|
||||||
|
) {
|
||||||
|
if count == 0 {
|
||||||
|
count++
|
||||||
|
fmt.Println("Count:", count)
|
||||||
|
|
||||||
|
return &zcommon.ImageListWithCVEFixedResponse{}, zerr.ErrCVEDBNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return &zcommon.ImageListWithCVEFixedResponse{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("image", func() {
|
||||||
|
args := []string{"image", "repo:tag"}
|
||||||
|
cmd := NewCVESCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
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, "dummyCVEID HIGH Title of that CVE")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("image db download wait", func() {
|
||||||
|
count := 0
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
args := []string{"image", "repo:vuln"}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
cmd := NewCVESCommand(mockService{
|
||||||
|
getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, searchedCVE string) (*cveResult, error,
|
||||||
|
) {
|
||||||
|
if count == 0 {
|
||||||
|
count++
|
||||||
|
fmt.Println("Count:", count)
|
||||||
|
|
||||||
|
return &cveResult{}, zerr.ErrCVEDBNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cveResult{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCVECommandREST(t *testing.T) {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("commands without gql", t, func() {
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
Convey("cveid", func() {
|
||||||
|
args := []string{"cveid", "CVE-1942"}
|
||||||
|
cmd := NewCVESCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("cveid error", func() {
|
||||||
|
// too many args
|
||||||
|
args := []string{"too", "many", "args"}
|
||||||
|
cmd := NewImagesByCVEIDCommand(mockService{})
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// bad args
|
||||||
|
args = []string{"not-a-cve-id"}
|
||||||
|
cmd = NewImagesByCVEIDCommand(mockService{})
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// no URL
|
||||||
|
args = []string{"CVE-1942"}
|
||||||
|
cmd = NewImagesByCVEIDCommand(mockService{})
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("fixed command", func() {
|
||||||
|
args := []string{"fixed", "image-name", "CVE-123"}
|
||||||
|
cmd := NewCVESCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("fixed command error", func() {
|
||||||
|
// too many args
|
||||||
|
args := []string{"too", "many", "args", "args"}
|
||||||
|
cmd := NewFixedTagsCommand(mockService{})
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// bad args
|
||||||
|
args = []string{"repo-tag-instead-of-just-repo:fail-here", "CVE-123"}
|
||||||
|
cmd = NewFixedTagsCommand(mockService{})
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// no URL
|
||||||
|
args = []string{"CVE-1942"}
|
||||||
|
cmd = NewFixedTagsCommand(mockService{})
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("image", func() {
|
||||||
|
args := []string{"image", "repo:tag"}
|
||||||
|
cmd := NewCVESCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "cvetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("image command error", func() {
|
||||||
|
// too many args
|
||||||
|
args := []string{"too", "many", "args", "args"}
|
||||||
|
cmd := NewCveForImageCommand(mockService{})
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// bad args
|
||||||
|
args = []string{"repo-tag-instead-of-just-repo:fail-here", "CVE-123"}
|
||||||
|
cmd = NewCveForImageCommand(mockService{})
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// no URL
|
||||||
|
args = []string{"CVE-1942"}
|
||||||
|
cmd = NewCveForImageCommand(mockService{})
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func MockNewCveCommand(searchService SearchService) *cobra.Command {
|
func MockNewCveCommand(searchService SearchService) *cobra.Command {
|
||||||
searchCveParams := make(map[string]*string)
|
searchCveParams := make(map[string]*string)
|
||||||
|
|
||||||
|
@ -1072,7 +1397,7 @@ func MockNewCveCommand(searchService SearchService) *cobra.Command {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1082,12 +1407,12 @@ func MockNewCveCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if urlFromConfig == "" {
|
if urlFromConfig == "" {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
servURL = urlFromConfig
|
servURL = urlFromConfig
|
||||||
} else {
|
} else {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
@ -1157,7 +1482,7 @@ func MockSearchCve(searchConfig searchConfig) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return zotErrors.ErrInvalidFlagsCombination
|
return zerr.ErrInvalidFlagsCombination
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
||||||
|
@ -1232,7 +1557,7 @@ func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
||||||
|
|
||||||
descriptor, ok := repoMeta.Tags[inputTag]
|
descriptor, ok := repoMeta.Tags[inputTag]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, zotErrors.ErrTagMetaNotFound
|
return false, zerr.ErrTagMetaNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDigestStr = descriptor.Digest
|
manifestDigestStr = descriptor.Digest
|
||||||
|
@ -1252,7 +1577,7 @@ func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
||||||
|
|
||||||
err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent)
|
err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, zotErrors.ErrScanNotSupported
|
return false, zerr.ErrScanNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, imageLayer := range manifestContent.Layers {
|
for _, imageLayer := range manifestContent.Layers {
|
||||||
|
@ -1262,7 +1587,7 @@ func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
||||||
return true, nil
|
return true, nil
|
||||||
default:
|
default:
|
||||||
|
|
||||||
return false, zotErrors.ErrScanNotSupported
|
return false, zerr.ErrScanNotSupported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1284,12 +1609,12 @@ type mockServiceForRetry struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *mockServiceForRetry) getImagesByCveID(ctx context.Context, config searchConfig,
|
func (service *mockServiceForRetry) getImagesByCveID(ctx context.Context, config searchConfig,
|
||||||
username, password, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
username, password, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
) {
|
) {
|
||||||
service.retryCounter += 1
|
service.retryCounter += 1
|
||||||
|
|
||||||
if service.retryCounter < service.succeedOn || service.succeedOn < 0 {
|
if service.retryCounter < service.succeedOn || service.succeedOn < 0 {
|
||||||
rch <- stringResult{"", zotErrors.ErrCVEDBNotFound}
|
rch <- stringResult{"", zerr.ErrCVEDBNotFound}
|
||||||
close(rch)
|
close(rch)
|
||||||
|
|
||||||
wtgrp.Done()
|
wtgrp.Done()
|
||||||
|
|
30
pkg/cli/cves_cmd.go
Normal file
30
pkg/cli/cves_cmd.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCVESCommand(searchService SearchService) *cobra.Command {
|
||||||
|
cvesCmd := &cobra.Command{
|
||||||
|
Use: "cves [command]",
|
||||||
|
Short: "Lookup CVEs in images hosted on the zot registry",
|
||||||
|
Long: `List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
|
||||||
|
}
|
||||||
|
|
||||||
|
cvesCmd.SetUsageTemplate(cvesCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
||||||
|
cvesCmd.PersistentFlags().StringP(cmdflags.OutputFormatFlag, "f", "", "Specify output format [text/json/yaml]")
|
||||||
|
cvesCmd.PersistentFlags().Bool(cmdflags.VerboseFlag, false, "Show verbose output")
|
||||||
|
cvesCmd.PersistentFlags().Bool(cmdflags.DebugFlag, false, "Show debug output")
|
||||||
|
|
||||||
|
cvesCmd.AddCommand(NewCveForImageCommand(searchService))
|
||||||
|
cvesCmd.AddCommand(NewImagesByCVEIDCommand(searchService))
|
||||||
|
cvesCmd.AddCommand(NewFixedTagsCommand(searchService))
|
||||||
|
|
||||||
|
return cvesCmd
|
||||||
|
}
|
128
pkg/cli/cves_sub_cmd.go
Normal file
128
pkg/cli/cves_sub_cmd.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxRetries = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCveForImageCommand(searchService SearchService) *cobra.Command {
|
||||||
|
var searchedCVEID string
|
||||||
|
|
||||||
|
cveForImageCmd := &cobra.Command{
|
||||||
|
Use: "image [repo:tag]|[repo@digest]",
|
||||||
|
Short: "List CVEs by REPO:TAG or REPO@DIGEST",
|
||||||
|
Long: `List CVEs by REPO:TAG or REPO@DIGEST`,
|
||||||
|
Args: OneImageWithRefArg,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CheckExtEndPointQuery(searchConfig, CVEListForImageQuery())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: '%s'", err, CVEListForImageQuery().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
image := args[0]
|
||||||
|
|
||||||
|
return SearchCVEForImageGQL(searchConfig, image, searchedCVEID)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cveForImageCmd.Flags().StringVar(&searchedCVEID, cmdflags.SearchedCVEID, "", "Search for a specific CVE by name/id")
|
||||||
|
|
||||||
|
return cveForImageCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImagesByCVEIDCommand(searchService SearchService) *cobra.Command {
|
||||||
|
var repo string
|
||||||
|
|
||||||
|
imagesByCVEIDCmd := &cobra.Command{
|
||||||
|
Use: "cveid [cveId]",
|
||||||
|
Short: "List images affected by a CVE",
|
||||||
|
Long: `List images affected by a CVE`,
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(args[0], "CVE") {
|
||||||
|
return fmt.Errorf("%w: expected a cveid 'CVE-...' got '%s'", zerr.ErrInvalidCLIParameter, args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CheckExtEndPointQuery(searchConfig, ImageListForCVEQuery())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: '%s'", err, ImageListForCVEQuery().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchedCVEID := args[0]
|
||||||
|
|
||||||
|
return SearchImagesByCVEIDGQL(searchConfig, repo, searchedCVEID)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesByCVEIDCmd.Flags().StringVar(&repo, "repo", "", "Search for a specific CVE by name/id")
|
||||||
|
|
||||||
|
return imagesByCVEIDCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
|
||||||
|
fixedTagsCmd := &cobra.Command{
|
||||||
|
Use: "fixed [repo] [cveId]",
|
||||||
|
Short: "List tags where a CVE is fixedRetryWithContext",
|
||||||
|
Long: `List tags where a CVE is fixedRetryWithContext`,
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
const argCount = 2
|
||||||
|
|
||||||
|
if err := cobra.ExactArgs(argCount)(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !zcommon.CheckIsCorrectRepoNameFormat(args[0]) {
|
||||||
|
return fmt.Errorf("%w: expected a valid repo name for first argument '%s'", zerr.ErrInvalidCLIParameter, args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CheckExtEndPointQuery(searchConfig, ImageListWithCVEFixedQuery())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: '%s'", err, ImageListWithCVEFixedQuery().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := args[0]
|
||||||
|
searchedCVEID := args[1]
|
||||||
|
|
||||||
|
return SearchFixedTagsGQL(searchConfig, repo, searchedCVEID)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixedTagsCmd
|
||||||
|
}
|
|
@ -9,12 +9,16 @@ import (
|
||||||
|
|
||||||
distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions"
|
distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api/constants"
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
"zotregistry.io/zot/pkg/common"
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type field struct {
|
type field struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Args []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"args"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type schemaList struct {
|
type schemaList struct {
|
||||||
|
@ -23,9 +27,19 @@ type schemaList struct {
|
||||||
QueryType struct {
|
QueryType struct {
|
||||||
Fields []field `json:"fields"`
|
Fields []field `json:"fields"`
|
||||||
} `json:"queryType"` //nolint:tagliatelle // graphQL schema
|
} `json:"queryType"` //nolint:tagliatelle // graphQL schema
|
||||||
|
Types []typeInfo `json:"types"`
|
||||||
} `json:"__schema"` //nolint:tagliatelle // graphQL schema
|
} `json:"__schema"` //nolint:tagliatelle // graphQL schema
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
Errors []common.ErrorGQL `json:"errors"`
|
Errors []zcommon.ErrorGQL `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Fields []typeField `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeField struct {
|
||||||
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsGQLQuery(queryList []field, query string) bool {
|
func containsGQLQuery(queryList []field, query string) bool {
|
||||||
|
@ -38,6 +52,54 @@ func containsGQLQuery(queryList []field, query string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func containsGQLQueryWithParams(queryList []field, serverGQLTypesList []typeInfo, requiredQueries ...GQLQuery) error {
|
||||||
|
serverGQLTypes := map[string][]typeField{}
|
||||||
|
|
||||||
|
for _, typeInfo := range serverGQLTypesList {
|
||||||
|
serverGQLTypes[typeInfo.Name] = typeInfo.Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, reqQuery := range requiredQueries {
|
||||||
|
foundQuery := false
|
||||||
|
|
||||||
|
for _, query := range queryList {
|
||||||
|
if query.Name == reqQuery.Name && haveSameArgs(query, reqQuery) {
|
||||||
|
foundQuery = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundQuery {
|
||||||
|
return fmt.Errorf("%w: %s", zerr.ErrGQLQueryNotSupported, reqQuery.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's check just the name of the returned type
|
||||||
|
returnType := reqQuery.ReturnType.Name
|
||||||
|
|
||||||
|
// we can next define fields of the returned types and check them recursively
|
||||||
|
// for now we will just check the name of the returned type to be known by the server
|
||||||
|
_, ok := serverGQLTypes[returnType]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: server doesn't support needed type '%s'", zerr.ErrGQLQueryNotSupported, returnType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func haveSameArgs(query field, reqQuery GQLQuery) bool {
|
||||||
|
if len(query.Args) != len(reqQuery.Args) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range query.Args {
|
||||||
|
if query.Args[i].Name != reqQuery.Args[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func checkExtEndPoint(config searchConfig) bool {
|
func checkExtEndPoint(config searchConfig) bool {
|
||||||
username, password := getUsernameAndPassword(*config.user)
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -59,7 +121,7 @@ func checkExtEndPoint(config searchConfig) bool {
|
||||||
searchEnabled := false
|
searchEnabled := false
|
||||||
|
|
||||||
for _, extension := range discoverResponse.Extensions {
|
for _, extension := range discoverResponse.Extensions {
|
||||||
if extension.Name == "_zot" {
|
if extension.Name == constants.BaseExtension {
|
||||||
for _, endpoint := range extension.Endpoints {
|
for _, endpoint := range extension.Endpoints {
|
||||||
if endpoint == constants.FullSearchPrefix {
|
if endpoint == constants.FullSearchPrefix {
|
||||||
searchEnabled = true
|
searchEnabled = true
|
||||||
|
@ -99,3 +161,80 @@ func checkExtEndPoint(config searchConfig) bool {
|
||||||
|
|
||||||
return containsGQLQuery(queryResponse.Data.Schema.QueryType.Fields, "ImageList")
|
return containsGQLQuery(queryResponse.Data.Schema.QueryType.Fields, "ImageList")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
discoverEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s",
|
||||||
|
constants.RoutePrefix, constants.ExtOciDiscoverPrefix))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
discoverResponse := &distext.ExtensionList{}
|
||||||
|
|
||||||
|
_, err = makeGETRequest(ctx, discoverEndPoint, username, password, *config.verifyTLS,
|
||||||
|
*config.debug, &discoverResponse, config.resultWriter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
searchEnabled := false
|
||||||
|
|
||||||
|
for _, extension := range discoverResponse.Extensions {
|
||||||
|
if extension.Name == constants.BaseExtension {
|
||||||
|
for _, endpoint := range extension.Endpoints {
|
||||||
|
if endpoint == constants.FullSearchPrefix {
|
||||||
|
searchEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !searchEnabled {
|
||||||
|
return fmt.Errorf("%w: search extension gql endpoints not found", zerr.ErrExtensionNotEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchEndPoint, _ := combineServerAndEndpointURL(*config.servURL, constants.FullSearchPrefix)
|
||||||
|
|
||||||
|
schemaQuery := `
|
||||||
|
{
|
||||||
|
__schema() {
|
||||||
|
queryType {
|
||||||
|
fields {
|
||||||
|
name
|
||||||
|
args {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
type {
|
||||||
|
name
|
||||||
|
kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
types {
|
||||||
|
name
|
||||||
|
fields {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
queryResponse := &schemaList{}
|
||||||
|
|
||||||
|
err = makeGraphQLRequest(ctx, searchEndPoint, schemaQuery, username, password, *config.verifyTLS,
|
||||||
|
*config.debug, queryResponse, config.resultWriter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("gql query failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkResultGraphQLQuery(ctx, err, queryResponse.Errors); err != nil {
|
||||||
|
return fmt.Errorf("gql query failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return containsGQLQueryWithParams(queryResponse.Data.Schema.QueryType.Fields,
|
||||||
|
queryResponse.Data.Schema.Types, requiredQueries...)
|
||||||
|
}
|
||||||
|
|
116
pkg/cli/gql_queries.go
Normal file
116
pkg/cli/gql_queries.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
type GQLField struct {
|
||||||
|
Name string
|
||||||
|
Type GQLType
|
||||||
|
}
|
||||||
|
|
||||||
|
type GQLType struct {
|
||||||
|
Name string
|
||||||
|
Fields []GQLField
|
||||||
|
}
|
||||||
|
|
||||||
|
type GQLQuery struct {
|
||||||
|
Name string
|
||||||
|
Args []string
|
||||||
|
ReturnType GQLType
|
||||||
|
}
|
||||||
|
|
||||||
|
func CVEResultForImage() GQLType {
|
||||||
|
return GQLType{
|
||||||
|
Name: "CVEResultForImage",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PaginatedImagesResult() GQLType {
|
||||||
|
return GQLType{
|
||||||
|
Name: "PaginatedImagesResult",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Referrer() GQLType {
|
||||||
|
return GQLType{
|
||||||
|
Name: "Referrer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobalSearchResult() GQLType {
|
||||||
|
return GQLType{
|
||||||
|
Name: "GlobalSearchResult",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageListQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "ImageList",
|
||||||
|
Args: []string{"repo", "requestedPage"},
|
||||||
|
ReturnType: PaginatedImagesResult(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageListForDigestQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "ImageListForDigest",
|
||||||
|
Args: []string{"id", "requestedPage"},
|
||||||
|
ReturnType: PaginatedImagesResult(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BaseImageListQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "BaseImageList",
|
||||||
|
Args: []string{"image", "digest", "requestedPage"},
|
||||||
|
ReturnType: PaginatedImagesResult(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DerivedImageListQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "DerivedImageList",
|
||||||
|
Args: []string{"image", "digest", "requestedPage"},
|
||||||
|
ReturnType: PaginatedImagesResult(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CVEListForImageQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "CVEListForImage",
|
||||||
|
Args: []string{"image", "requestedPage", "searchedCVE"},
|
||||||
|
ReturnType: CVEResultForImage(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageListForCVEQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "ImageListForCVE",
|
||||||
|
Args: []string{"id", "filter", "requestedPage"},
|
||||||
|
ReturnType: PaginatedImagesResult(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageListWithCVEFixedQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "ImageListWithCVEFixed",
|
||||||
|
Args: []string{"id", "image", "filter", "requestedPage"},
|
||||||
|
ReturnType: PaginatedImagesResult(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReferrersQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "Referrers",
|
||||||
|
Args: []string{"repo", "digest", "type"},
|
||||||
|
ReturnType: Referrer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobalSearchQuery() GQLQuery {
|
||||||
|
return GQLQuery{
|
||||||
|
Name: "GlobalSearch",
|
||||||
|
Args: []string{"query", "filter", "requestedPage"},
|
||||||
|
ReturnType: GlobalSearchResult(),
|
||||||
|
}
|
||||||
|
}
|
93
pkg/cli/gql_queries_test.go
Normal file
93
pkg/cli/gql_queries_test.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli //nolint:testpackage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
"zotregistry.io/zot/pkg/api"
|
||||||
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
|
"zotregistry.io/zot/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGQLQueries(t *testing.T) {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
dir := t.TempDir()
|
||||||
|
conf.Storage.RootDirectory = dir
|
||||||
|
defaultVal := true
|
||||||
|
conf.Extensions = &extconf.ExtensionConfig{
|
||||||
|
Search: &extconf.SearchConfig{
|
||||||
|
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
searchConfig := searchConfig{
|
||||||
|
servURL: &baseURL,
|
||||||
|
user: ref(""),
|
||||||
|
verifyTLS: ref(false),
|
||||||
|
debug: ref(false),
|
||||||
|
resultWriter: io.Discard,
|
||||||
|
}
|
||||||
|
|
||||||
|
Convey("Make sure the current CLI used the right queries in case they change", t, func() {
|
||||||
|
Convey("ImageList", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, ImageListQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("ImageListForDigest", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, ImageListForDigestQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("BaseImageList", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, BaseImageListQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("DerivedImageList", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, DerivedImageListQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("CVEListForImage", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, CVEListForImageQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("ImageListForCVE", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, ImageListForCVEQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("ImageListWithCVEFixed", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, ImageListWithCVEFixedQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Referrers", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, ReferrersQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GlobalSearch", func() {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, GlobalSearchQuery())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:dupl
|
//nolint:dupl
|
||||||
|
@ -24,16 +24,16 @@ func NewImageCommand(searchService SearchService) *cobra.Command {
|
||||||
var isSpinner, verifyTLS, verbose, debug bool
|
var isSpinner, verifyTLS, verbose, debug bool
|
||||||
|
|
||||||
imageCmd := &cobra.Command{
|
imageCmd := &cobra.Command{
|
||||||
Use: "images [config-name]",
|
Use: "image [config-name]",
|
||||||
Short: "List images hosted on the zot registry",
|
Short: "DEPRECATED (see images)",
|
||||||
Long: `List images hosted on the zot registry`,
|
Long: `DEPRECATED (see images)! List images hosted on the zot registry`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
if servURL == "" {
|
if servURL == "" {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||||
|
@ -44,12 +44,12 @@ func NewImageCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if urlFromConfig == "" {
|
if urlFromConfig == "" {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
servURL = urlFromConfig
|
servURL = urlFromConfig
|
||||||
} else {
|
} else {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,11 +129,12 @@ func setupImageFlags(imageCmd *cobra.Command, searchImageParams map[string]*stri
|
||||||
searchImageParams["baseImage"] = imageCmd.Flags().StringP("base-images", "b", "",
|
searchImageParams["baseImage"] = imageCmd.Flags().StringP("base-images", "b", "",
|
||||||
"List images that are base for the given image")
|
"List images that are base for the given image")
|
||||||
|
|
||||||
imageCmd.Flags().StringVar(servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
imageCmd.PersistentFlags().StringVar(servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
||||||
imageCmd.Flags().StringVarP(user, "user", "u", "", `User Credentials of zot server in "username:password" format`)
|
imageCmd.PersistentFlags().StringVarP(user, "user", "u", "",
|
||||||
imageCmd.Flags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
|
`User Credentials of zot server in "username:password" format`)
|
||||||
imageCmd.Flags().BoolVar(verbose, "verbose", false, "Show verbose output")
|
imageCmd.PersistentFlags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
|
||||||
imageCmd.Flags().BoolVar(debug, "debug", false, "Show debug output")
|
imageCmd.PersistentFlags().BoolVar(verbose, "verbose", false, "Show verbose output")
|
||||||
|
imageCmd.PersistentFlags().BoolVar(debug, "debug", false, "Show debug output")
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchImage(searchConfig searchConfig) error {
|
func searchImage(searchConfig searchConfig) error {
|
||||||
|
@ -156,7 +157,7 @@ func searchImage(searchConfig searchConfig) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return zotErrors.ErrInvalidFlagsCombination
|
return zerr.ErrInvalidFlagsCombination
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -26,9 +26,10 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "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/cli/cmdflags"
|
||||||
"zotregistry.io/zot/pkg/common"
|
"zotregistry.io/zot/pkg/common"
|
||||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
zlog "zotregistry.io/zot/pkg/log"
|
zlog "zotregistry.io/zot/pkg/log"
|
||||||
|
@ -74,7 +75,7 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zotErrors.ErrNoURLProvided)
|
So(err, ShouldEqual, zerr.ErrNoURLProvided)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test image invalid home directory", t, func() {
|
Convey("Test image invalid home directory", t, func() {
|
||||||
|
@ -130,7 +131,7 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -253,7 +254,7 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
imageCmd.SetArgs(args)
|
imageCmd.SetArgs(args)
|
||||||
err := imageCmd.Execute()
|
err := imageCmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1555,6 +1556,564 @@ func runDisplayIndexTests(baseURL string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImagesCommandGQL(t *testing.T) {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
defaultVal := true
|
||||||
|
conf.Extensions = &extconf.ExtensionConfig{
|
||||||
|
Search: &extconf.SearchConfig{
|
||||||
|
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("commands with gql", t, func() {
|
||||||
|
err := test.RemoveLocalStorageContents(ctlr.StoreController.DefaultStore)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("base and derived command", func() {
|
||||||
|
baseImage := test.CreateImageWith().LayerBlobs(
|
||||||
|
[][]byte{{1, 2, 3}, {11, 22, 33}},
|
||||||
|
).DefaultConfig().Build()
|
||||||
|
|
||||||
|
derivedImage := test.CreateImageWith().LayerBlobs(
|
||||||
|
[][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}},
|
||||||
|
).DefaultConfig().Build()
|
||||||
|
|
||||||
|
err := test.UploadImage(baseImage, baseURL, "repo", "base")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.UploadImage(derivedImage, baseURL, "repo", "derived")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"base", "repo:derived"}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo base linux/amd64 df554ddd false 699B")
|
||||||
|
|
||||||
|
args = []string{"derived", "repo:base"}
|
||||||
|
cmd = NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
str = space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual = strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo derived linux/amd64 79f4b82e false 854B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("base and derived command errors", func() {
|
||||||
|
// too many parameters
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
args := []string{"too", "many", "args"}
|
||||||
|
cmd := NewImageBaseCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
cmd = NewImageDerivedCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// bad input
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
args = []string{"only-repo"}
|
||||||
|
cmd = NewImageBaseCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
cmd = NewImageDerivedCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// no url
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
args = []string{"repo:tag"}
|
||||||
|
cmd = NewImageBaseCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
cmd = NewImageDerivedCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("digest command", func() {
|
||||||
|
image := test.CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, "repo", "img")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"digest", image.DigestStr()}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B",
|
||||||
|
image.DigestStr()[7:7+8]))
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("digest command errors", func() {
|
||||||
|
// too many parameters
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
args := []string{"too", "many", "args"}
|
||||||
|
cmd := NewImageDigestCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// bad input
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
args = []string{"bad-digest"}
|
||||||
|
cmd = NewImageDigestCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// no url
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
args = []string{godigest.FromString("str").String()}
|
||||||
|
cmd = NewImageDigestCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("list command", func() {
|
||||||
|
image := test.CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, "repo", "img")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"list"}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
fmt.Println(actual)
|
||||||
|
So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B",
|
||||||
|
image.DigestStr()[7:7+8]))
|
||||||
|
fmt.Println(actual)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("list command errors", func() {
|
||||||
|
// too many parameters
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
args := []string{"repo:img", "arg"}
|
||||||
|
cmd := NewImageListCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// no url
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
args = []string{}
|
||||||
|
cmd = NewImageListCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("name command", func() {
|
||||||
|
image := test.CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, "repo", "img")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.UploadImage(test.CreateRandomImage(), baseURL, "repo", "img2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"name", "repo:img"}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
fmt.Println(actual)
|
||||||
|
So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B",
|
||||||
|
image.DigestStr()[7:7+8]))
|
||||||
|
fmt.Println(actual)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("name command errors", func() {
|
||||||
|
// too many parameters
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
args := []string{"repo:img", "arg"}
|
||||||
|
cmd := NewImageNameCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// bad input
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
args = []string{":tag"}
|
||||||
|
cmd = NewImageNameCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// no url
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
args = []string{"repo:tag"}
|
||||||
|
cmd = NewImageNameCommand(NewSearchService())
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("CVE", func() {
|
||||||
|
vulnImage := test.CreateDefaultVulnerableImage()
|
||||||
|
err := test.UploadImage(vulnImage, baseURL, "repo", "vuln")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
args := []string{"cve", "repo:vuln"}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
cmd := NewImagesCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
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, "dummyCVEID HIGH Title of that CVE")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("CVE errors", func() {
|
||||||
|
count := 0
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
args := []string{"cve", "repo:vuln"}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
cmd := NewImagesCommand(mockService{
|
||||||
|
getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, searchedCVE string) (*cveResult, error,
|
||||||
|
) {
|
||||||
|
if count == 0 {
|
||||||
|
count++
|
||||||
|
fmt.Println("Count:", count)
|
||||||
|
|
||||||
|
return &cveResult{}, zerr.ErrCVEDBNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cveResult{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Config error", t, func() {
|
||||||
|
args := []string{"base", "repo:derived"}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
args = []string{"derived", "repo:base"}
|
||||||
|
cmd = NewImagesCommand(NewSearchService())
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
args = []string{"digest", ispec.DescriptorEmptyJSON.Digest.String()}
|
||||||
|
cmd = NewImagesCommand(NewSearchService())
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
args = []string{"list"}
|
||||||
|
cmd = NewImagesCommand(NewSearchService())
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
args = []string{"name", "repo:img"}
|
||||||
|
cmd = NewImagesCommand(NewSearchService())
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
args = []string{"cve", "repo:vuln"}
|
||||||
|
cmd = NewImagesCommand(mockService{})
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageCommandREST(t *testing.T) {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("commands without gql", t, func() {
|
||||||
|
err := test.RemoveLocalStorageContents(ctlr.StoreController.DefaultStore)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("base and derived command", func() {
|
||||||
|
baseImage := test.CreateImageWith().LayerBlobs(
|
||||||
|
[][]byte{{1, 2, 3}, {11, 22, 33}},
|
||||||
|
).DefaultConfig().Build()
|
||||||
|
|
||||||
|
derivedImage := test.CreateImageWith().LayerBlobs(
|
||||||
|
[][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}},
|
||||||
|
).DefaultConfig().Build()
|
||||||
|
|
||||||
|
err := test.UploadImage(baseImage, baseURL, "repo", "base")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.UploadImage(derivedImage, baseURL, "repo", "derived")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"base", "repo:derived"}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
args = []string{"derived", "repo:base"}
|
||||||
|
cmd = NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("digest command", func() {
|
||||||
|
image := test.CreateRandomImage()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, "repo", "img")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"digest", image.DigestStr()}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("list command", func() {
|
||||||
|
image := test.CreateRandomImage()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, "repo", "img")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"list"}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
fmt.Println(buff.String())
|
||||||
|
fmt.Println()
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("name command", func() {
|
||||||
|
image := test.CreateRandomImage()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, "repo", "img")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.UploadImage(test.CreateRandomImage(), baseURL, "repo", "img2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"name", "repo:img"}
|
||||||
|
cmd := NewImagesCommand(NewSearchService())
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
fmt.Println(buff.String())
|
||||||
|
fmt.Println()
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("CVE", func() {
|
||||||
|
vulnImage := test.CreateDefaultVulnerableImage()
|
||||||
|
err := test.UploadImage(vulnImage, baseURL, "repo", "vuln")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
args := []string{"cve", "repo:vuln"}
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
cmd := NewImagesCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func uploadTestMultiarch(baseURL string) {
|
func uploadTestMultiarch(baseURL string) {
|
||||||
// ------- Define Image1
|
// ------- Define Image1
|
||||||
layer11 := []byte{11, 12, 13, 14}
|
layer11 := []byte{11, 12, 13, 14}
|
||||||
|
@ -1606,7 +2165,7 @@ func MockNewImageCommand(searchService SearchService) *cobra.Command {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1616,12 +2175,12 @@ func MockNewImageCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if urlFromConfig == "" {
|
if urlFromConfig == "" {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
servURL = urlFromConfig
|
servURL = urlFromConfig
|
||||||
} else {
|
} else {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
@ -1679,7 +2238,7 @@ func MockSearchImage(searchConfig searchConfig) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return zotErrors.ErrInvalidFlagsCombination
|
return zerr.ErrInvalidFlagsCombination
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadManifest(url string) error {
|
func uploadManifest(url string) error {
|
||||||
|
@ -1895,7 +2454,73 @@ func uploadManifestDerivedBase(url string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockService struct{}
|
type mockService struct {
|
||||||
|
getAllImagesFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||||
|
|
||||||
|
getImagesGQLFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
imageName string) (*common.ImageListResponse, error)
|
||||||
|
|
||||||
|
getImageByNameFn func(ctx context.Context, config searchConfig,
|
||||||
|
username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
)
|
||||||
|
|
||||||
|
getFixedTagsForCVEFn func(ctx context.Context, config searchConfig,
|
||||||
|
username, password, imageName, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
)
|
||||||
|
|
||||||
|
getImageByNameAndCVEIDFn func(ctx context.Context, config searchConfig, username,
|
||||||
|
password, imageName, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
)
|
||||||
|
|
||||||
|
getImagesByCveIDFn func(ctx context.Context, config searchConfig, username, password, cveid string,
|
||||||
|
rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
)
|
||||||
|
|
||||||
|
getImagesByDigestFn func(ctx context.Context, config searchConfig, username,
|
||||||
|
password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
)
|
||||||
|
|
||||||
|
getReferrersFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
repo, digest string,
|
||||||
|
) (referrersResult, error)
|
||||||
|
|
||||||
|
globalSearchGQLFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
query string,
|
||||||
|
) (*common.GlobalSearch, error)
|
||||||
|
|
||||||
|
getReferrersGQLFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
repo, digest string,
|
||||||
|
) (*common.ReferrersResp, error)
|
||||||
|
|
||||||
|
getDerivedImageListGQLFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
derivedImage string,
|
||||||
|
) (*common.DerivedImageListResponse, error)
|
||||||
|
|
||||||
|
getBaseImageListGQLFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
derivedImage string,
|
||||||
|
) (*common.BaseImageListResponse, error)
|
||||||
|
|
||||||
|
getImagesForDigestGQLFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
digest string,
|
||||||
|
) (*common.ImagesForDigest, error)
|
||||||
|
|
||||||
|
getCveByImageGQLFn func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, searchedCVE string,
|
||||||
|
) (*cveResult, error)
|
||||||
|
|
||||||
|
getImagesByCveIDGQLFn func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
digest string,
|
||||||
|
) (*common.ImagesForCve, error)
|
||||||
|
|
||||||
|
getTagsForCVEGQLFn func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string,
|
||||||
|
) (*common.ImagesForCve, error)
|
||||||
|
|
||||||
|
getFixedTagsForCVEGQLFn func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string,
|
||||||
|
) (*common.ImageListWithCVEFixedResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
func (service mockService) getRepos(ctx context.Context, config searchConfig, username,
|
func (service mockService) getRepos(ctx context.Context, config searchConfig, username,
|
||||||
password string, channel chan stringResult, wtgrp *sync.WaitGroup,
|
password string, channel chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
@ -1903,39 +2528,56 @@ func (service mockService) getRepos(ctx context.Context, config searchConfig, us
|
||||||
defer wtgrp.Done()
|
defer wtgrp.Done()
|
||||||
defer close(channel)
|
defer close(channel)
|
||||||
|
|
||||||
var catalog [3]string
|
fmt.Fprintln(config.resultWriter, "\n\nREPOSITORY NAME")
|
||||||
catalog[0] = "python"
|
|
||||||
catalog[1] = "busybox"
|
|
||||||
catalog[2] = "hello-world"
|
|
||||||
|
|
||||||
channel <- stringResult{"", nil}
|
fmt.Fprintln(config.resultWriter, "repo1")
|
||||||
|
fmt.Fprintln(config.resultWriter, "repo2")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service mockService) getReferrers(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) getReferrers(ctx context.Context, config searchConfig, username, password string,
|
||||||
repo, digest string,
|
repo, digest string,
|
||||||
) (referrersResult, error) {
|
) (referrersResult, error) {
|
||||||
return referrersResult{}, nil
|
if service.getReferrersFn != nil {
|
||||||
|
return service.getReferrersFn(ctx, config, username, password, repo, digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return referrersResult{
|
||||||
|
common.Referrer{
|
||||||
|
ArtifactType: "art.type",
|
||||||
|
Digest: ispec.DescriptorEmptyJSON.Digest.String(),
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Size: 100,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service mockService) globalSearchGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) globalSearchGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
query string,
|
query string,
|
||||||
) (*common.GlobalSearch, error) {
|
) (*common.GlobalSearch, error) {
|
||||||
|
if service.globalSearchGQLFn != nil {
|
||||||
|
return service.globalSearchGQLFn(ctx, config, username, password, query)
|
||||||
|
}
|
||||||
|
|
||||||
return &common.GlobalSearch{
|
return &common.GlobalSearch{
|
||||||
Images: []common.ImageSummary{
|
Images: []common.ImageSummary{
|
||||||
{
|
{
|
||||||
RepoName: "repo",
|
RepoName: "repo",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Size: "100",
|
||||||
Manifests: []common.ManifestSummary{
|
Manifests: []common.ManifestSummary{
|
||||||
{
|
{
|
||||||
Digest: godigest.FromString("str").String(),
|
Digest: godigest.FromString("str").String(),
|
||||||
Size: "100",
|
Size: "100",
|
||||||
|
ConfigDigest: ispec.DescriptorEmptyJSON.Digest.String(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Repos: []common.RepoSummary{
|
Repos: []common.RepoSummary{
|
||||||
{
|
{
|
||||||
Name: "repo",
|
Name: "repo",
|
||||||
|
Size: "100",
|
||||||
|
LastUpdated: time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -1944,6 +2586,10 @@ func (service mockService) globalSearchGQL(ctx context.Context, config searchCon
|
||||||
func (service mockService) getReferrersGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) getReferrersGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
repo, digest string,
|
repo, digest string,
|
||||||
) (*common.ReferrersResp, error) {
|
) (*common.ReferrersResp, error) {
|
||||||
|
if service.getReferrersGQLFn != nil {
|
||||||
|
return service.getReferrersGQLFn(ctx, config, username, password, repo, digest)
|
||||||
|
}
|
||||||
|
|
||||||
return &common.ReferrersResp{
|
return &common.ReferrersResp{
|
||||||
ReferrersResult: common.ReferrersResult{
|
ReferrersResult: common.ReferrersResult{
|
||||||
Referrers: []common.Referrer{
|
Referrers: []common.Referrer{
|
||||||
|
@ -1961,6 +2607,10 @@ func (service mockService) getReferrersGQL(ctx context.Context, config searchCon
|
||||||
func (service mockService) getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
derivedImage string,
|
derivedImage string,
|
||||||
) (*common.DerivedImageListResponse, error) {
|
) (*common.DerivedImageListResponse, error) {
|
||||||
|
if service.getDerivedImageListGQLFn != nil {
|
||||||
|
return service.getDerivedImageListGQLFn(ctx, config, username, password, derivedImage)
|
||||||
|
}
|
||||||
|
|
||||||
imageListGQLResponse := &common.DerivedImageListResponse{}
|
imageListGQLResponse := &common.DerivedImageListResponse{}
|
||||||
imageListGQLResponse.DerivedImageList.Results = []common.ImageSummary{
|
imageListGQLResponse.DerivedImageList.Results = []common.ImageSummary{
|
||||||
{
|
{
|
||||||
|
@ -1982,8 +2632,12 @@ func (service mockService) getDerivedImageListGQL(ctx context.Context, config se
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service mockService) getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
derivedImage string,
|
baseImage string,
|
||||||
) (*common.BaseImageListResponse, error) {
|
) (*common.BaseImageListResponse, error) {
|
||||||
|
if service.getBaseImageListGQLFn != nil {
|
||||||
|
return service.getBaseImageListGQLFn(ctx, config, username, password, baseImage)
|
||||||
|
}
|
||||||
|
|
||||||
imageListGQLResponse := &common.BaseImageListResponse{}
|
imageListGQLResponse := &common.BaseImageListResponse{}
|
||||||
imageListGQLResponse.BaseImageList.Results = []common.ImageSummary{
|
imageListGQLResponse.BaseImageList.Results = []common.ImageSummary{
|
||||||
{
|
{
|
||||||
|
@ -2007,6 +2661,10 @@ func (service mockService) getBaseImageListGQL(ctx context.Context, config searc
|
||||||
func (service mockService) getImagesGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) getImagesGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
imageName string,
|
imageName string,
|
||||||
) (*common.ImageListResponse, error) {
|
) (*common.ImageListResponse, error) {
|
||||||
|
if service.getImagesGQLFn != nil {
|
||||||
|
return service.getImagesGQLFn(ctx, config, username, password, imageName)
|
||||||
|
}
|
||||||
|
|
||||||
imageListGQLResponse := &common.ImageListResponse{}
|
imageListGQLResponse := &common.ImageListResponse{}
|
||||||
imageListGQLResponse.PaginatedImagesResult.Results = []common.ImageSummary{
|
imageListGQLResponse.PaginatedImagesResult.Results = []common.ImageSummary{
|
||||||
{
|
{
|
||||||
|
@ -2029,9 +2687,13 @@ func (service mockService) getImagesGQL(ctx context.Context, config searchConfig
|
||||||
return imageListGQLResponse, nil
|
return imageListGQLResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service mockService) getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) getImagesForDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
digest string,
|
digest string,
|
||||||
) (*common.ImagesForDigest, error) {
|
) (*common.ImagesForDigest, error) {
|
||||||
|
if service.getImagesForDigestGQLFn != nil {
|
||||||
|
return service.getImagesForDigestGQLFn(ctx, config, username, password, digest)
|
||||||
|
}
|
||||||
|
|
||||||
imageListGQLResponse := &common.ImagesForDigest{}
|
imageListGQLResponse := &common.ImagesForDigest{}
|
||||||
imageListGQLResponse.Results = []common.ImageSummary{
|
imageListGQLResponse.Results = []common.ImageSummary{
|
||||||
{
|
{
|
||||||
|
@ -2057,6 +2719,10 @@ func (service mockService) getImagesByDigestGQL(ctx context.Context, config sear
|
||||||
func (service mockService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service mockService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
digest string,
|
digest string,
|
||||||
) (*common.ImagesForCve, error) {
|
) (*common.ImagesForCve, error) {
|
||||||
|
if service.getImagesByCveIDGQLFn != nil {
|
||||||
|
return service.getImagesByCveIDGQLFn(ctx, config, username, password, digest)
|
||||||
|
}
|
||||||
|
|
||||||
imagesForCve := &common.ImagesForCve{
|
imagesForCve := &common.ImagesForCve{
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
ImagesForCVEList: struct {
|
ImagesForCVEList: struct {
|
||||||
|
@ -2075,6 +2741,10 @@ func (service mockService) getImagesByCveIDGQL(ctx context.Context, config searc
|
||||||
func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password,
|
func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password,
|
||||||
imageName, cveID string,
|
imageName, cveID string,
|
||||||
) (*common.ImagesForCve, error) {
|
) (*common.ImagesForCve, error) {
|
||||||
|
if service.getTagsForCVEGQLFn != nil {
|
||||||
|
return service.getTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID)
|
||||||
|
}
|
||||||
|
|
||||||
images := &common.ImagesForCve{
|
images := &common.ImagesForCve{
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
ImagesForCVEList: struct {
|
ImagesForCVEList: struct {
|
||||||
|
@ -2082,6 +2752,10 @@ func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchCo
|
||||||
}{},
|
}{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if imageName == "" {
|
||||||
|
imageName = "image-name"
|
||||||
|
}
|
||||||
|
|
||||||
images.Errors = nil
|
images.Errors = nil
|
||||||
|
|
||||||
mockedImage := service.getMockedImageByName(imageName)
|
mockedImage := service.getMockedImageByName(imageName)
|
||||||
|
@ -2093,6 +2767,10 @@ func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchCo
|
||||||
func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password,
|
func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password,
|
||||||
imageName, cveID string,
|
imageName, cveID string,
|
||||||
) (*common.ImageListWithCVEFixedResponse, error) {
|
) (*common.ImageListWithCVEFixedResponse, error) {
|
||||||
|
if service.getFixedTagsForCVEGQLFn != nil {
|
||||||
|
return service.getFixedTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID)
|
||||||
|
}
|
||||||
|
|
||||||
fixedTags := &common.ImageListWithCVEFixedResponse{
|
fixedTags := &common.ImageListWithCVEFixedResponse{
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
ImageListWithCVEFixed: struct {
|
ImageListWithCVEFixed: struct {
|
||||||
|
@ -2111,6 +2789,9 @@ func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config sea
|
||||||
func (service mockService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
func (service mockService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
||||||
imageName, searchedCVE string,
|
imageName, searchedCVE string,
|
||||||
) (*cveResult, error) {
|
) (*cveResult, error) {
|
||||||
|
if service.getCveByImageGQLFn != nil {
|
||||||
|
return service.getCveByImageGQLFn(ctx, config, username, password, imageName, searchedCVE)
|
||||||
|
}
|
||||||
cveRes := &cveResult{}
|
cveRes := &cveResult{}
|
||||||
cveRes.Data = cveData{
|
cveRes.Data = cveData{
|
||||||
CVEListForImage: cveListForImage{
|
CVEListForImage: cveListForImage{
|
||||||
|
@ -2141,6 +2822,7 @@ func (service mockService) getMockedImageByName(imageName string) imageStruct {
|
||||||
image := imageStruct{}
|
image := imageStruct{}
|
||||||
image.RepoName = imageName
|
image.RepoName = imageName
|
||||||
image.Tag = "tag"
|
image.Tag = "tag"
|
||||||
|
image.MediaType = ispec.MediaTypeImageManifest
|
||||||
image.Manifests = []common.ManifestSummary{
|
image.Manifests = []common.ManifestSummary{
|
||||||
{
|
{
|
||||||
Digest: godigest.FromString("Digest").String(),
|
Digest: godigest.FromString("Digest").String(),
|
||||||
|
@ -2160,6 +2842,12 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig
|
||||||
defer wtgrp.Done()
|
defer wtgrp.Done()
|
||||||
defer close(channel)
|
defer close(channel)
|
||||||
|
|
||||||
|
if service.getAllImagesFn != nil {
|
||||||
|
service.getAllImagesFn(ctx, config, username, password, channel, wtgrp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
image := &imageStruct{}
|
image := &imageStruct{}
|
||||||
image.RepoName = "randomimageName"
|
image.RepoName = "randomimageName"
|
||||||
image.Tag = "tag"
|
image.Tag = "tag"
|
||||||
|
@ -2192,6 +2880,12 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf
|
||||||
defer wtgrp.Done()
|
defer wtgrp.Done()
|
||||||
defer close(channel)
|
defer close(channel)
|
||||||
|
|
||||||
|
if service.getImageByNameFn != nil {
|
||||||
|
service.getImageByNameFn(ctx, config, username, password, imageName, channel, wtgrp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
image := &imageStruct{}
|
image := &imageStruct{}
|
||||||
image.RepoName = imageName
|
image.RepoName = imageName
|
||||||
image.Tag = "tag"
|
image.Tag = "tag"
|
||||||
|
@ -2257,26 +2951,62 @@ func (service mockService) getCveByImage(ctx context.Context, config searchConfi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service mockService) getFixedTagsForCVE(ctx context.Context, config searchConfig,
|
func (service mockService) getFixedTagsForCVE(ctx context.Context, config searchConfig,
|
||||||
username, password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
username, password, imageName, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
) {
|
) {
|
||||||
|
if service.getFixedTagsForCVEFn != nil {
|
||||||
|
defer wtgrp.Done()
|
||||||
|
defer close(rch)
|
||||||
|
|
||||||
|
service.getFixedTagsForCVEFn(ctx, config, username, password, imageName, cveid, rch, wtgrp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
service.getImageByName(ctx, config, username, password, imageName, rch, wtgrp)
|
service.getImageByName(ctx, config, username, password, imageName, rch, wtgrp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service mockService) getImageByNameAndCVEID(ctx context.Context, config searchConfig, username,
|
func (service mockService) getImageByNameAndCVEID(ctx context.Context, config searchConfig, username,
|
||||||
password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
password, imageName, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
) {
|
) {
|
||||||
|
if service.getImageByNameAndCVEIDFn != nil {
|
||||||
|
defer wtgrp.Done()
|
||||||
|
defer close(rch)
|
||||||
|
|
||||||
|
service.getImageByNameAndCVEIDFn(ctx, config, username, password, imageName, cveid, rch, wtgrp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
service.getImageByName(ctx, config, username, password, imageName, rch, wtgrp)
|
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, cveid string,
|
||||||
rch chan stringResult, wtgrp *sync.WaitGroup,
|
rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
) {
|
) {
|
||||||
|
if service.getImagesByCveIDFn != nil {
|
||||||
|
defer wtgrp.Done()
|
||||||
|
defer close(rch)
|
||||||
|
|
||||||
|
service.getImagesByCveIDFn(ctx, config, username, password, cveid, rch, wtgrp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp)
|
service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service mockService) getImagesByDigest(ctx context.Context, config searchConfig, username,
|
func (service mockService) getImagesByDigest(ctx context.Context, config searchConfig, username,
|
||||||
password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
) {
|
) {
|
||||||
|
if service.getImagesByDigestFn != nil {
|
||||||
|
defer wtgrp.Done()
|
||||||
|
defer close(rch)
|
||||||
|
|
||||||
|
service.getImagesByDigestFn(ctx, config, username, password, digest, rch, wtgrp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp)
|
service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2288,7 +3018,7 @@ func makeConfigFile(content string) string {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
|
|
||||||
if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
|
if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
33
pkg/cli/images_cmd.go
Normal file
33
pkg/cli/images_cmd.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewImagesCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageCmd := &cobra.Command{
|
||||||
|
Use: "images [command]",
|
||||||
|
Short: "List images hosted on the zot registry",
|
||||||
|
Long: `List images hosted on the zot registry`,
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
||||||
|
imageCmd.PersistentFlags().StringP(cmdflags.OutputFormatFlag, "f", "", "Specify output format [text/json/yaml]")
|
||||||
|
imageCmd.PersistentFlags().Bool(cmdflags.VerboseFlag, false, "Show verbose output")
|
||||||
|
imageCmd.PersistentFlags().Bool(cmdflags.DebugFlag, false, "Show debug output")
|
||||||
|
|
||||||
|
imageCmd.AddCommand(NewImageListCommand(searchService))
|
||||||
|
imageCmd.AddCommand(NewImageCVEListCommand(searchService))
|
||||||
|
imageCmd.AddCommand(NewImageBaseCommand(searchService))
|
||||||
|
imageCmd.AddCommand(NewImageDerivedCommand(searchService))
|
||||||
|
imageCmd.AddCommand(NewImageDigestCommand(searchService))
|
||||||
|
imageCmd.AddCommand(NewImageNameCommand(searchService))
|
||||||
|
|
||||||
|
return imageCmd
|
||||||
|
}
|
284
pkg/cli/images_sub_cmd.go
Normal file
284
pkg/cli/images_sub_cmd.go
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/briandowns/spinner"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewImageListCommand(searchService SearchService) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List all images",
|
||||||
|
Long: "List all images",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, ImageListQuery()); err == nil {
|
||||||
|
return SearchAllImagesGQL(searchConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchAllImages(searchConfig)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageCVEListCommand(searchService SearchService) *cobra.Command {
|
||||||
|
var searchedCVEID string
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "cve [repo-name:tag][repo-name@digest]",
|
||||||
|
Short: "List all CVE's of the image",
|
||||||
|
Long: "List all CVE's of the image",
|
||||||
|
Args: OneImageWithRefArg,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, CVEListForImageQuery()); err == nil {
|
||||||
|
image := args[0]
|
||||||
|
|
||||||
|
return SearchCVEForImageGQL(searchConfig, image, searchedCVEID)
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&searchedCVEID, cmdflags.SearchedCVEID, "", "Search for a specific CVE by name/id")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageDerivedCommand(searchService SearchService) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "derived [repo-name:tag][repo-name@digest]",
|
||||||
|
Short: "List images that are derived from given image",
|
||||||
|
Long: "List images that are derived from given image",
|
||||||
|
Args: OneImageWithRefArg,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, DerivedImageListQuery()); err == nil {
|
||||||
|
return SearchDerivedImageListGQL(searchConfig, args[0])
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageBaseCommand(searchService SearchService) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "base [repo-name:tag][repo-name@digest]",
|
||||||
|
Short: "List images that are base for the given image",
|
||||||
|
Long: "List images that are base for the given image",
|
||||||
|
Args: OneImageWithRefArg,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, BaseImageListQuery()); err == nil {
|
||||||
|
return SearchBaseImageListGQL(searchConfig, args[0])
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageDigestCommand(searchService SearchService) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "digest [digest]",
|
||||||
|
Short: "List images that contain a blob(manifest, config or layer) with the given digest",
|
||||||
|
Long: "List images that contain a blob(manifest, config or layer) with the given digest",
|
||||||
|
Args: OneDigestArg,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, ImageListForDigestQuery()); err == nil {
|
||||||
|
return SearchImagesForDigestGQL(searchConfig, args[0])
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageNameCommand(searchService SearchService) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "name [repo:tag]",
|
||||||
|
Short: "List image details by name",
|
||||||
|
Long: "List image details by name",
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
image := args[0]
|
||||||
|
|
||||||
|
if dir, _ := zcommon.GetImageDirAndTag(image); dir == "" {
|
||||||
|
return zerr.ErrInvalidRepoRefFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, ImageListQuery()); err == nil {
|
||||||
|
return SearchImageByNameGQL(searchConfig, args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchImageByName(searchConfig, args[0])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (searchConfig, error) {
|
||||||
|
serverURL, err := GetServerURLFromFlags(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return searchConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isSpinner, verifyTLS := GetCliConfigOptions(cmd)
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
user := defaultIfError(flags.GetString(cmdflags.UserFlag))
|
||||||
|
fixed := defaultIfError(flags.GetBool(cmdflags.FixedFlag))
|
||||||
|
debug := defaultIfError(flags.GetBool(cmdflags.DebugFlag))
|
||||||
|
verbose := defaultIfError(flags.GetBool(cmdflags.VerboseFlag))
|
||||||
|
outputFormat := defaultIfError(flags.GetString(cmdflags.OutputFormatFlag))
|
||||||
|
|
||||||
|
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||||
|
spin.Prefix = prefix
|
||||||
|
|
||||||
|
return searchConfig{
|
||||||
|
params: map[string]*string{},
|
||||||
|
searchService: searchService,
|
||||||
|
servURL: &serverURL,
|
||||||
|
user: &user,
|
||||||
|
outputFormat: &outputFormat,
|
||||||
|
verifyTLS: &verifyTLS,
|
||||||
|
fixedFlag: &fixed,
|
||||||
|
verbose: &verbose,
|
||||||
|
debug: &debug,
|
||||||
|
spinner: spinnerState{spin, isSpinner},
|
||||||
|
resultWriter: cmd.OutOrStdout(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIfError[T any](out T, err error) T {
|
||||||
|
var defaultVal T
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCliConfigOptions(cmd *cobra.Command) (bool, bool) {
|
||||||
|
configName, err := cmd.Flags().GetString(cmdflags.ConfigFlag)
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
configDir := path.Join(home, "/.zot")
|
||||||
|
|
||||||
|
isSpinner, err := parseBooleanConfig(configDir, configName, showspinnerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyTLS, err := parseBooleanConfig(configDir, configName, verifyTLSConfig)
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSpinner, verifyTLS
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetServerURLFromFlags(cmd *cobra.Command) (string, error) {
|
||||||
|
serverURL, err := cmd.Flags().GetString(cmdflags.URLFlag)
|
||||||
|
if err == nil && serverURL != "" {
|
||||||
|
return serverURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
configName, err := cmd.Flags().GetString(cmdflags.ConfigFlag)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if configName == "" {
|
||||||
|
return "", fmt.Errorf("%w: specify either '--%s' or '--%s' flags", zerr.ErrNoURLProvided, cmdflags.URLFlag,
|
||||||
|
cmdflags.ConfigFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverURL, err = ReadServerURLFromConfig(configName)
|
||||||
|
if err != nil {
|
||||||
|
return serverURL, fmt.Errorf("reading url from config failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverURL == "" {
|
||||||
|
return "", fmt.Errorf("%w: url field from config is empty", zerr.ErrNoURLProvided)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadServerURLFromConfig(configName string) (string, error) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
configDir := path.Join(home, "/.zot")
|
||||||
|
|
||||||
|
urlFromConfig, err := getConfigValue(configDir, configName, "url")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlFromConfig, nil
|
||||||
|
}
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
)
|
)
|
||||||
|
|
||||||
const prefix = "Searching... "
|
const prefix = "Searching... "
|
||||||
|
@ -24,13 +25,14 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
||||||
Use: "repos [config-name]",
|
Use: "repos [config-name]",
|
||||||
Short: "List all repositories",
|
Short: "List all repositories",
|
||||||
Long: `List all repositories`,
|
Long: `List all repositories`,
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
if servURL == "" {
|
if servURL == "" {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||||
|
@ -41,12 +43,12 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if urlFromConfig == "" {
|
if urlFromConfig == "" {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
servURL = urlFromConfig
|
servURL = urlFromConfig
|
||||||
} else {
|
} else {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +98,12 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
||||||
|
|
||||||
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
||||||
repoCmd.Flags().StringVar(&servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
repoCmd.AddCommand(NewListReposCommand(searchService))
|
||||||
repoCmd.Flags().StringVarP(&user, "user", "u", "", `User Credentials of zot server in "username:password" format`)
|
|
||||||
repoCmd.Flags().BoolVar(&debug, "debug", false, "Show debug output")
|
repoCmd.Flags().StringVar(&servURL, cmdflags.URLFlag, "", "Specify zot server URL if config-name is not mentioned")
|
||||||
|
repoCmd.Flags().StringVarP(&user, cmdflags.UserFlag, "u", "",
|
||||||
|
`User Credentials of zot server in "username:password" format`)
|
||||||
|
repoCmd.Flags().BoolVar(&debug, cmdflags.DebugFlag, false, "Show debug output")
|
||||||
|
|
||||||
return repoCmd
|
return repoCmd
|
||||||
}
|
}
|
25
pkg/cli/repos_sub_cmd.go
Normal file
25
pkg/cli/repos_sub_cmd.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
func NewListReposCommand(searchService SearchService) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List all repositories",
|
||||||
|
Long: "List all repositories",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchRepos(searchConfig)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
55
pkg/cli/repos_test.go
Normal file
55
pkg/cli/repos_test.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli //nolint:testpackage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
"zotregistry.io/zot/pkg/api"
|
||||||
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
|
"zotregistry.io/zot/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReposCommand(t *testing.T) {
|
||||||
|
Convey("repos", t, func() {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"repostest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
args := []string{"list"}
|
||||||
|
cmd := NewRepoCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "repostest", "")
|
||||||
|
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, "repo1")
|
||||||
|
So(actual, ShouldContainSubstring, "repo2")
|
||||||
|
})
|
||||||
|
}
|
127
pkg/cli/root.go
127
pkg/cli/root.go
|
@ -18,10 +18,11 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"zotregistry.io/zot/errors"
|
zerr "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"
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||||
zlog "zotregistry.io/zot/pkg/log"
|
zlog "zotregistry.io/zot/pkg/log"
|
||||||
|
@ -213,7 +214,13 @@ func NewCliRootCmd() *cobra.Command {
|
||||||
// additional cmds
|
// additional cmds
|
||||||
enableCli(rootCmd)
|
enableCli(rootCmd)
|
||||||
// "version"
|
// "version"
|
||||||
rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit")
|
rootCmd.Flags().BoolVarP(&showVersion, cmdflags.VersionFlag, "v", false, "show the version and exit")
|
||||||
|
rootCmd.PersistentFlags().String(cmdflags.URLFlag, "",
|
||||||
|
"Specify zot server URL if config-name is not mentioned")
|
||||||
|
rootCmd.PersistentFlags().String(cmdflags.ConfigFlag, "",
|
||||||
|
"Specify the repository where to connect")
|
||||||
|
rootCmd.PersistentFlags().StringP(cmdflags.UserFlag, "u", "",
|
||||||
|
`User Credentials of zot server in "username:password" format`)
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
@ -225,18 +232,18 @@ func validateStorageConfig(cfg *config.Config, log zlog.Logger) error {
|
||||||
|
|
||||||
for _, storageConfig := range cfg.Storage.SubPaths {
|
for _, storageConfig := range cfg.Storage.SubPaths {
|
||||||
if strings.EqualFold(defaultRootDir, storageConfig.RootDirectory) {
|
if strings.EqualFold(defaultRootDir, storageConfig.RootDirectory) {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("storage subpaths cannot use default storage root directory")
|
log.Error().Err(zerr.ErrBadConfig).Msg("storage subpaths cannot use default storage root directory")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
expConfig, ok := expConfigMap[storageConfig.RootDirectory]
|
expConfig, ok := expConfigMap[storageConfig.RootDirectory]
|
||||||
if ok {
|
if ok {
|
||||||
equal := expConfig.ParamsEqual(storageConfig)
|
equal := expConfig.ParamsEqual(storageConfig)
|
||||||
if !equal {
|
if !equal {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("storage config with same root directory should have same parameters")
|
log.Error().Err(zerr.ErrBadConfig).Msg("storage config with same root directory should have same parameters")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
expConfigMap[storageConfig.RootDirectory] = storageConfig
|
expConfigMap[storageConfig.RootDirectory] = storageConfig
|
||||||
|
@ -251,31 +258,31 @@ func validateCacheConfig(cfg *config.Config, log zlog.Logger) error {
|
||||||
// dedupe true, remote storage, remoteCache true, but no cacheDriver (remote)
|
// dedupe true, remote storage, remoteCache true, but no cacheDriver (remote)
|
||||||
//nolint: lll
|
//nolint: lll
|
||||||
if cfg.Storage.Dedupe && cfg.Storage.StorageDriver != nil && cfg.Storage.RemoteCache && cfg.Storage.CacheDriver == nil {
|
if cfg.Storage.Dedupe && cfg.Storage.StorageDriver != nil && cfg.Storage.RemoteCache && cfg.Storage.CacheDriver == nil {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg(
|
log.Error().Err(zerr.ErrBadConfig).Msg(
|
||||||
"dedupe set to true with remote storage and caching, but no remote cache configured!")
|
"dedupe set to true with remote storage and caching, but no remote cache configured!")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Storage.CacheDriver != nil && cfg.Storage.RemoteCache {
|
if cfg.Storage.CacheDriver != nil && cfg.Storage.RemoteCache {
|
||||||
// local storage with remote caching
|
// local storage with remote caching
|
||||||
if cfg.Storage.StorageDriver == nil {
|
if cfg.Storage.StorageDriver == nil {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("cannot have local storage driver with remote caching!")
|
log.Error().Err(zerr.ErrBadConfig).Msg("cannot have local storage driver with remote caching!")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsupported cache driver
|
// unsupported cache driver
|
||||||
if cfg.Storage.CacheDriver["name"] != storageConstants.DynamoDBDriverName {
|
if cfg.Storage.CacheDriver["name"] != storageConstants.DynamoDBDriverName {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
log.Error().Err(zerr.ErrBadConfig).
|
||||||
Interface("cacheDriver", cfg.Storage.CacheDriver["name"]).Msg("unsupported cache driver")
|
Interface("cacheDriver", cfg.Storage.CacheDriver["name"]).Msg("unsupported cache driver")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Storage.RemoteCache && cfg.Storage.CacheDriver != nil {
|
if !cfg.Storage.RemoteCache && cfg.Storage.CacheDriver != nil {
|
||||||
log.Warn().Err(errors.ErrBadConfig).Str("directory", cfg.Storage.RootDirectory).
|
log.Warn().Err(zerr.ErrBadConfig).Str("directory", cfg.Storage.RootDirectory).
|
||||||
Msg("remoteCache set to false but cacheDriver config (remote caching) provided for directory" +
|
Msg("remoteCache set to false but cacheDriver config (remote caching) provided for directory" +
|
||||||
"will ignore and use local caching")
|
"will ignore and use local caching")
|
||||||
}
|
}
|
||||||
|
@ -285,30 +292,30 @@ func validateCacheConfig(cfg *config.Config, log zlog.Logger) error {
|
||||||
// dedupe true, remote storage, remoteCache true, but no cacheDriver (remote)
|
// dedupe true, remote storage, remoteCache true, but no cacheDriver (remote)
|
||||||
//nolint: lll
|
//nolint: lll
|
||||||
if subPath.Dedupe && subPath.StorageDriver != nil && subPath.RemoteCache && subPath.CacheDriver == nil {
|
if subPath.Dedupe && subPath.StorageDriver != nil && subPath.RemoteCache && subPath.CacheDriver == nil {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("dedupe set to true with remote storage and caching, but no remote cache configured!")
|
log.Error().Err(zerr.ErrBadConfig).Msg("dedupe set to true with remote storage and caching, but no remote cache configured!")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if subPath.CacheDriver != nil && subPath.RemoteCache {
|
if subPath.CacheDriver != nil && subPath.RemoteCache {
|
||||||
// local storage with remote caching
|
// local storage with remote caching
|
||||||
if subPath.StorageDriver == nil {
|
if subPath.StorageDriver == nil {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("cannot have local storage driver with remote caching!")
|
log.Error().Err(zerr.ErrBadConfig).Msg("cannot have local storage driver with remote caching!")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsupported cache driver
|
// unsupported cache driver
|
||||||
if subPath.CacheDriver["name"] != storageConstants.DynamoDBDriverName {
|
if subPath.CacheDriver["name"] != storageConstants.DynamoDBDriverName {
|
||||||
log.Error().Err(errors.ErrBadConfig).Interface("cacheDriver", cfg.Storage.CacheDriver["name"]).
|
log.Error().Err(zerr.ErrBadConfig).Interface("cacheDriver", cfg.Storage.CacheDriver["name"]).
|
||||||
Msg("unsupported cache driver")
|
Msg("unsupported cache driver")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !subPath.RemoteCache && subPath.CacheDriver != nil {
|
if !subPath.RemoteCache && subPath.CacheDriver != nil {
|
||||||
log.Warn().Err(errors.ErrBadConfig).Str("directory", cfg.Storage.RootDirectory).
|
log.Warn().Err(zerr.ErrBadConfig).Str("directory", cfg.Storage.RootDirectory).
|
||||||
Msg("remoteCache set to false but cacheDriver config (remote caching) provided for directory," +
|
Msg("remoteCache set to false but cacheDriver config (remote caching) provided for directory," +
|
||||||
"will ignore and use local caching")
|
"will ignore and use local caching")
|
||||||
}
|
}
|
||||||
|
@ -331,27 +338,27 @@ func validateExtensionsConfig(cfg *config.Config, log zlog.Logger) error {
|
||||||
// it would make sense to also check for mgmt and user prefs to be enabled,
|
// it would make sense to also check for mgmt and user prefs to be enabled,
|
||||||
// but those are both enabled by having the search and ui extensions enabled
|
// but those are both enabled by having the search and ui extensions enabled
|
||||||
if cfg.Extensions.Search == nil || !*cfg.Extensions.Search.Enable {
|
if cfg.Extensions.Search == nil || !*cfg.Extensions.Search.Enable {
|
||||||
log.Warn().Err(errors.ErrBadConfig).Msg("UI functionality can't be used without search extension.")
|
log.Warn().Err(zerr.ErrBadConfig).Msg("UI functionality can't be used without search extension.")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
if cfg.Storage.StorageDriver != nil && cfg.Extensions != nil && cfg.Extensions.Search != nil &&
|
if cfg.Storage.StorageDriver != nil && cfg.Extensions != nil && cfg.Extensions.Search != nil &&
|
||||||
cfg.Extensions.Search.Enable != nil && *cfg.Extensions.Search.Enable && cfg.Extensions.Search.CVE != nil {
|
cfg.Extensions.Search.Enable != nil && *cfg.Extensions.Search.Enable && cfg.Extensions.Search.CVE != nil {
|
||||||
log.Warn().Err(errors.ErrBadConfig).Msg("CVE functionality can't be used with remote storage. Please disable CVE")
|
log.Warn().Err(zerr.ErrBadConfig).Msg("CVE functionality can't be used with remote storage. Please disable CVE")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, subPath := range cfg.Storage.SubPaths {
|
for _, subPath := range cfg.Storage.SubPaths {
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
if subPath.StorageDriver != nil && cfg.Extensions != nil && cfg.Extensions.Search != nil &&
|
if subPath.StorageDriver != nil && cfg.Extensions != nil && cfg.Extensions.Search != nil &&
|
||||||
cfg.Extensions.Search.Enable != nil && *cfg.Extensions.Search.Enable && cfg.Extensions.Search.CVE != nil {
|
cfg.Extensions.Search.Enable != nil && *cfg.Extensions.Search.Enable && cfg.Extensions.Search.CVE != nil {
|
||||||
log.Warn().Err(errors.ErrBadConfig).Msg("CVE functionality can't be used with remote storage. Please disable CVE")
|
log.Warn().Err(zerr.ErrBadConfig).Msg("CVE functionality can't be used with remote storage. Please disable CVE")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,17 +409,17 @@ func validateConfiguration(config *config.Config, log zlog.Logger) error {
|
||||||
if len(config.Storage.StorageDriver) != 0 {
|
if len(config.Storage.StorageDriver) != 0 {
|
||||||
// enforce s3 driver in case of using storage driver
|
// enforce s3 driver in case of using storage driver
|
||||||
if config.Storage.StorageDriver["name"] != storageConstants.S3StorageDriverName {
|
if config.Storage.StorageDriver["name"] != storageConstants.S3StorageDriverName {
|
||||||
log.Error().Err(errors.ErrBadConfig).Interface("cacheDriver", config.Storage.StorageDriver["name"]).
|
log.Error().Err(zerr.ErrBadConfig).Interface("cacheDriver", config.Storage.StorageDriver["name"]).
|
||||||
Msg("unsupported storage driver")
|
Msg("unsupported storage driver")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforce filesystem storage in case sync feature is enabled
|
// enforce filesystem storage in case sync feature is enabled
|
||||||
if config.Extensions != nil && config.Extensions.Sync != nil {
|
if config.Extensions != nil && config.Extensions.Sync != nil {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("sync supports only filesystem storage")
|
log.Error().Err(zerr.ErrBadConfig).Msg("sync supports only filesystem storage")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,10 +431,10 @@ func validateConfiguration(config *config.Config, log zlog.Logger) error {
|
||||||
for route, storageConfig := range subPaths {
|
for route, storageConfig := range subPaths {
|
||||||
if len(storageConfig.StorageDriver) != 0 {
|
if len(storageConfig.StorageDriver) != 0 {
|
||||||
if storageConfig.StorageDriver["name"] != storageConstants.S3StorageDriverName {
|
if storageConfig.StorageDriver["name"] != storageConstants.S3StorageDriverName {
|
||||||
log.Error().Err(errors.ErrBadConfig).Str("subpath", route).Interface("storageDriver",
|
log.Error().Err(zerr.ErrBadConfig).Str("subpath", route).Interface("storageDriver",
|
||||||
storageConfig.StorageDriver["name"]).Msg("unsupported storage driver")
|
storageConfig.StorageDriver["name"]).Msg("unsupported storage driver")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,23 +463,23 @@ func validateOpenIDConfig(cfg *config.Config, log zlog.Logger) error {
|
||||||
if config.IsOpenIDSupported(provider) {
|
if config.IsOpenIDSupported(provider) {
|
||||||
if providerConfig.ClientID == "" || providerConfig.Issuer == "" ||
|
if providerConfig.ClientID == "" || providerConfig.Issuer == "" ||
|
||||||
len(providerConfig.Scopes) == 0 {
|
len(providerConfig.Scopes) == 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
log.Error().Err(zerr.ErrBadConfig).
|
||||||
Msg("OpenID provider config requires clientid, issuer and scopes parameters")
|
Msg("OpenID provider config requires clientid, issuer and scopes parameters")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
} else if config.IsOauth2Supported(provider) {
|
} else if config.IsOauth2Supported(provider) {
|
||||||
if providerConfig.ClientID == "" || len(providerConfig.Scopes) == 0 {
|
if providerConfig.ClientID == "" || len(providerConfig.Scopes) == 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
log.Error().Err(zerr.ErrBadConfig).
|
||||||
Msg("OAuth2 provider config requires clientid and scopes parameters")
|
Msg("OAuth2 provider config requires clientid and scopes parameters")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
log.Error().Err(zerr.ErrBadConfig).
|
||||||
Msg("unsupported openid/oauth2 provider")
|
Msg("unsupported openid/oauth2 provider")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -483,11 +490,11 @@ func validateOpenIDConfig(cfg *config.Config, log zlog.Logger) error {
|
||||||
func validateAuthzPolicies(config *config.Config, log zlog.Logger) error {
|
func validateAuthzPolicies(config *config.Config, log zlog.Logger) error {
|
||||||
if (config.HTTP.Auth == nil || (config.HTTP.Auth.HTPasswd.Path == "" && config.HTTP.Auth.LDAP == nil &&
|
if (config.HTTP.Auth == nil || (config.HTTP.Auth.HTPasswd.Path == "" && config.HTTP.Auth.LDAP == nil &&
|
||||||
config.HTTP.Auth.OpenID == nil)) && !authzContainsOnlyAnonymousPolicy(config) {
|
config.HTTP.Auth.OpenID == nil)) && !authzContainsOnlyAnonymousPolicy(config) {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
log.Error().Err(zerr.ErrBadConfig).
|
||||||
Msg("access control config requires one of httpasswd, ldap or openid authentication " +
|
Msg("access control config requires one of httpasswd, ldap or openid authentication " +
|
||||||
"or using only 'anonymousPolicy' policies")
|
"or using only 'anonymousPolicy' policies")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -730,15 +737,15 @@ func LoadConfiguration(config *config.Config, configPath string) error {
|
||||||
log := zlog.NewLogger(config.Log.Level, config.Log.Output)
|
log := zlog.NewLogger(config.Log.Level, config.Log.Output)
|
||||||
|
|
||||||
if len(metaData.Keys) == 0 {
|
if len(metaData.Keys) == 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("config doesn't contain any key:value pair")
|
log.Error().Err(zerr.ErrBadConfig).Msg("config doesn't contain any key:value pair")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(metaData.Unused) > 0 {
|
if len(metaData.Unused) > 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).Strs("keys", metaData.Unused).Msg("unknown keys")
|
log.Error().Err(zerr.ErrBadConfig).Strs("keys", metaData.Unused).Msg("unknown keys")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
|
@ -803,21 +810,21 @@ func validateLDAP(config *config.Config, log zlog.Logger) error {
|
||||||
log.Error().Str("userAttribute", ldap.UserAttribute).
|
log.Error().Str("userAttribute", ldap.UserAttribute).
|
||||||
Msg("invalid LDAP configuration, missing mandatory key: userAttribute")
|
Msg("invalid LDAP configuration, missing mandatory key: userAttribute")
|
||||||
|
|
||||||
return errors.ErrLDAPConfig
|
return zerr.ErrLDAPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldap.Address == "" {
|
if ldap.Address == "" {
|
||||||
log.Error().Str("address", ldap.Address).
|
log.Error().Str("address", ldap.Address).
|
||||||
Msg("invalid LDAP configuration, missing mandatory key: address")
|
Msg("invalid LDAP configuration, missing mandatory key: address")
|
||||||
|
|
||||||
return errors.ErrLDAPConfig
|
return zerr.ErrLDAPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldap.BaseDN == "" {
|
if ldap.BaseDN == "" {
|
||||||
log.Error().Str("basedn", ldap.BaseDN).
|
log.Error().Str("basedn", ldap.BaseDN).
|
||||||
Msg("invalid LDAP configuration, missing mandatory key: basedn")
|
Msg("invalid LDAP configuration, missing mandatory key: basedn")
|
||||||
|
|
||||||
return errors.ErrLDAPConfig
|
return zerr.ErrLDAPConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -830,7 +837,7 @@ func validateHTTP(config *config.Config, log zlog.Logger) error {
|
||||||
if err != nil || (port < 0 || port > 65535) {
|
if err != nil || (port < 0 || port > 65535) {
|
||||||
log.Error().Str("port", config.HTTP.Port).Msg("invalid port")
|
log.Error().Str("port", config.HTTP.Port).Msg("invalid port")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -840,27 +847,27 @@ func validateHTTP(config *config.Config, log zlog.Logger) error {
|
||||||
func validateGC(config *config.Config, log zlog.Logger) error {
|
func validateGC(config *config.Config, log zlog.Logger) error {
|
||||||
// enforce GC params
|
// enforce GC params
|
||||||
if config.Storage.GCDelay < 0 {
|
if config.Storage.GCDelay < 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).Dur("delay", config.Storage.GCDelay).
|
log.Error().Err(zerr.ErrBadConfig).Dur("delay", config.Storage.GCDelay).
|
||||||
Msg("invalid garbage-collect delay specified")
|
Msg("invalid garbage-collect delay specified")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Storage.GCInterval < 0 {
|
if config.Storage.GCInterval < 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).Dur("interval", config.Storage.GCInterval).
|
log.Error().Err(zerr.ErrBadConfig).Dur("interval", config.Storage.GCInterval).
|
||||||
Msg("invalid garbage-collect interval specified")
|
Msg("invalid garbage-collect interval specified")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.Storage.GC {
|
if !config.Storage.GC {
|
||||||
if config.Storage.GCDelay != 0 {
|
if config.Storage.GCDelay != 0 {
|
||||||
log.Warn().Err(errors.ErrBadConfig).
|
log.Warn().Err(zerr.ErrBadConfig).
|
||||||
Msg("garbage-collect delay specified without enabling garbage-collect, will be ignored")
|
Msg("garbage-collect delay specified without enabling garbage-collect, will be ignored")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Storage.GCInterval != 0 {
|
if config.Storage.GCInterval != 0 {
|
||||||
log.Warn().Err(errors.ErrBadConfig).
|
log.Warn().Err(zerr.ErrBadConfig).
|
||||||
Msg("periodic garbage-collect interval specified without enabling garbage-collect, will be ignored")
|
Msg("periodic garbage-collect interval specified without enabling garbage-collect, will be ignored")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -868,12 +875,12 @@ func validateGC(config *config.Config, log zlog.Logger) error {
|
||||||
// subpaths
|
// subpaths
|
||||||
for name, subPath := range config.Storage.SubPaths {
|
for name, subPath := range config.Storage.SubPaths {
|
||||||
if subPath.GC && subPath.GCDelay <= 0 {
|
if subPath.GC && subPath.GCDelay <= 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
log.Error().Err(zerr.ErrBadConfig).
|
||||||
Str("subPath", name).
|
Str("subPath", name).
|
||||||
Interface("gcDelay", subPath.GCDelay).
|
Interface("gcDelay", subPath.GCDelay).
|
||||||
Msg("invalid GC delay configuration - cannot be negative or zero")
|
Msg("invalid GC delay configuration - cannot be negative or zero")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,10 +893,10 @@ func validateSync(config *config.Config, log zlog.Logger) error {
|
||||||
for id, regCfg := range config.Extensions.Sync.Registries {
|
for id, regCfg := range config.Extensions.Sync.Registries {
|
||||||
// check retry options are configured for sync
|
// check retry options are configured for sync
|
||||||
if regCfg.MaxRetries != nil && regCfg.RetryDelay == nil {
|
if regCfg.MaxRetries != nil && regCfg.RetryDelay == nil {
|
||||||
log.Error().Err(errors.ErrBadConfig).Int("id", id).Interface("extensions.sync.registries[id]",
|
log.Error().Err(zerr.ErrBadConfig).Int("id", id).Interface("extensions.sync.registries[id]",
|
||||||
config.Extensions.Sync.Registries[id]).Msg("retryDelay is required when using maxRetries")
|
config.Extensions.Sync.Registries[id]).Msg("retryDelay is required when using maxRetries")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if regCfg.Content != nil {
|
if regCfg.Content != nil {
|
||||||
|
@ -902,11 +909,11 @@ func validateSync(config *config.Config, log zlog.Logger) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.StripPrefix && !strings.Contains(content.Prefix, "/*") && content.Destination == "/" {
|
if content.StripPrefix && !strings.Contains(content.Prefix, "/*") && content.Destination == "/" {
|
||||||
log.Error().Err(errors.ErrBadConfig).
|
log.Error().Err(zerr.ErrBadConfig).
|
||||||
Interface("sync content", content).
|
Interface("sync content", content).
|
||||||
Msg("sync config: can not use stripPrefix true and destination '/' without using glob patterns in prefix")
|
Msg("sync config: can not use stripPrefix true and destination '/' without using glob patterns in prefix")
|
||||||
|
|
||||||
return errors.ErrBadConfig
|
return zerr.ErrBadConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ func TestServe(t *testing.T) {
|
||||||
Convey("config with missing rootDir", func(c C) {
|
Convey("config with missing rootDir", func(c C) {
|
||||||
rootDir := t.TempDir()
|
rootDir := t.TempDir()
|
||||||
|
|
||||||
// missing storag config should result in an error in Controller.Init()
|
// missing storage config should result in an error in Controller.Init()
|
||||||
content := []byte(`{
|
content := []byte(`{
|
||||||
"distSpecVersion": "1.1.0-dev",
|
"distSpecVersion": "1.1.0-dev",
|
||||||
"http": {
|
"http": {
|
||||||
|
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:dupl
|
//nolint:dupl
|
||||||
|
@ -21,7 +22,7 @@ func NewSearchCommand(searchService SearchService) *cobra.Command {
|
||||||
|
|
||||||
var isSpinner, verifyTLS, verbose, debug bool
|
var isSpinner, verifyTLS, verbose, debug bool
|
||||||
|
|
||||||
imageCmd := &cobra.Command{
|
searchCmd := &cobra.Command{
|
||||||
Use: "search [config-name]",
|
Use: "search [config-name]",
|
||||||
Short: "Search images and their tags",
|
Short: "Search images and their tags",
|
||||||
Long: `Search repos or images
|
Long: `Search repos or images
|
||||||
|
@ -36,13 +37,14 @@ Example:
|
||||||
zli search --subject repo@sha256:f9a0981...
|
zli search --subject repo@sha256:f9a0981...
|
||||||
zli search --subject repo:tag
|
zli search --subject repo:tag
|
||||||
`,
|
`,
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := path.Join(home + "/.zot")
|
configPath := path.Join(home, "/.zot")
|
||||||
if servURL == "" {
|
if servURL == "" {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||||
|
@ -53,12 +55,12 @@ Example:
|
||||||
}
|
}
|
||||||
|
|
||||||
if urlFromConfig == "" {
|
if urlFromConfig == "" {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
|
|
||||||
servURL = urlFromConfig
|
servURL = urlFromConfig
|
||||||
} else {
|
} else {
|
||||||
return zotErrors.ErrNoURLProvided
|
return zerr.ErrNoURLProvided
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,27 +109,32 @@ Example:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSearchFlags(imageCmd, searchImageParams, &servURL, &user, &outputFormat, &verbose, &debug)
|
setupSearchFlags(searchCmd, searchImageParams, &servURL, &user, &outputFormat, &verbose, &debug)
|
||||||
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
searchCmd.SetUsageTemplate(searchCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
||||||
return imageCmd
|
searchCmd.AddCommand(NewSearchQueryCommand(searchService))
|
||||||
|
searchCmd.AddCommand(NewSearchSubjectCommand(searchService))
|
||||||
|
|
||||||
|
return searchCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupSearchFlags(imageCmd *cobra.Command, searchImageParams map[string]*string,
|
func setupSearchFlags(searchCmd *cobra.Command, searchImageParams map[string]*string,
|
||||||
servURL, user, outputFormat *string, verbose *bool, debug *bool,
|
servURL, user, outputFormat *string, verbose *bool, debug *bool,
|
||||||
) {
|
) {
|
||||||
searchImageParams["query"] = imageCmd.Flags().StringP("query", "q", "",
|
searchImageParams["query"] = searchCmd.Flags().StringP("query", "q", "",
|
||||||
"Specify what repo or image(repo:tag) to be searched")
|
"Specify what repo or image(repo:tag) to be searched")
|
||||||
|
|
||||||
searchImageParams["subject"] = imageCmd.Flags().StringP("subject", "s", "",
|
searchImageParams["subject"] = searchCmd.Flags().StringP("subject", "s", "",
|
||||||
"List all referrers for this subject. The subject can be specified by tag(repo:tag) or by digest"+
|
"List all referrers for this subject. The subject can be specified by tag(repo:tag) or by digest"+
|
||||||
"(repo@digest)")
|
"(repo@digest)")
|
||||||
|
|
||||||
imageCmd.Flags().StringVar(servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
searchCmd.Flags().StringVar(servURL, cmdflags.URLFlag, "", "Specify zot server URL if config-name is not mentioned")
|
||||||
imageCmd.Flags().StringVarP(user, "user", "u", "", `User Credentials of zot server in "username:password" format`)
|
searchCmd.Flags().StringVarP(user, cmdflags.UserFlag, "u", "",
|
||||||
imageCmd.Flags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
|
`User Credentials of zot server in "username:password" format`)
|
||||||
imageCmd.Flags().BoolVar(verbose, "verbose", false, "Show verbose output")
|
searchCmd.PersistentFlags().StringVarP(outputFormat, cmdflags.OutputFormatFlag, "f", "",
|
||||||
imageCmd.Flags().BoolVar(debug, "debug", false, "Show debug output")
|
"Specify output format [text/json/yaml]")
|
||||||
|
searchCmd.PersistentFlags().BoolVar(verbose, cmdflags.VerboseFlag, false, "Show verbose output")
|
||||||
|
searchCmd.PersistentFlags().BoolVar(debug, cmdflags.DebugFlag, false, "Show debug output")
|
||||||
}
|
}
|
||||||
|
|
||||||
func globalSearch(searchConfig searchConfig) error {
|
func globalSearch(searchConfig searchConfig) error {
|
||||||
|
@ -150,5 +157,5 @@ func globalSearch(searchConfig searchConfig) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return zotErrors.ErrInvalidFlagsCombination
|
return zerr.ErrInvalidFlagsCombination
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,6 @@ import (
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ref[T any](input T) *T {
|
|
||||||
ref := input
|
|
||||||
|
|
||||||
return &ref
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
customArtTypeV1 = "application/custom.art.type.v1"
|
customArtTypeV1 = "application/custom.art.type.v1"
|
||||||
customArtTypeV2 = "application/custom.art.type.v2"
|
customArtTypeV2 = "application/custom.art.type.v2"
|
||||||
|
@ -428,7 +422,7 @@ func TestFormatsReferrersCLI(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
Convey("JSON format", func() {
|
Convey("JSON format", func() {
|
||||||
args := []string{"reftest", "--output", "json", "--subject", repo + "@" + image.DigestStr()}
|
args := []string{"reftest", "--format", "json", "--subject", repo + "@" + image.DigestStr()}
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||||
baseURL))
|
baseURL))
|
||||||
|
@ -446,7 +440,7 @@ func TestFormatsReferrersCLI(t *testing.T) {
|
||||||
fmt.Println(buff.String())
|
fmt.Println(buff.String())
|
||||||
})
|
})
|
||||||
Convey("YAML format", func() {
|
Convey("YAML format", func() {
|
||||||
args := []string{"reftest", "--output", "yaml", "--subject", repo + "@" + image.DigestStr()}
|
args := []string{"reftest", "--format", "yaml", "--subject", repo + "@" + image.DigestStr()}
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||||
baseURL))
|
baseURL))
|
||||||
|
@ -464,7 +458,7 @@ func TestFormatsReferrersCLI(t *testing.T) {
|
||||||
fmt.Println(buff.String())
|
fmt.Println(buff.String())
|
||||||
})
|
})
|
||||||
Convey("Invalid format", func() {
|
Convey("Invalid format", func() {
|
||||||
args := []string{"reftest", "--output", "invalid_format", "--subject", repo + "@" + image.DigestStr()}
|
args := []string{"reftest", "--format", "invalid_format", "--subject", repo + "@" + image.DigestStr()}
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||||
baseURL))
|
baseURL))
|
||||||
|
@ -488,7 +482,7 @@ func TestReferrersCLIErrors(t *testing.T) {
|
||||||
cmd := NewSearchCommand(new(searchService))
|
cmd := NewSearchCommand(new(searchService))
|
||||||
|
|
||||||
Convey("no url provided", func() {
|
Convey("no url provided", func() {
|
||||||
args := []string{"reftest", "--output", "invalid", "--query", "repo/alpine"}
|
args := []string{"reftest", "--format", "invalid", "--query", "repo/alpine"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"reftest","showspinner":false}]}`)
|
||||||
|
|
||||||
|
@ -594,3 +588,9 @@ func TestReferrersCLIErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ref[T any](input T) *T {
|
||||||
|
ref := input
|
||||||
|
|
||||||
|
return &ref
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,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/cli/cmdflags"
|
||||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
)
|
)
|
||||||
|
@ -240,7 +241,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
||||||
cmd := NewSearchCommand(new(searchService))
|
cmd := NewSearchCommand(new(searchService))
|
||||||
|
|
||||||
Convey("JSON format", func() {
|
Convey("JSON format", func() {
|
||||||
args := []string{"searchtest", "--output", "json", "--query", "repo/alpine"}
|
args := []string{"searchtest", "--format", "json", "--query", "repo/alpine"}
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||||
baseURL))
|
baseURL))
|
||||||
|
@ -257,7 +258,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("YAML format", func() {
|
Convey("YAML format", func() {
|
||||||
args := []string{"searchtest", "--output", "yaml", "--query", "repo/alpine"}
|
args := []string{"searchtest", "--format", "yaml", "--query", "repo/alpine"}
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||||
baseURL))
|
baseURL))
|
||||||
|
@ -274,7 +275,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Invalid format", func() {
|
Convey("Invalid format", func() {
|
||||||
args := []string{"searchtest", "--output", "invalid", "--query", "repo/alpine"}
|
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||||
baseURL))
|
baseURL))
|
||||||
|
@ -296,7 +297,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
||||||
cmd := NewSearchCommand(new(searchService))
|
cmd := NewSearchCommand(new(searchService))
|
||||||
|
|
||||||
Convey("no url provided", func() {
|
Convey("no url provided", func() {
|
||||||
args := []string{"searchtest", "--output", "invalid", "--query", "repo/alpine"}
|
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"searchtest","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"searchtest","showspinner":false}]}`)
|
||||||
|
|
||||||
|
@ -311,7 +312,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("getConfigValue", func() {
|
Convey("getConfigValue", func() {
|
||||||
args := []string{"searchtest", "--output", "invalid", "--query", "repo/alpine"}
|
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`bad-json`)
|
configPath := makeConfigFile(`bad-json`)
|
||||||
|
|
||||||
|
@ -358,7 +359,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("url from config is empty", func() {
|
Convey("url from config is empty", func() {
|
||||||
args := []string{"searchtest", "--output", "invalid", "--query", "repo/alpine"}
|
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||||
|
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"searchtest", "url":"", "showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"searchtest", "url":"", "showspinner":false}]}`)
|
||||||
|
|
||||||
|
@ -402,3 +403,142 @@ func TestSearchCLIErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchCommandGQL(t *testing.T) {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
defaultVal := true
|
||||||
|
conf.Extensions = &extconf.ExtensionConfig{
|
||||||
|
Search: &extconf.SearchConfig{
|
||||||
|
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("commands without gql", t, func() {
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
Convey("query", func() {
|
||||||
|
args := []string{"query", "repo/al"}
|
||||||
|
cmd := NewSearchCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo 8c25cb36 false 100B")
|
||||||
|
So(actual, ShouldContainSubstring, "repo 100B 2010-01-01 01:01:01 +0000 UTC 0 0")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("query command errors", func() {
|
||||||
|
// no url
|
||||||
|
args := []string{"repo/al"}
|
||||||
|
cmd := NewSearchQueryCommand(mockService{})
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("subject", func() {
|
||||||
|
err := test.UploadImage(test.CreateRandomImage(), baseURL, "repo", "tag")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
args := []string{"subject", "repo:tag"}
|
||||||
|
cmd := NewSearchCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||||
|
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, "ArtifactType 100 B Digest")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("subject command errors", func() {
|
||||||
|
// no url
|
||||||
|
args := []string{"repo:tag"}
|
||||||
|
cmd := NewSearchSubjectCommand(mockService{})
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchCommandREST(t *testing.T) {
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("commands without gql", t, func() {
|
||||||
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||||
|
baseURL))
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
Convey("query", func() {
|
||||||
|
args := []string{"query", "repo/al"}
|
||||||
|
cmd := NewSearchCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("subject", func() {
|
||||||
|
err := test.UploadImage(test.CreateRandomImage(), baseURL, "repo", "tag")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
args := []string{"subject", "repo:tag"}
|
||||||
|
cmd := NewSearchCommand(mockService{})
|
||||||
|
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||||
|
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,
|
||||||
|
"art.type 100 B sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
459
pkg/cli/search_functions.go
Normal file
459
pkg/cli/search_functions.go
Normal file
|
@ -0,0 +1,459 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SearchAllImages(config searchConfig) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
imageErr := make(chan stringResult)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go config.searchService.getAllImages(ctx, config, username, password, imageErr, &wg)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
|
||||||
|
go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
|
||||||
|
wg.Wait()
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchAllImagesGQL(config searchConfig) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
imageList, err := config.searchService.getImagesGQL(ctx, config, username, password, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageListData := []imageStruct{}
|
||||||
|
|
||||||
|
for _, image := range imageList.Results {
|
||||||
|
imageListData = append(imageListData, imageStruct(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
return printImageResult(config, imageListData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchImageByName(config searchConfig, image string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
imageErr := make(chan stringResult)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go config.searchService.getImageByName(ctx, config, username, password,
|
||||||
|
image, imageErr, &wg)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchImageByNameGQL(config searchConfig, imageName string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
repo, tag := zcommon.GetImageDirAndTag(imageName)
|
||||||
|
|
||||||
|
imageList, err := config.searchService.getImagesGQL(ctx, config, username, password, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageListData := []imageStruct{}
|
||||||
|
|
||||||
|
for _, image := range imageList.Results {
|
||||||
|
if tag == "" || image.Tag == tag {
|
||||||
|
imageListData = append(imageListData, imageStruct(image))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return printImageResult(config, imageListData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchImagesByDigest(config searchConfig, digest string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
imageErr := make(chan stringResult)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go config.searchService.getImagesByDigest(ctx, config, username, password,
|
||||||
|
digest, imageErr, &wg)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchDerivedImageListGQL(config searchConfig, derivedImage string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
imageList, err := config.searchService.getDerivedImageListGQL(ctx, config, username,
|
||||||
|
password, derivedImage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageListData := []imageStruct{}
|
||||||
|
|
||||||
|
for _, image := range imageList.DerivedImageList.Results {
|
||||||
|
imageListData = append(imageListData, imageStruct(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
return printImageResult(config, imageListData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchBaseImageListGQL(config searchConfig, baseImage string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
imageList, err := config.searchService.getBaseImageListGQL(ctx, config, username,
|
||||||
|
password, baseImage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageListData := []imageStruct{}
|
||||||
|
|
||||||
|
for _, image := range imageList.BaseImageList.Results {
|
||||||
|
imageListData = append(imageListData, imageStruct(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
return printImageResult(config, imageListData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchImagesForDigestGQL(config searchConfig, digest string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
imageList, err := config.searchService.getImagesForDigestGQL(ctx, config, username, password, digest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageListData := []imageStruct{}
|
||||||
|
|
||||||
|
for _, image := range imageList.Results {
|
||||||
|
imageListData = append(imageListData, imageStruct(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := printImageResult(config, imageListData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var cveList *cveResult
|
||||||
|
|
||||||
|
err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cveList, err = config.searchService.getCveByImageGQL(ctx, config, username, password, image, searchedCveID)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(config.resultWriter,
|
||||||
|
"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}, maxRetries, cveDBRetryInterval*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cveList.Data.CVEListForImage.CVEList) == 0 {
|
||||||
|
fmt.Fprint(config.resultWriter, "No CVEs found for image\n")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
if *config.outputFormat == defaultOutputFormat || *config.outputFormat == "" {
|
||||||
|
printCVETableHeader(&builder, *config.verbose, 0, 0, 0)
|
||||||
|
fmt.Fprint(config.resultWriter, builder.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := cveList.string(*config.outputFormat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(config.resultWriter, out)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var imageList *zcommon.ImagesForCve
|
||||||
|
|
||||||
|
err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
imageList, err = config.searchService.getTagsForCVEGQL(ctx, config, username, password,
|
||||||
|
repo, cveid)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(config.resultWriter,
|
||||||
|
"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}, maxRetries, cveDBRetryInterval*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageListData := []imageStruct{}
|
||||||
|
|
||||||
|
for _, image := range imageList.Results {
|
||||||
|
imageListData = append(imageListData, imageStruct(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
return printImageResult(config, imageListData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var fixedTags *zcommon.ImageListWithCVEFixedResponse
|
||||||
|
|
||||||
|
err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
fixedTags, err = config.searchService.getFixedTagsForCVEGQL(ctx, config, username, password,
|
||||||
|
repo, cveid)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(config.resultWriter,
|
||||||
|
"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}, maxRetries, cveDBRetryInterval*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageList := make([]imageStruct, 0, len(fixedTags.Results))
|
||||||
|
|
||||||
|
for _, image := range fixedTags.Results {
|
||||||
|
imageList = append(imageList, imageStruct(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
return printImageResult(config, imageList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobalSearchGQL(config searchConfig, query string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
globalSearchResult, err := config.searchService.globalSearchGQL(ctx, config, username, password, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesList := []imageStruct{}
|
||||||
|
|
||||||
|
for _, image := range globalSearchResult.Images {
|
||||||
|
imagesList = append(imagesList, imageStruct(image))
|
||||||
|
}
|
||||||
|
|
||||||
|
reposList := []repoStruct{}
|
||||||
|
|
||||||
|
for _, repo := range globalSearchResult.Repos {
|
||||||
|
reposList = append(reposList, repoStruct(repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := printImageResult(config, imagesList); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return printRepoResults(config, reposList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchReferrersGQL(config searchConfig, subject string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
|
||||||
|
repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := ref
|
||||||
|
|
||||||
|
if refIsTag {
|
||||||
|
digest, err = fetchImageDigest(repo, ref, username, password, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := config.searchService.getReferrersGQL(context.Background(), config, username, password, repo, digest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
referrersList := referrersResult(response.Referrers)
|
||||||
|
|
||||||
|
maxArtifactTypeLen := math.MinInt
|
||||||
|
|
||||||
|
for _, referrer := range referrersList {
|
||||||
|
if maxArtifactTypeLen < len(referrer.ArtifactType) {
|
||||||
|
maxArtifactTypeLen = len(referrer.ArtifactType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printReferrersTableHeader(config, config.resultWriter, maxArtifactTypeLen)
|
||||||
|
|
||||||
|
return printReferrersResult(config, referrersList, maxArtifactTypeLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchReferrers(config searchConfig, subject string) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
|
||||||
|
repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := ref
|
||||||
|
|
||||||
|
if refIsTag {
|
||||||
|
digest, err = fetchImageDigest(repo, ref, username, password, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
referrersList, err := config.searchService.getReferrers(context.Background(), config, username, password,
|
||||||
|
repo, digest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxArtifactTypeLen := math.MinInt
|
||||||
|
|
||||||
|
for _, referrer := range referrersList {
|
||||||
|
if maxArtifactTypeLen < len(referrer.ArtifactType) {
|
||||||
|
maxArtifactTypeLen = len(referrer.ArtifactType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printReferrersTableHeader(config, config.resultWriter, maxArtifactTypeLen)
|
||||||
|
|
||||||
|
return printReferrersResult(config, referrersList, maxArtifactTypeLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchRepos(config searchConfig) error {
|
||||||
|
username, password := getUsernameAndPassword(*config.user)
|
||||||
|
repoErr := make(chan stringResult)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go config.searchService.getRepos(ctx, config, username, password, repoErr, &wg)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
|
||||||
|
go collectResults(config, &wg, repoErr, cancel, printImageTableHeader, errCh)
|
||||||
|
wg.Wait()
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
761
pkg/cli/search_functions_test.go
Normal file
761
pkg/cli/search_functions_test.go
Normal file
|
@ -0,0 +1,761 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
//
|
||||||
|
//nolint:dupl
|
||||||
|
package cli //nolint:testpackage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
|
"zotregistry.io/zot/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSearchAllImages(t *testing.T) {
|
||||||
|
Convey("SearchAllImages", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getAllImagesFn: func(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
channel chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
) {
|
||||||
|
str, err := getMockImageStruct().stringPlainText(10, 10, 10, false)
|
||||||
|
|
||||||
|
channel <- stringResult{StrValue: str, Err: err}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchAllImages(searchConfig)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchAllImagesGQL(t *testing.T) {
|
||||||
|
Convey("SearchAllImagesGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string,
|
||||||
|
) (*common.ImageListResponse, error) {
|
||||||
|
return &common.ImageListResponse{ImageList: common.ImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{getMockImageSummary()},
|
||||||
|
},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchAllImagesGQL(searchConfig)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchAllImagesGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string,
|
||||||
|
) (*common.ImageListResponse, error) {
|
||||||
|
return &common.ImageListResponse{ImageList: common.ImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{getMockImageSummary()},
|
||||||
|
},
|
||||||
|
}}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchAllImagesGQL(searchConfig)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchImageByName(t *testing.T) {
|
||||||
|
Convey("SearchImageByName", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImageByNameFn: func(ctx context.Context, config searchConfig, username string, password string, imageName string,
|
||||||
|
channel chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
) {
|
||||||
|
str, err := getMockImageStruct().stringPlainText(10, 10, 10, false)
|
||||||
|
|
||||||
|
channel <- stringResult{StrValue: str, Err: err}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImageByName(searchConfig, "repo")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchImageByName error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImageByNameFn: func(ctx context.Context, config searchConfig, username string, password string, imageName string,
|
||||||
|
channel chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
) {
|
||||||
|
channel <- stringResult{StrValue: "", Err: zerr.ErrInjected}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImageByName(searchConfig, "repo")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchImageByNameGQL(t *testing.T) {
|
||||||
|
Convey("SearchImageByNameGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string,
|
||||||
|
) (*common.ImageListResponse, error) {
|
||||||
|
return &common.ImageListResponse{ImageList: common.ImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{getMockImageSummary()},
|
||||||
|
},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImageByNameGQL(searchConfig, "repo")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchImageByNameGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesGQLFn: func(ctx context.Context, config searchConfig, username, password, imageName string,
|
||||||
|
) (*common.ImageListResponse, error) {
|
||||||
|
return &common.ImageListResponse{ImageList: common.ImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{getMockImageSummary()},
|
||||||
|
},
|
||||||
|
}}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImageByNameGQL(searchConfig, "repo")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchImagesByDigest(t *testing.T) {
|
||||||
|
Convey("SearchImagesByDigest", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesByDigestFn: func(ctx context.Context, config searchConfig, username string, password string, digest string,
|
||||||
|
rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
) {
|
||||||
|
str, err := getMockImageStruct().stringPlainText(10, 10, 10, false)
|
||||||
|
|
||||||
|
rch <- stringResult{StrValue: str, Err: err}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImagesByDigest(searchConfig, godigest.FromString("str").String())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchImagesByDigest error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesByDigestFn: func(ctx context.Context, config searchConfig, username string, password string, digest string,
|
||||||
|
rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||||
|
) {
|
||||||
|
rch <- stringResult{StrValue: "", Err: zerr.ErrInjected}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImagesByDigest(searchConfig, godigest.FromString("str").String())
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchDerivedImageListGQL(t *testing.T) {
|
||||||
|
Convey("SearchDerivedImageListGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getDerivedImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
derivedImage string) (*common.DerivedImageListResponse, error,
|
||||||
|
) {
|
||||||
|
return &common.DerivedImageListResponse{DerivedImageList: common.DerivedImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{
|
||||||
|
getMockImageSummary(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchDerivedImageListGQL(searchConfig, "repo:tag")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchDerivedImageListGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getDerivedImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
derivedImage string) (*common.DerivedImageListResponse, error,
|
||||||
|
) {
|
||||||
|
return &common.DerivedImageListResponse{DerivedImageList: common.DerivedImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{Results: []common.ImageSummary{}},
|
||||||
|
}}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchDerivedImageListGQL(searchConfig, "repo:tag")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchBaseImageListGQL(t *testing.T) {
|
||||||
|
Convey("SearchBaseImageListGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getBaseImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
derivedImage string) (*common.BaseImageListResponse, error,
|
||||||
|
) {
|
||||||
|
return &common.BaseImageListResponse{BaseImageList: common.BaseImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{Results: []common.ImageSummary{
|
||||||
|
getMockImageSummary(),
|
||||||
|
}},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchBaseImageListGQL(searchConfig, "repo:tag")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchBaseImageListGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getBaseImageListGQLFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
derivedImage string) (*common.BaseImageListResponse, error,
|
||||||
|
) {
|
||||||
|
return &common.BaseImageListResponse{BaseImageList: common.BaseImageList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{Results: []common.ImageSummary{}},
|
||||||
|
}}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchBaseImageListGQL(searchConfig, "repo:tag")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchImagesForDigestGQL(t *testing.T) {
|
||||||
|
Convey("SearchImagesForDigestGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesForDigestGQLFn: func(ctx context.Context, config searchConfig, username string,
|
||||||
|
password string, digest string) (*common.ImagesForDigest, error,
|
||||||
|
) {
|
||||||
|
return &common.ImagesForDigest{ImagesForDigestList: common.ImagesForDigestList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{getMockImageSummary()},
|
||||||
|
},
|
||||||
|
}}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImagesForDigestGQL(searchConfig, "digest")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchImagesForDigestGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getImagesForDigestGQLFn: func(ctx context.Context, config searchConfig, username string,
|
||||||
|
password string, digest string) (*common.ImagesForDigest, error,
|
||||||
|
) {
|
||||||
|
return &common.ImagesForDigest{ImagesForDigestList: common.ImagesForDigestList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{},
|
||||||
|
}}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImagesForDigestGQL(searchConfig, "digest")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchCVEForImageGQL(t *testing.T) {
|
||||||
|
Convey("SearchCVEForImageGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
imageName string, searchedCVE string) (*cveResult, error,
|
||||||
|
) {
|
||||||
|
return &cveResult{
|
||||||
|
Data: cveData{
|
||||||
|
CVEListForImage: cveListForImage{
|
||||||
|
CVEList: []cve{
|
||||||
|
{
|
||||||
|
ID: "dummyCVEID",
|
||||||
|
Description: "Description of the CVE",
|
||||||
|
Title: "Title of that CVE",
|
||||||
|
Severity: "HIGH",
|
||||||
|
PackageList: []packageList{
|
||||||
|
{
|
||||||
|
Name: "packagename",
|
||||||
|
FixedVersion: "fixedver",
|
||||||
|
InstalledVersion: "installedver",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchCVEForImageGQL(searchConfig, "repo-test", "dummyCVEID")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchCVEForImageGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getCveByImageGQLFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
imageName string, searchedCVE string) (*cveResult, error,
|
||||||
|
) {
|
||||||
|
return &cveResult{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchCVEForImageGQL(searchConfig, "repo-test", "dummyCVEID")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchImagesByCVEIDGQL(t *testing.T) {
|
||||||
|
Convey("SearchImagesByCVEIDGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string) (*common.ImagesForCve, error,
|
||||||
|
) {
|
||||||
|
return &common.ImagesForCve{
|
||||||
|
ImagesForCVEList: common.ImagesForCVEList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{
|
||||||
|
getMockImageSummary(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImagesByCVEIDGQL(searchConfig, "repo", "CVE-12345")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchImagesByCVEIDGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string) (*common.ImagesForCve, error,
|
||||||
|
) {
|
||||||
|
return &common.ImagesForCve{
|
||||||
|
ImagesForCVEList: common.ImagesForCVEList{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{},
|
||||||
|
},
|
||||||
|
}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchImagesByCVEIDGQL(searchConfig, "repo", "CVE-12345")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchFixedTagsGQL(t *testing.T) {
|
||||||
|
Convey("SearchFixedTagsGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getFixedTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string) (*common.ImageListWithCVEFixedResponse, error,
|
||||||
|
) {
|
||||||
|
return &common.ImageListWithCVEFixedResponse{
|
||||||
|
ImageListWithCVEFixed: common.ImageListWithCVEFixed{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{
|
||||||
|
Results: []common.ImageSummary{getMockImageSummary()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchFixedTagsGQL(searchConfig, "repo", "CVE-12345")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo tag os/arch 8c25cb36 false 100B")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchFixedTagsGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getFixedTagsForCVEGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
imageName, cveID string) (*common.ImageListWithCVEFixedResponse, error,
|
||||||
|
) {
|
||||||
|
return &common.ImageListWithCVEFixedResponse{
|
||||||
|
ImageListWithCVEFixed: common.ImageListWithCVEFixed{
|
||||||
|
PaginatedImagesResult: common.PaginatedImagesResult{},
|
||||||
|
},
|
||||||
|
}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchFixedTagsGQL(searchConfig, "repo", "CVE-12345")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchReferrersGQL(t *testing.T) {
|
||||||
|
Convey("SearchReferrersGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getReferrersGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
repo, digest string) (*common.ReferrersResp, error,
|
||||||
|
) {
|
||||||
|
return &common.ReferrersResp{
|
||||||
|
ReferrersResult: common.ReferrersResult{
|
||||||
|
Referrers: []common.Referrer{{
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Size: 100,
|
||||||
|
ArtifactType: "art.type",
|
||||||
|
Digest: godigest.FromString("123").String(),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchReferrersGQL(searchConfig, "repo@"+godigest.FromString("str").String())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring,
|
||||||
|
"art.type 100 B sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchReferrersGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getReferrersGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
repo, digest string) (*common.ReferrersResp, error,
|
||||||
|
) {
|
||||||
|
return &common.ReferrersResp{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchReferrersGQL(searchConfig, "repo@"+godigest.FromString("str").String())
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalSearchGQL(t *testing.T) {
|
||||||
|
Convey("GlobalSearchGQL", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
globalSearchGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
query string) (*common.GlobalSearch, error,
|
||||||
|
) {
|
||||||
|
return &common.GlobalSearch{
|
||||||
|
Repos: []common.RepoSummary{{
|
||||||
|
Name: "repo",
|
||||||
|
Size: "100",
|
||||||
|
LastUpdated: time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC),
|
||||||
|
}},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := GlobalSearchGQL(searchConfig, "repo")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring,
|
||||||
|
"repo ")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GlobalSearchGQL error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
globalSearchGQLFn: func(ctx context.Context, config searchConfig, username, password,
|
||||||
|
query string) (*common.GlobalSearch, error,
|
||||||
|
) {
|
||||||
|
return &common.GlobalSearch{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := GlobalSearchGQL(searchConfig, "repo")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchReferrers(t *testing.T) {
|
||||||
|
Convey("SearchReferrers", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getReferrersFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
repo string, digest string) (referrersResult, error,
|
||||||
|
) {
|
||||||
|
return referrersResult([]common.Referrer{
|
||||||
|
{
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Size: 100,
|
||||||
|
ArtifactType: "art.type",
|
||||||
|
Digest: godigest.FromString("123").String(),
|
||||||
|
},
|
||||||
|
}), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchReferrers(searchConfig, "repo@"+godigest.FromString("str").String())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring,
|
||||||
|
"art.type 100 B sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("SearchReferrers error", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getReferrersFn: func(ctx context.Context, config searchConfig, username string, password string,
|
||||||
|
repo string, digest string) (referrersResult, error,
|
||||||
|
) {
|
||||||
|
return referrersResult{}, zerr.ErrInjected
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := SearchReferrers(searchConfig, "repo@"+godigest.FromString("str").String())
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchRepos(t *testing.T) {
|
||||||
|
Convey("SearchRepos", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{})
|
||||||
|
|
||||||
|
err := SearchRepos(searchConfig)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual := strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo1")
|
||||||
|
So(actual, ShouldContainSubstring, "repo2")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMockSearchConfig(buff *bytes.Buffer, mockService mockService) searchConfig {
|
||||||
|
return searchConfig{
|
||||||
|
resultWriter: buff,
|
||||||
|
user: ref(""),
|
||||||
|
searchService: mockService,
|
||||||
|
servURL: ref("http://127.0.0.1:8000"),
|
||||||
|
outputFormat: ref(""),
|
||||||
|
verifyTLS: ref(false),
|
||||||
|
fixedFlag: ref(false),
|
||||||
|
verbose: ref(false),
|
||||||
|
debug: ref(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMockImageStruct() imageStruct {
|
||||||
|
return imageStruct(common.ImageSummary{
|
||||||
|
RepoName: "repo", Tag: "tag",
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Digest: godigest.FromString("str").String(),
|
||||||
|
Size: "100",
|
||||||
|
Manifests: []common.ManifestSummary{{
|
||||||
|
Size: "100",
|
||||||
|
Platform: common.Platform{Os: "os", Arch: "arch"},
|
||||||
|
Digest: godigest.FromString("str").String(),
|
||||||
|
ConfigDigest: godigest.FromString("str").String(),
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMockImageSummary() common.ImageSummary {
|
||||||
|
return common.ImageSummary{
|
||||||
|
RepoName: "repo", Tag: "tag",
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Digest: godigest.FromString("str").String(),
|
||||||
|
Size: "100",
|
||||||
|
Manifests: []common.ManifestSummary{{
|
||||||
|
Size: "100",
|
||||||
|
Platform: common.Platform{Os: "os", Arch: "arch"},
|
||||||
|
Digest: godigest.FromString("str").String(),
|
||||||
|
ConfigDigest: godigest.FromString("str").String(),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUtils(t *testing.T) {
|
||||||
|
Convey("Utils", t, func() {
|
||||||
|
ok := haveSameArgs(field{"query", []struct {
|
||||||
|
Name string "json:\"name\""
|
||||||
|
}{
|
||||||
|
{Name: "arg1"}, {Name: "arg2"},
|
||||||
|
}}, GQLQuery{
|
||||||
|
Name: "query", Args: []string{"arg1"},
|
||||||
|
})
|
||||||
|
So(ok, ShouldBeFalse)
|
||||||
|
|
||||||
|
ok = haveSameArgs(field{"query", []struct {
|
||||||
|
Name string "json:\"name\""
|
||||||
|
}{
|
||||||
|
{Name: "arg1"}, {Name: "arg2"},
|
||||||
|
}}, GQLQuery{
|
||||||
|
Name: "query", Args: []string{"arg1", "arg3"},
|
||||||
|
})
|
||||||
|
So(ok, ShouldBeFalse)
|
||||||
|
|
||||||
|
err := containsGQLQueryWithParams(
|
||||||
|
[]field{
|
||||||
|
{Name: "query"},
|
||||||
|
},
|
||||||
|
[]typeInfo{},
|
||||||
|
GQLQuery{Name: "other-name"},
|
||||||
|
)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GetConfigOptions", t, func() {
|
||||||
|
// no flags
|
||||||
|
cmd := &cobra.Command{}
|
||||||
|
isSpinner, verifyTLS := GetCliConfigOptions(cmd)
|
||||||
|
So(isSpinner, ShouldBeFalse)
|
||||||
|
So(verifyTLS, ShouldBeFalse)
|
||||||
|
|
||||||
|
// bad showspinner
|
||||||
|
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":"bad", "verify-tls": false}]}`)
|
||||||
|
cmd = &cobra.Command{}
|
||||||
|
cmd.Flags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
isSpinner, verifyTLS = GetCliConfigOptions(cmd)
|
||||||
|
So(isSpinner, ShouldBeFalse)
|
||||||
|
So(verifyTLS, ShouldBeFalse)
|
||||||
|
os.Remove(configPath)
|
||||||
|
|
||||||
|
// bad verify-tls
|
||||||
|
configPath = makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false, "verify-tls": "bad"}]}`)
|
||||||
|
cmd = &cobra.Command{}
|
||||||
|
cmd.Flags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
isSpinner, verifyTLS = GetCliConfigOptions(cmd)
|
||||||
|
So(isSpinner, ShouldBeFalse)
|
||||||
|
So(verifyTLS, ShouldBeFalse)
|
||||||
|
os.Remove(configPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GetServerURLFromFlags", t, func() {
|
||||||
|
cmd := &cobra.Command{}
|
||||||
|
cmd.Flags().String(cmdflags.URLFlag, "url", "")
|
||||||
|
url, err := GetServerURLFromFlags(cmd)
|
||||||
|
So(url, ShouldResemble, "url")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// err no config or url
|
||||||
|
cmd = &cobra.Command{}
|
||||||
|
url, err = GetServerURLFromFlags(cmd)
|
||||||
|
So(url, ShouldResemble, "")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// err ulr from config is empty
|
||||||
|
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest"}]}`)
|
||||||
|
cmd = &cobra.Command{}
|
||||||
|
cmd.Flags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
url, err = GetServerURLFromFlags(cmd)
|
||||||
|
So(url, ShouldResemble, "")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
os.Remove(configPath)
|
||||||
|
|
||||||
|
// err reading the server url from config
|
||||||
|
configPath = makeConfigFile("{}")
|
||||||
|
cmd = &cobra.Command{}
|
||||||
|
cmd.Flags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||||
|
url, err = GetServerURLFromFlags(cmd)
|
||||||
|
So(url, ShouldResemble, "")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
os.Remove(configPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("CheckExtEndPointQuery", t, func() {
|
||||||
|
// invalid url
|
||||||
|
err := CheckExtEndPointQuery(searchConfig{
|
||||||
|
user: ref(""),
|
||||||
|
servURL: ref("bad-url"),
|
||||||
|
})
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// good url but no connection
|
||||||
|
err = CheckExtEndPointQuery(searchConfig{
|
||||||
|
user: ref(""),
|
||||||
|
servURL: ref("http://127.0.0.1:5000"),
|
||||||
|
verifyTLS: ref(false),
|
||||||
|
debug: ref(false),
|
||||||
|
resultWriter: io.Discard,
|
||||||
|
})
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
109
pkg/cli/search_sub_cmd.go
Normal file
109
pkg/cli/search_sub_cmd.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
//go:build search
|
||||||
|
// +build search
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSearchSubjectCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageCmd := &cobra.Command{
|
||||||
|
Use: "subject [repo:tag]|[repo@digest]",
|
||||||
|
Short: "List all referrers for this subject.",
|
||||||
|
Long: `List all referrers for this subject. The subject can be specified by tag(repo:tag) or by digest" +
|
||||||
|
"(repo@digest)`,
|
||||||
|
Example: `# For referrers search specify the referred subject using it's full digest or tag:
|
||||||
|
zli search subject "repo@sha256:f9a0981..."
|
||||||
|
zli search subject "repo:tag"`,
|
||||||
|
Args: OneImageWithRefArg,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, ReferrersQuery()); err == nil {
|
||||||
|
return SearchReferrersGQL(searchConfig, args[0])
|
||||||
|
} else {
|
||||||
|
return SearchReferrers(searchConfig, args[0])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSearchQueryCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageCmd := &cobra.Command{
|
||||||
|
Use: "query",
|
||||||
|
Short: "Fuzzy search for repos and their tags.",
|
||||||
|
Long: "Fuzzy search for repos and their tags.",
|
||||||
|
Example: `# For repo search specify a substring of the repo name without the tag
|
||||||
|
zli search query "test/repo"
|
||||||
|
|
||||||
|
# For image search specify the full repo name followed by the tag or a prefix of the tag.
|
||||||
|
zli search query "test/repo:2.1."
|
||||||
|
|
||||||
|
# To search all tags in all repos.
|
||||||
|
zli search query ":"`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// special format for searching all images and tags
|
||||||
|
if args[0] == ":" {
|
||||||
|
err := CheckExtEndPointQuery(searchConfig, GlobalSearchQuery())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: '%s'", err, ImageListQuery().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchAllImagesGQL(searchConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CheckExtEndPointQuery(searchConfig, GlobalSearchQuery()); err != nil {
|
||||||
|
return fmt.Errorf("%w: '%s'", err, CVEListForImageQuery().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GlobalSearchGQL(searchConfig, args[0])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func OneImageWithRefArg(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
image := args[0]
|
||||||
|
|
||||||
|
if dir, ref, _ := zcommon.GetImageDirAndReference(image); dir == "" || ref == "" {
|
||||||
|
return zerr.ErrInvalidRepoRefFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OneDigestArg(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := args[0]
|
||||||
|
if _, err := godigest.Parse(digest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api/constants"
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
zcommon "zotregistry.io/zot/pkg/common"
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
)
|
)
|
||||||
|
@ -326,7 +326,7 @@ func (search imagesByDigestSearcherGQL) search(config searchConfig) (bool, error
|
||||||
|
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
imageList, err := config.searchService.getImagesByDigestGQL(ctx, config, username, password, *config.params["digest"])
|
imageList, err := config.searchService.getImagesForDigestGQL(ctx, config, username, password, *config.params["digest"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
@ -770,7 +770,7 @@ func (search globalSearcherREST) search(config searchConfig) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, fmt.Errorf("search extension is not enabled: %w", zotErrors.ErrExtensionNotEnabled)
|
return true, fmt.Errorf("search extension is not enabled: %w", zerr.ErrExtensionNotEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan stringResult,
|
func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan stringResult,
|
||||||
|
@ -813,7 +813,7 @@ func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan strin
|
||||||
config.spinner.stopSpinner()
|
config.spinner.stopSpinner()
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
errCh <- zotErrors.ErrCLITimeout
|
errCh <- zerr.ErrCLITimeout
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api/constants"
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
"zotregistry.io/zot/pkg/common"
|
"zotregistry.io/zot/pkg/common"
|
||||||
)
|
)
|
||||||
|
@ -34,13 +34,13 @@ const (
|
||||||
type SearchService interface { //nolint:interfacebloat
|
type SearchService interface { //nolint:interfacebloat
|
||||||
getImagesGQL(ctx context.Context, config searchConfig, username, password string,
|
getImagesGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
imageName string) (*common.ImageListResponse, error)
|
imageName string) (*common.ImageListResponse, error)
|
||||||
getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
getImagesForDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
digest string) (*common.ImagesForDigest, error)
|
digest string) (*common.ImagesForDigest, error)
|
||||||
getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
||||||
imageName string, searchedCVE string) (*cveResult, error)
|
imageName string, searchedCVE string) (*cveResult, error)
|
||||||
getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
|
getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
digest string) (*common.ImagesForCve, error)
|
digest string) (*common.ImagesForCve, error)
|
||||||
getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, repo,
|
||||||
cveID string) (*common.ImagesForCve, error)
|
cveID string) (*common.ImagesForCve, error)
|
||||||
getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
||||||
cveID string) (*common.ImageListWithCVEFixedResponse, error)
|
cveID string) (*common.ImageListWithCVEFixedResponse, error)
|
||||||
|
@ -260,7 +260,7 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service searchService) getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
func (service searchService) getImagesForDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
digest string,
|
digest string,
|
||||||
) (*common.ImagesForDigest, error) {
|
) (*common.ImagesForDigest, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
|
@ -354,7 +354,7 @@ func (service searchService) getCveByImageGQL(ctx context.Context, config search
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service searchService) getTagsForCVEGQL(ctx context.Context, config searchConfig,
|
func (service searchService) getTagsForCVEGQL(ctx context.Context, config searchConfig,
|
||||||
username, password, imageName, cveID string,
|
username, password, repo, cveID string,
|
||||||
) (*common.ImagesForCve, error) {
|
) (*common.ImagesForCve, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
|
@ -387,7 +387,19 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
|
||||||
return nil, errResult
|
return nil, errResult
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
if repo == "" {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredResults := &common.ImagesForCve{}
|
||||||
|
|
||||||
|
for _, image := range result.Results {
|
||||||
|
if image.RepoName == repo {
|
||||||
|
filteredResults.Results = append(filteredResults.Results, image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredResults, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig,
|
func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig,
|
||||||
|
@ -537,7 +549,9 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
||||||
) {
|
) {
|
||||||
defer wtgrp.Done()
|
defer wtgrp.Done()
|
||||||
|
|
||||||
tagListEndpoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("/v2/%s/tags/list", imageName))
|
repo, imageTag := common.GetImageDirAndTag(imageName)
|
||||||
|
|
||||||
|
tagListEndpoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("/v2/%s/tags/list", repo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isContextDone(ctx) {
|
if isContextDone(ctx) {
|
||||||
return
|
return
|
||||||
|
@ -570,9 +584,17 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldMatchTag := imageTag != ""
|
||||||
|
matchesTag := tag == imageTag
|
||||||
|
|
||||||
|
// when the tag is empty we match everything
|
||||||
|
if shouldMatchTag && !matchesTag {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
wtgrp.Add(1)
|
wtgrp.Add(1)
|
||||||
|
|
||||||
go addManifestCallToPool(ctx, config, pool, username, password, imageName, tag, rch, wtgrp)
|
go addManifestCallToPool(ctx, config, pool, username, password, repo, tag, rch, wtgrp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,7 +809,7 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config
|
||||||
go rlim.startRateLimiter(ctx)
|
go rlim.startRateLimiter(ctx)
|
||||||
|
|
||||||
for _, image := range result.Results {
|
for _, image := range result.Results {
|
||||||
if !strings.EqualFold(imageName, image.RepoName) {
|
if imageName != "" && !strings.EqualFold(imageName, image.RepoName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1438,7 +1460,7 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man
|
||||||
platform += offset
|
platform += offset
|
||||||
}
|
}
|
||||||
|
|
||||||
minifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "")
|
manifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "")
|
||||||
configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "")
|
configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "")
|
||||||
imgSize, _ := strconv.ParseUint(manifest.Size, 10, 64)
|
imgSize, _ := strconv.ParseUint(manifest.Size, 10, 64)
|
||||||
size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis)
|
size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis)
|
||||||
|
@ -1447,7 +1469,7 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man
|
||||||
|
|
||||||
row[colImageNameIndex] = imageName
|
row[colImageNameIndex] = imageName
|
||||||
row[colTagIndex] = tagName
|
row[colTagIndex] = tagName
|
||||||
row[colDigestIndex] = minifestDigestStr
|
row[colDigestIndex] = manifestDigestStr
|
||||||
row[colPlatformIndex] = platform
|
row[colPlatformIndex] = platform
|
||||||
row[colSizeIndex] = size
|
row[colSizeIndex] = size
|
||||||
row[colIsSignedIndex] = strconv.FormatBool(isSigned)
|
row[colIsSignedIndex] = strconv.FormatBool(isSigned)
|
||||||
|
@ -1487,23 +1509,23 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPlatformStr(platf common.Platform) string {
|
func getPlatformStr(platform common.Platform) string {
|
||||||
if platf.Arch == "" && platf.Os == "" {
|
if platform.Arch == "" && platform.Os == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
platform := platf.Os
|
fullPlatform := platform.Os
|
||||||
|
|
||||||
if platf.Arch != "" {
|
if platform.Arch != "" {
|
||||||
platform = platform + "/" + platf.Arch
|
fullPlatform = fullPlatform + "/" + platform.Arch
|
||||||
platform = strings.Trim(platform, "/")
|
fullPlatform = strings.Trim(fullPlatform, "/")
|
||||||
|
|
||||||
if platf.Variant != "" {
|
if platform.Variant != "" {
|
||||||
platform = platform + "/" + platf.Variant
|
fullPlatform = fullPlatform + "/" + platform.Variant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return platform
|
return fullPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
func (img imageStruct) stringJSON() (string, error) {
|
func (img imageStruct) stringJSON() (string, error) {
|
||||||
|
@ -1534,12 +1556,12 @@ type catalogResponse struct {
|
||||||
|
|
||||||
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
|
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
|
||||||
if !isURL(serverURL) {
|
if !isURL(serverURL) {
|
||||||
return "", zotErrors.ErrInvalidURL
|
return "", zerr.ErrInvalidURL
|
||||||
}
|
}
|
||||||
|
|
||||||
newURL, err := url.Parse(serverURL)
|
newURL, err := url.Parse(serverURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", zotErrors.ErrInvalidURL
|
return "", zerr.ErrInvalidURL
|
||||||
}
|
}
|
||||||
|
|
||||||
newURL, _ = newURL.Parse(endPoint)
|
newURL, _ = newURL.Parse(endPoint)
|
||||||
|
|
|
@ -111,7 +111,7 @@ func GetRepoReference(repo string) (string, string, bool, error) {
|
||||||
return repoName, digest, false, nil
|
return repoName, digest, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFullImageName returns the formated string for the given repo/tag or repo/digest.
|
// GetFullImageName returns the formatted string for the given repo/tag or repo/digest.
|
||||||
func GetFullImageName(repo, ref string) string {
|
func GetFullImageName(repo, ref string) string {
|
||||||
if IsTag(ref) {
|
if IsTag(ref) {
|
||||||
return repo + ":" + ref
|
return repo + ":" + ref
|
||||||
|
@ -129,3 +129,7 @@ func IsDigest(ref string) bool {
|
||||||
func IsTag(ref string) bool {
|
func IsTag(ref string) bool {
|
||||||
return !IsDigest(ref)
|
return !IsDigest(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckIsCorrectRepoNameFormat(repo string) bool {
|
||||||
|
return !strings.ContainsAny(repo, ":@")
|
||||||
|
}
|
||||||
|
|
24
pkg/common/retry.go
Normal file
24
pkg/common/retry.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RetryWithContext(ctx context.Context, operation func(attempt int, retryIn time.Duration) error, maxRetries int,
|
||||||
|
delay time.Duration,
|
||||||
|
) error {
|
||||||
|
err := operation(1, delay)
|
||||||
|
|
||||||
|
for attempt := 1; err != nil && attempt < maxRetries; attempt++ {
|
||||||
|
select {
|
||||||
|
case <-time.After(delay):
|
||||||
|
case <-ctx.Done():
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = operation(attempt+1, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/exporter/api"
|
"zotregistry.io/zot/pkg/exporter/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,12 +66,12 @@ func loadConfiguration(config *api.Config, configPath string) {
|
||||||
|
|
||||||
metaData := &mapstructure.Metadata{}
|
metaData := &mapstructure.Metadata{}
|
||||||
if err := viper.Unmarshal(&config, metadataConfig(metaData)); err != nil {
|
if err := viper.Unmarshal(&config, metadataConfig(metaData)); err != nil {
|
||||||
log.Error().Err(err).Msg("Error while unmarshalling new config")
|
log.Error().Err(err).Msg("Error while unmarshaling new config")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(metaData.Keys) == 0 || len(metaData.Unused) > 0 {
|
if len(metaData.Keys) == 0 || len(metaData.Unused) > 0 {
|
||||||
log.Error().Err(errors.ErrBadConfig).Msg("Bad configuration, retry writing it")
|
log.Error().Err(zerr.ErrBadConfig).Msg("Bad configuration, retry writing it")
|
||||||
panic(errors.ErrBadConfig)
|
panic(zerr.ErrBadConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func GetExtensions(config *config.Config) distext.ExtensionList {
|
||||||
|
|
||||||
if len(endpoints) > 0 {
|
if len(endpoints) > 0 {
|
||||||
extensions = append(extensions, distext.Extension{
|
extensions = append(extensions, distext.Extension{
|
||||||
Name: "_zot",
|
Name: constants.BaseExtension,
|
||||||
URL: "https://github.com/project-zot/zot/blob/" + config.ReleaseTag + "/pkg/extensions/_zot.md",
|
URL: "https://github.com/project-zot/zot/blob/" + config.ReleaseTag + "/pkg/extensions/_zot.md",
|
||||||
Description: "zot registry extensions",
|
Description: "zot registry extensions",
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
|
|
|
@ -30,7 +30,7 @@ type ImageAnnotations struct {
|
||||||
/*
|
/*
|
||||||
OCI annotation/label with backwards compatibility
|
OCI annotation/label with backwards compatibility
|
||||||
|
|
||||||
arg can be either lables or annotations
|
arg can be either labels or annotations
|
||||||
https://github.com/opencontainers/image-spec/blob/main/annotations.md.
|
https://github.com/opencontainers/image-spec/blob/main/annotations.md.
|
||||||
*/
|
*/
|
||||||
func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey string) string {
|
func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey string) string {
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestConvertErrors(t *testing.T) {
|
||||||
err = metaDB.SetRepoReference("repo1", "0.1.0", digest11, ispec.MediaTypeImageManifest)
|
err = metaDB.SetRepoReference("repo1", "0.1.0", digest11, ispec.MediaTypeImageManifest)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
repoMetas, manifestMetaMap, _, err := metaDB.SearchRepos(context.Background(), "")
|
reposMeta, manifestMetaMap, _, err := metaDB.SearchRepos(context.Background(), "")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
ctx := graphql.WithResponseContext(context.Background(),
|
ctx := graphql.WithResponseContext(context.Background(),
|
||||||
|
@ -69,7 +69,7 @@ func TestConvertErrors(t *testing.T) {
|
||||||
|
|
||||||
_ = convert.RepoMeta2RepoSummary(
|
_ = convert.RepoMeta2RepoSummary(
|
||||||
ctx,
|
ctx,
|
||||||
repoMetas[0],
|
reposMeta[0],
|
||||||
manifestMetaMap,
|
manifestMetaMap,
|
||||||
map[string]mTypes.IndexData{},
|
map[string]mTypes.IndexData{},
|
||||||
convert.SkipQGLField{},
|
convert.SkipQGLField{},
|
||||||
|
@ -286,7 +286,7 @@ func TestConvertErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateLastUpdatedTimestam(t *testing.T) {
|
func TestUpdateLastUpdatedTimestamp(t *testing.T) {
|
||||||
Convey("Image summary is the first image checked for the repo", t, func() {
|
Convey("Image summary is the first image checked for the repo", t, func() {
|
||||||
before := time.Time{}
|
before := time.Time{}
|
||||||
after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC)
|
after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC)
|
||||||
|
|
|
@ -44,7 +44,7 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta mTypes.RepoMetadata,
|
||||||
repoIsUserStarred = repoMeta.IsStarred // value specific to the current user
|
repoIsUserStarred = repoMeta.IsStarred // value specific to the current user
|
||||||
repoIsUserBookMarked = repoMeta.IsBookmarked // value specific to the current user
|
repoIsUserBookMarked = repoMeta.IsBookmarked // value specific to the current user
|
||||||
|
|
||||||
// map used to keep track of all blobs of a repo without dublicates as
|
// map used to keep track of all blobs of a repo without duplicates as
|
||||||
// some images may have the same layers
|
// some images may have the same layers
|
||||||
repoBlob2Size = make(map[string]int64, 10)
|
repoBlob2Size = make(map[string]int64, 10)
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta mTypes.RepoMetadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PaginatedRepoMeta2RepoSummaries(ctx context.Context, repoMetas []mTypes.RepoMetadata,
|
func PaginatedRepoMeta2RepoSummaries(ctx context.Context, reposMeta []mTypes.RepoMetadata,
|
||||||
manifestMetaMap map[string]mTypes.ManifestMetadata, indexDataMap map[string]mTypes.IndexData,
|
manifestMetaMap map[string]mTypes.ManifestMetadata, indexDataMap map[string]mTypes.IndexData,
|
||||||
skip SkipQGLField, cveInfo cveinfo.CveInfo, filter mTypes.Filter, pageInput pagination.PageInput,
|
skip SkipQGLField, cveInfo cveinfo.CveInfo, filter mTypes.Filter, pageInput pagination.PageInput,
|
||||||
) ([]*gql_generated.RepoSummary, zcommon.PageInfo, error) {
|
) ([]*gql_generated.RepoSummary, zcommon.PageInfo, error) {
|
||||||
|
@ -149,7 +149,7 @@ func PaginatedRepoMeta2RepoSummaries(ctx context.Context, repoMetas []mTypes.Rep
|
||||||
return []*gql_generated.RepoSummary{}, zcommon.PageInfo{}, err
|
return []*gql_generated.RepoSummary{}, zcommon.PageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repoMeta := range repoMetas {
|
for _, repoMeta := range reposMeta {
|
||||||
repoSummary := RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
|
repoSummary := RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
|
||||||
|
|
||||||
if RepoSumAcceptedByFilter(repoSummary, filter) {
|
if RepoSumAcceptedByFilter(repoSummary, filter) {
|
||||||
|
@ -679,7 +679,7 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta mTypes.RepoMetadata
|
||||||
isStarred = repoMeta.IsStarred // value specific to the current user
|
isStarred = repoMeta.IsStarred // value specific to the current user
|
||||||
isBookmarked = repoMeta.IsBookmarked // value specific to the current user
|
isBookmarked = repoMeta.IsBookmarked // value specific to the current user
|
||||||
|
|
||||||
// map used to keep track of all blobs of a repo without dublicates as
|
// map used to keep track of all blobs of a repo without duplicates as
|
||||||
// some images may have the same layers
|
// some images may have the same layers
|
||||||
repoBlob2Size = make(map[string]int64, 10)
|
repoBlob2Size = make(map[string]int64, 10)
|
||||||
|
|
||||||
|
@ -694,7 +694,7 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta mTypes.RepoMetadata
|
||||||
skip.Vulnerabilities, repoMeta, manifestMetaMap, indexDataMap, cveInfo)
|
skip.Vulnerabilities, repoMeta, manifestMetaMap, indexDataMap, cveInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Str("repository", repoName).Str("reference", tag).
|
log.Error().Str("repository", repoName).Str("reference", tag).
|
||||||
Msg("metadb: erorr while converting descriptor for image")
|
Msg("metadb: error while converting descriptor for image")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -599,9 +599,9 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(cveResult.ImgList.CVEResultForImage.CVEList), ShouldNotBeZeroValue)
|
So(len(cveResult.ImgList.CVEResultForImage.CVEList), ShouldNotBeZeroValue)
|
||||||
|
|
||||||
cvid := cveResult.ImgList.CVEResultForImage.CVEList[0].ID
|
cveid := cveResult.ImgList.CVEResultForImage.CVEList[0].ID
|
||||||
|
|
||||||
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -610,7 +610,7 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
|
So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
|
||||||
|
|
||||||
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-cve-test\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-cve-test\"){Results{RepoName%20LastUpdated}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -618,7 +618,7 @@ func TestCVESearch(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
|
So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
|
||||||
|
|
||||||
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -635,7 +635,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.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noindex\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-noindex\"){Results{RepoName%20LastUpdated}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -643,7 +643,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.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-index\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-invalid-index\"){Results{RepoName%20LastUpdated}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -651,11 +651,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.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-noblob\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-noblob\"){Results{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.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-test\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-test\"){Results{RepoName%20LastUpdated}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -663,7 +663,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.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cvid + "\",image:\"zot-squashfs-invalid-blob\"){Results{RepoName%20LastUpdated}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-invalid-blob\"){Results{RepoName%20LastUpdated}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
@ -732,7 +732,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.FullSearchPrefix + "?query={ImageListForCVE(id:\"" + cvid + "\"){Results{RepoName%20Tag}}}")
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"" + cveid + "\"){Results{RepoName%20Tag}}}")
|
||||||
So(resp, ShouldNotBeNil)
|
So(resp, ShouldNotBeNil)
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
})
|
})
|
||||||
|
@ -1412,7 +1412,7 @@ func TestCVEStruct(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 0)
|
So(len(tagList), ShouldEqual, 0)
|
||||||
|
|
||||||
// Repo is not found, assume it is affetected by the CVE
|
// Repo is not found, assume it is affected by the CVE
|
||||||
// But we don't have enough of it's data to actually return it
|
// But we don't have enough of it's data to actually return it
|
||||||
tagList, err = cveInfo.GetImageListForCVE("repo100", "CVE100")
|
tagList, err = cveInfo.GetImageListForCVE("repo100", "CVE100")
|
||||||
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
|
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
|
||||||
|
|
|
@ -123,7 +123,7 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob1,
|
ConfigBlob: configBlob1,
|
||||||
|
@ -134,7 +134,7 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,14 +189,14 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
configBlob, err := json.Marshal(ispec.Image{})
|
configBlob, err := json.Marshal(ispec.Image{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: []byte("bad manifest blob"),
|
ManifestBlob: []byte("bad manifest blob"),
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,14 +262,14 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: []byte("bad config blob"),
|
ConfigBlob: []byte("bad config blob"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,7 +375,7 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob1,
|
ConfigBlob: configBlob1,
|
||||||
|
@ -386,7 +386,7 @@ func TestGlobalSearch(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,7 +505,7 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: []byte("bad manifest blob"),
|
ManifestBlob: []byte("bad manifest blob"),
|
||||||
ConfigBlob: configBlob1,
|
ConfigBlob: configBlob1,
|
||||||
|
@ -516,7 +516,7 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,7 +601,7 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||||
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob1,
|
ConfigBlob: configBlob1,
|
||||||
|
@ -612,7 +612,7 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Convey("MetaDB missing requestedPage", func() {
|
Convey("MetaDB missing requestedPage", func() {
|
||||||
|
@ -776,7 +776,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
manifestBlob := []byte("invalid")
|
manifestBlob := []byte("invalid")
|
||||||
|
|
||||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -784,7 +784,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -820,7 +820,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
configBlob, err := json.Marshal(ispec.ImageConfig{})
|
configBlob, err := json.Marshal(ispec.ImageConfig{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||||
manifestDigest: {
|
manifestDigest: {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -829,9 +829,9 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
}
|
}
|
||||||
matchedTags := repos[0].Tags
|
matchedTags := repos[0].Tags
|
||||||
for tag, manifestDescriptor := range repos[0].Tags {
|
for tag, manifestDescriptor := range repos[0].Tags {
|
||||||
if !filterFunc(repos[0], manifestMetaDatas[manifestDescriptor.Digest]) {
|
if !filterFunc(repos[0], manifestsMetaData[manifestDescriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetaDatas, manifestDescriptor.Digest)
|
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -839,7 +839,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
|
|
||||||
repos[0].Tags = matchedTags
|
repos[0].Tags = matchedTags
|
||||||
|
|
||||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -898,7 +898,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||||
manifestDigest: {
|
manifestDigest: {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -908,9 +908,9 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
|
|
||||||
matchedTags := repos[0].Tags
|
matchedTags := repos[0].Tags
|
||||||
for tag, manifestDescriptor := range repos[0].Tags {
|
for tag, manifestDescriptor := range repos[0].Tags {
|
||||||
if !filterFunc(repos[0], manifestMetaDatas[manifestDescriptor.Digest]) {
|
if !filterFunc(repos[0], manifestsMetaData[manifestDescriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetaDatas, manifestDescriptor.Digest)
|
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -918,7 +918,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
|
|
||||||
repos[0].Tags = matchedTags
|
repos[0].Tags = matchedTags
|
||||||
|
|
||||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -975,7 +975,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||||
manifestDigest: {
|
manifestDigest: {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -985,9 +985,9 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
|
|
||||||
matchedTags := repos[0].Tags
|
matchedTags := repos[0].Tags
|
||||||
for tag, manifestDescriptor := range repos[0].Tags {
|
for tag, manifestDescriptor := range repos[0].Tags {
|
||||||
if !filterFunc(repos[0], manifestMetaDatas[manifestDescriptor.Digest]) {
|
if !filterFunc(repos[0], manifestsMetaData[manifestDescriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetaDatas, manifestDescriptor.Digest)
|
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -995,7 +995,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
|
|
||||||
repos[0].Tags = matchedTags
|
repos[0].Tags = matchedTags
|
||||||
|
|
||||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1042,7 +1042,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||||
manifestDigest: {
|
manifestDigest: {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -1054,9 +1054,9 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
matchedTags := repo.Tags
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
for tag, manifestDescriptor := range repo.Tags {
|
for tag, manifestDescriptor := range repo.Tags {
|
||||||
if !filterFunc(repo, manifestMetaDatas[manifestDescriptor.Digest]) {
|
if !filterFunc(repo, manifestsMetaData[manifestDescriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetaDatas, manifestDescriptor.Digest)
|
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1065,7 +1065,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
repos[i].Tags = matchedTags
|
repos[i].Tags = matchedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,7 +1111,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||||
manifestDigest: {
|
manifestDigest: {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -1123,9 +1123,9 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
matchedTags := repo.Tags
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
for tag, manifestDescriptor := range repo.Tags {
|
for tag, manifestDescriptor := range repo.Tags {
|
||||||
if !filterFunc(repo, manifestMetaDatas[manifestDescriptor.Digest]) {
|
if !filterFunc(repo, manifestsMetaData[manifestDescriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetaDatas, manifestDescriptor.Digest)
|
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1136,7 +1136,7 @@ func TestImageListForDigest(t *testing.T) {
|
||||||
repos = append(repos, repo)
|
repos = append(repos, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1429,7 +1429,7 @@ func TestImageList(t *testing.T) {
|
||||||
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -1442,21 +1442,21 @@ func TestImageList(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filterFunc(repos[0], manifestMetaDatas["digestTag1.0.1"]) {
|
if !filterFunc(repos[0], manifestsMetaData["digestTag1.0.1"]) {
|
||||||
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{},
|
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{},
|
||||||
map[string]mTypes.IndexData{}, nil
|
map[string]mTypes.IndexData{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
limit := 1
|
limit := 1
|
||||||
ofset := 0
|
offset := 0
|
||||||
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
||||||
pageInput := gql_generated.PageInput{
|
pageInput := gql_generated.PageInput{
|
||||||
Limit: &limit,
|
Limit: &limit,
|
||||||
Offset: &ofset,
|
Offset: &offset,
|
||||||
SortBy: &sortCriteria,
|
SortBy: &sortCriteria,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1889,7 +1889,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create metadb data for scannable image with vulnerabilities
|
// Create metadb data for scannable image with vulnerabilities
|
||||||
// Create manifets metadata first
|
// Create manifest metadata first
|
||||||
timeStamp1 := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC)
|
timeStamp1 := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
configBlob1, err := json.Marshal(ispec.Image{
|
configBlob1, err := json.Marshal(ispec.Image{
|
||||||
|
@ -2076,7 +2076,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
||||||
},
|
},
|
||||||
"CVE2": {
|
"CVE2": {
|
||||||
ID: "CVE2",
|
ID: "CVE2",
|
||||||
Severity: "MEDIM",
|
Severity: "MEDIUM",
|
||||||
Title: "Title CVE2",
|
Title: "Title CVE2",
|
||||||
Description: "Description CVE2",
|
Description: "Description CVE2",
|
||||||
},
|
},
|
||||||
|
@ -2961,7 +2961,7 @@ func TestDerivedImageList(t *testing.T) {
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -3019,9 +3019,9 @@ func TestDerivedImageList(t *testing.T) {
|
||||||
matchedTags := repo.Tags
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
for tag, descriptor := range repo.Tags {
|
for tag, descriptor := range repo.Tags {
|
||||||
if !filterFunc(repo, manifestMetas[descriptor.Digest]) {
|
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetas, descriptor.Digest)
|
delete(manifestsMeta, descriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -3030,7 +3030,7 @@ func TestDerivedImageList(t *testing.T) {
|
||||||
repos[i].Tags = matchedTags
|
repos[i].Tags = matchedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3249,7 +3249,7 @@ func TestBaseImageList(t *testing.T) {
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -3301,9 +3301,9 @@ func TestBaseImageList(t *testing.T) {
|
||||||
matchedTags := repo.Tags
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
for tag, descriptor := range repo.Tags {
|
for tag, descriptor := range repo.Tags {
|
||||||
if !filterFunc(repo, manifestMetas[descriptor.Digest]) {
|
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetas, descriptor.Digest)
|
delete(manifestsMeta, descriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -3312,7 +3312,7 @@ func TestBaseImageList(t *testing.T) {
|
||||||
repos[i].Tags = matchedTags
|
repos[i].Tags = matchedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
@ -3416,7 +3416,7 @@ func TestBaseImageList(t *testing.T) {
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||||
"digestTag1.0.1": {
|
"digestTag1.0.1": {
|
||||||
ManifestBlob: manifestBlob,
|
ManifestBlob: manifestBlob,
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
|
@ -3467,9 +3467,9 @@ func TestBaseImageList(t *testing.T) {
|
||||||
matchedTags := repo.Tags
|
matchedTags := repo.Tags
|
||||||
|
|
||||||
for tag, descriptor := range repo.Tags {
|
for tag, descriptor := range repo.Tags {
|
||||||
if !filterFunc(repo, manifestMetas[descriptor.Digest]) {
|
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
|
||||||
delete(matchedTags, tag)
|
delete(matchedTags, tag)
|
||||||
delete(manifestMetas, descriptor.Digest)
|
delete(manifestsMeta, descriptor.Digest)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -3478,7 +3478,7 @@ func TestBaseImageList(t *testing.T) {
|
||||||
repos[i].Tags = matchedTags
|
repos[i].Tags = matchedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, manifestMetas, map[string]mTypes.IndexData{}, nil
|
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||||
|
@ -3519,12 +3519,12 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
Digest: "goodIndexBadManifests",
|
Digest: "goodIndexBadManifests",
|
||||||
MediaType: ispec.MediaTypeImageIndex,
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
},
|
},
|
||||||
"tagGoodIndex1GoodManfest": {
|
"tagGoodIndex1GoodManifest": {
|
||||||
Digest: "goodIndexGoodManfest",
|
Digest: "goodIndexGoodManifest",
|
||||||
MediaType: ispec.MediaTypeImageIndex,
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
},
|
},
|
||||||
"tagGoodIndex2GoodManfest": {
|
"tagGoodIndex2GoodManifest": {
|
||||||
Digest: "goodIndexGoodManfest",
|
Digest: "goodIndexGoodManifest",
|
||||||
MediaType: ispec.MediaTypeImageIndex,
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3569,7 +3569,7 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
return mTypes.IndexData{
|
return mTypes.IndexData{
|
||||||
IndexBlob: goodIndexBadManifestsBlob,
|
IndexBlob: goodIndexBadManifestsBlob,
|
||||||
}, nil
|
}, nil
|
||||||
case "goodIndexGoodManfest":
|
case "goodIndexGoodManifest":
|
||||||
return mTypes.IndexData{
|
return mTypes.IndexData{
|
||||||
IndexBlob: goodIndexGoodManifestBlob,
|
IndexBlob: goodIndexGoodManifestBlob,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -6242,8 +6242,8 @@ func TestImageSummary(t *testing.T) {
|
||||||
|
|
||||||
WaitTillServerReady(baseURL)
|
WaitTillServerReady(baseURL)
|
||||||
|
|
||||||
manifestBlob, errMarsal := json.Marshal(manifest)
|
manifestBlob, errMarshal := json.Marshal(manifest)
|
||||||
So(errMarsal, ShouldBeNil)
|
So(errMarshal, ShouldBeNil)
|
||||||
So(manifestBlob, ShouldNotBeNil)
|
So(manifestBlob, ShouldNotBeNil)
|
||||||
manifestDigest := godigest.FromBytes(manifestBlob)
|
manifestDigest := godigest.FromBytes(manifestBlob)
|
||||||
repoName := "test-repo" //nolint:goconst
|
repoName := "test-repo" //nolint:goconst
|
||||||
|
@ -6427,7 +6427,7 @@ func TestImageSummary(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUplodingArtifactsWithDifferentMediaType(t *testing.T) {
|
func TestUploadingArtifactsWithDifferentMediaType(t *testing.T) {
|
||||||
Convey("", t, func() {
|
Convey("", t, func() {
|
||||||
port := GetFreePort()
|
port := GetFreePort()
|
||||||
baseURL := GetBaseURL(port)
|
baseURL := GetBaseURL(port)
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
"zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/extensions/config"
|
"zotregistry.io/zot/pkg/extensions/config"
|
||||||
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
||||||
"zotregistry.io/zot/pkg/extensions/lint"
|
"zotregistry.io/zot/pkg/extensions/lint"
|
||||||
|
@ -92,7 +92,7 @@ func TestSyncInternal(t *testing.T) {
|
||||||
|
|
||||||
repositoryReference = fmt.Sprintf("%s/%s:tagged", host, testImage)
|
repositoryReference = fmt.Sprintf("%s/%s:tagged", host, testImage)
|
||||||
_, err = parseRepositoryReference(repositoryReference)
|
_, err = parseRepositoryReference(repositoryReference)
|
||||||
So(err, ShouldEqual, errors.ErrInvalidRepositoryName)
|
So(err, ShouldEqual, zerr.ErrInvalidRepositoryName)
|
||||||
|
|
||||||
repositoryReference = fmt.Sprintf("http://%s/%s", host, testImage)
|
repositoryReference = fmt.Sprintf("http://%s/%s", host, testImage)
|
||||||
_, err = parseRepositoryReference(repositoryReference)
|
_, err = parseRepositoryReference(repositoryReference)
|
||||||
|
@ -341,7 +341,7 @@ func TestLocalRegistry(t *testing.T) {
|
||||||
registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
|
registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
|
||||||
SetRepoReferenceFn: func(repo, Reference string, manifestDigest godigest.Digest, mediaType string) error {
|
SetRepoReferenceFn: func(repo, Reference string, manifestDigest godigest.Digest, mediaType string) error {
|
||||||
if Reference == "1.0" {
|
if Reference == "1.0" {
|
||||||
return errors.ErrRepoMetaNotFound
|
return zerr.ErrRepoMetaNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -355,7 +355,7 @@ func TestLocalRegistry(t *testing.T) {
|
||||||
Convey("trigger metaDB error on image manifest in CommitImage()", func() {
|
Convey("trigger metaDB error on image manifest in CommitImage()", func() {
|
||||||
registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
|
registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
|
||||||
SetRepoReferenceFn: func(repo, Reference string, manifestDigest godigest.Digest, mediaType string) error {
|
SetRepoReferenceFn: func(repo, Reference string, manifestDigest godigest.Digest, mediaType string) error {
|
||||||
return errors.ErrRepoMetaNotFound
|
return zerr.ErrRepoMetaNotFound
|
||||||
},
|
},
|
||||||
}, log)
|
}, log)
|
||||||
|
|
||||||
|
|
|
@ -1156,7 +1156,7 @@ func (bdw *BoltDB) FilterTags(ctx context.Context, filterFunc mTypes.FilterFunc,
|
||||||
repoMeta.IsStarred = zcommon.Contains(userStars, repoMeta.Name)
|
repoMeta.IsStarred = zcommon.Contains(userStars, repoMeta.Name)
|
||||||
|
|
||||||
matchedTags := make(map[string]mTypes.Descriptor)
|
matchedTags := make(map[string]mTypes.Descriptor)
|
||||||
// take all manifestMetas
|
// take all manifestsMeta
|
||||||
for tag, descriptor := range repoMeta.Tags {
|
for tag, descriptor := range repoMeta.Tags {
|
||||||
switch descriptor.MediaType {
|
switch descriptor.MediaType {
|
||||||
case ispec.MediaTypeImageManifest:
|
case ispec.MediaTypeImageManifest:
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
glob "github.com/bmatcuk/doublestar/v4" //nolint:gci
|
glob "github.com/bmatcuk/doublestar/v4" //nolint:gci
|
||||||
|
|
||||||
"zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Key int
|
type Key int
|
||||||
|
@ -41,7 +41,7 @@ func GetAccessControlContext(ctx context.Context) (*AccessControlContext, error)
|
||||||
if authCtx := ctx.Value(authzCtxKey); authCtx != nil {
|
if authCtx := ctx.Value(authzCtxKey); authCtx != nil {
|
||||||
acCtx, ok := authCtx.(AccessControlContext)
|
acCtx, ok := authCtx.(AccessControlContext)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.ErrBadType
|
return nil, zerr.ErrBadType
|
||||||
}
|
}
|
||||||
|
|
||||||
return &acCtx, nil
|
return &acCtx, nil
|
||||||
|
@ -110,7 +110,7 @@ func GetAuthnMiddlewareContext(ctx context.Context) (*AuthnMiddlewareContext, er
|
||||||
if authnMiddlewareCtx := ctx.Value(authnMiddlewareCtxKey); authnMiddlewareCtx != nil {
|
if authnMiddlewareCtx := ctx.Value(authnMiddlewareCtxKey); authnMiddlewareCtx != nil {
|
||||||
amCtx, ok := authnMiddlewareCtx.(AuthnMiddlewareContext)
|
amCtx, ok := authnMiddlewareCtx.(AuthnMiddlewareContext)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.ErrBadType
|
return nil, zerr.ErrBadType
|
||||||
}
|
}
|
||||||
|
|
||||||
return &amCtx, nil
|
return &amCtx, nil
|
||||||
|
|
|
@ -54,7 +54,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/storage"
|
"zotregistry.io/zot/pkg/storage"
|
||||||
storageCommon "zotregistry.io/zot/pkg/storage/common"
|
storageCommon "zotregistry.io/zot/pkg/storage/common"
|
||||||
"zotregistry.io/zot/pkg/storage/local"
|
"zotregistry.io/zot/pkg/storage/local"
|
||||||
"zotregistry.io/zot/pkg/storage/types"
|
stypes "zotregistry.io/zot/pkg/storage/types"
|
||||||
"zotregistry.io/zot/pkg/test/inject"
|
"zotregistry.io/zot/pkg/test/inject"
|
||||||
"zotregistry.io/zot/pkg/test/mocks"
|
"zotregistry.io/zot/pkg/test/mocks"
|
||||||
)
|
)
|
||||||
|
@ -1437,12 +1437,12 @@ func ListNotarySignatures(reference string, tdir string) ([]godigest.Digest, err
|
||||||
|
|
||||||
sigRepo := notreg.NewRepository(remoteRepo)
|
sigRepo := notreg.NewRepository(remoteRepo)
|
||||||
|
|
||||||
artifectDesc, err := sigRepo.Resolve(ctx, reference)
|
artifactDesc, err := sigRepo.Resolve(ctx, reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return signatures, err
|
return signatures, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sigRepo.ListSignatures(ctx, artifectDesc, func(signatureManifests []ispec.Descriptor) error {
|
err = sigRepo.ListSignatures(ctx, artifactDesc, func(signatureManifests []ispec.Descriptor) error {
|
||||||
for _, sigManifestDesc := range signatureManifests {
|
for _, sigManifestDesc := range signatureManifests {
|
||||||
signatures = append(signatures, sigManifestDesc.Digest)
|
signatures = append(signatures, sigManifestDesc.Digest)
|
||||||
}
|
}
|
||||||
|
@ -2067,11 +2067,11 @@ func GetDefaultLayersBlobs() [][]byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultImageStore(rootDir string, log zLog.Logger) types.ImageStore {
|
func GetDefaultImageStore(rootDir string, log zLog.Logger) stypes.ImageStore {
|
||||||
return local.NewImageStore(rootDir, false, time.Hour, false, false, log,
|
return local.NewImageStore(rootDir, false, time.Hour, false, false, log,
|
||||||
monitoring.NewMetricsServer(false, log),
|
monitoring.NewMetricsServer(false, log),
|
||||||
mocks.MockedLint{
|
mocks.MockedLint{
|
||||||
LintFn: func(repo string, manifestDigest godigest.Digest, imageStore types.ImageStore) (bool, error) {
|
LintFn: func(repo string, manifestDigest godigest.Digest, imageStore stypes.ImageStore) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2084,3 +2084,20 @@ func GetDefaultStoreController(rootDir string, log zLog.Logger) storage.StoreCon
|
||||||
DefaultStore: GetDefaultImageStore(rootDir, log),
|
DefaultStore: GetDefaultImageStore(rootDir, log),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RemoveLocalStorageContents(imageStore stypes.ImageStore) error {
|
||||||
|
repos, err := imageStore.GetRepositories()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range repos {
|
||||||
|
// take just the first path
|
||||||
|
err = os.RemoveAll(filepath.Join(imageStore.RootDir(), filepath.SplitList(repo)[0]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ type ConfigBuilder interface {
|
||||||
RandomConfig() ManifestBuilder
|
RandomConfig() ManifestBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
// VulnerableConfigBuilder abstracts specifying the config of an vulnerage OCI image.
|
// VulnerableConfigBuilder abstracts specifying the config of an vulnerable OCI image.
|
||||||
// Keeping the RootFS field consistent with the vulnerable layers.
|
// Keeping the RootFS field consistent with the vulnerable layers.
|
||||||
type VulnerableConfigBuilder interface {
|
type VulnerableConfigBuilder interface {
|
||||||
// VulnerableConfig sets the given config while keeping the correct RootFS values for the
|
// VulnerableConfig sets the given config while keeping the correct RootFS values for the
|
||||||
|
|
Loading…
Reference in a new issue