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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
@ -19,6 +18,7 @@ import (
|
|||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gopkg.in/resty.v1"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
@ -32,7 +32,8 @@ func makeHTTPGetRequest(url string, resultPtr interface{}, client *resty.Client)
|
|||
if resp.StatusCode() != http.StatusOK {
|
||||
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)
|
||||
|
@ -52,7 +53,8 @@ func makeHTTPDeleteRequest(url string, client *resty.Client) error {
|
|||
if resp.StatusCode() != http.StatusAccepted {
|
||||
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
|
||||
|
@ -336,7 +338,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
|||
// request specific check
|
||||
statusCode = resp.StatusCode()
|
||||
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)
|
||||
|
@ -374,7 +377,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
|||
// request specific check
|
||||
statusCode = resp.StatusCode()
|
||||
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
|
||||
|
@ -388,7 +392,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
|||
// request specific check
|
||||
statusCode = resp.StatusCode()
|
||||
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)
|
||||
|
@ -408,7 +413,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
|||
// request specific check
|
||||
statusCode = resp.StatusCode()
|
||||
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
|
||||
|
@ -451,7 +457,8 @@ func pushMonolithImage(workdir, url, trepo string, repos []string, config testCo
|
|||
// request specific check
|
||||
statusCode = resp.StatusCode()
|
||||
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
|
||||
|
|
|
@ -74,7 +74,7 @@ var (
|
|||
ErrInvalidArgs = errors.New("cli: Invalid Arguments")
|
||||
ErrInvalidFlagsCombination = errors.New("cli: Invalid combination of flags")
|
||||
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")
|
||||
ErrCannotResetConfigKey = errors.New("cli: cannot reset given config key")
|
||||
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")
|
||||
ErrEmptyTag = errors.New("metadb: tag 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")
|
||||
ErrOffsetIsNegative = errors.New("pageturner: offset has negative value")
|
||||
ErrSortCriteriaNotSupported = errors.New("pageturner: the sort criteria is not supported")
|
||||
|
@ -151,4 +151,8 @@ var (
|
|||
ErrInvalidPublicKeyContent = errors.New("signatures: invalid public key content")
|
||||
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")
|
||||
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"
|
||||
ExtOciDiscoverPrefix = "/_oci/ext/discover"
|
||||
|
||||
BaseExtension = "_zot"
|
||||
|
||||
// zot specific extensions.
|
||||
BasePrefix = "/_zot"
|
||||
ExtPrefix = BasePrefix + "/ext"
|
||||
|
|
|
@ -8491,7 +8491,7 @@ func TestDistSpecExtensions(t *testing.T) {
|
|||
t.Log(extensionList.Extensions)
|
||||
So(len(extensionList.Extensions), ShouldEqual, 1)
|
||||
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].Description, ShouldNotBeEmpty)
|
||||
// Verify the endpoints below are enabled by search
|
||||
|
@ -8539,7 +8539,7 @@ func TestDistSpecExtensions(t *testing.T) {
|
|||
t.Log(extensionList.Extensions)
|
||||
So(len(extensionList.Extensions), ShouldEqual, 1)
|
||||
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].Description, ShouldNotBeEmpty)
|
||||
// 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")
|
||||
|
||||
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
|
||||
// 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).
|
||||
|
|
|
@ -8,7 +8,9 @@ import "github.com/spf13/cobra"
|
|||
func enableCli(rootCmd *cobra.Command) {
|
||||
rootCmd.AddCommand(NewConfigCommand())
|
||||
rootCmd.AddCommand(NewImageCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewImagesCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewCveCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewCVESCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewRepoCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewSearchCommand(NewSearchService()))
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -21,7 +20,7 @@ import (
|
|||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sigstore/cosign/v2/pkg/oci/remote"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
||||
|
@ -124,19 +123,20 @@ func doHTTPRequest(req *http.Request, verifyTLS bool, debug bool,
|
|||
|
||||
if debug {
|
||||
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()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
return nil, zotErrors.ErrUnauthorizedAccess
|
||||
return nil, zerr.ErrUnauthorizedAccess
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
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)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
switch len(args) {
|
||||
case noArgs:
|
||||
if isListing { // zot config -l
|
||||
|
@ -115,7 +115,7 @@ func NewConfigAddCommand() *cobra.Command {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
// zot config add <config-name> <url>
|
||||
err = addConfig(configPath, args[0], args[1])
|
||||
if err != nil {
|
||||
|
@ -145,7 +145,7 @@ func NewConfigRemoveCommand() *cobra.Command {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
// zot config add <config-name> <url>
|
||||
err = removeConfig(configPath, args[0])
|
||||
if err != nil {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
func TestConfigCmdBasics(t *testing.T) {
|
||||
|
@ -146,7 +146,7 @@ func TestConfigCmdMain(t *testing.T) {
|
|||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldEqual, zotErrors.ErrCliBadConfig)
|
||||
So(err, ShouldEqual, zerr.ErrCliBadConfig)
|
||||
})
|
||||
|
||||
Convey("Test add config with invalid URL", t, func() {
|
||||
|
@ -160,7 +160,7 @@ func TestConfigCmdMain(t *testing.T) {
|
|||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||
})
|
||||
|
||||
Convey("Test remove config entry successfully", t, func() {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -29,15 +29,15 @@ func NewCveCommand(searchService SearchService) *cobra.Command {
|
|||
|
||||
cveCmd := &cobra.Command{
|
||||
Use: "cve [config-name]",
|
||||
Short: "Lookup CVEs in images hosted on the zot registry",
|
||||
Long: `List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
|
||||
Short: "DEPRECATED (see cves)",
|
||||
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 {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
|
@ -48,12 +48,12 @@ func NewCveCommand(searchService SearchService) *cobra.Command {
|
|||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ func searchCve(searchConfig searchConfig) error {
|
|||
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
|
||||
// wait and retry a few more times
|
||||
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/spf13/cobra"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
|
@ -82,7 +83,7 @@ func TestSearchCVECmd(t *testing.T) {
|
|||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zotErrors.ErrNoURLProvided)
|
||||
So(err, ShouldEqual, zerr.ErrNoURLProvided)
|
||||
})
|
||||
|
||||
Convey("Test CVE no params", t, func() {
|
||||
|
@ -95,7 +96,7 @@ func TestSearchCVECmd(t *testing.T) {
|
|||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldEqual, zotErrors.ErrInvalidFlagsCombination)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidFlagsCombination)
|
||||
})
|
||||
|
||||
Convey("Test CVE invalid url", t, func() {
|
||||
|
@ -109,7 +110,7 @@ func TestSearchCVECmd(t *testing.T) {
|
|||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||
})
|
||||
|
||||
|
@ -285,7 +286,7 @@ func TestSearchCVECmd(t *testing.T) {
|
|||
err := cveCmd.Execute()
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
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)
|
||||
})
|
||||
|
||||
|
@ -355,7 +356,7 @@ func TestSearchCVECmd(t *testing.T) {
|
|||
cveCmd.SetArgs(args)
|
||||
err := cveCmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||
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 {
|
||||
searchCveParams := make(map[string]*string)
|
||||
|
||||
|
@ -1072,7 +1397,7 @@ func MockNewCveCommand(searchService SearchService) *cobra.Command {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
if err != nil {
|
||||
|
@ -1082,12 +1407,12 @@ func MockNewCveCommand(searchService SearchService) *cobra.Command {
|
|||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -1232,7 +1557,7 @@ func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
|||
|
||||
descriptor, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zotErrors.ErrTagMetaNotFound
|
||||
return false, zerr.ErrTagMetaNotFound
|
||||
}
|
||||
|
||||
manifestDigestStr = descriptor.Digest
|
||||
|
@ -1252,7 +1577,7 @@ func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
|||
|
||||
err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent)
|
||||
if err != nil {
|
||||
return false, zotErrors.ErrScanNotSupported
|
||||
return false, zerr.ErrScanNotSupported
|
||||
}
|
||||
|
||||
for _, imageLayer := range manifestContent.Layers {
|
||||
|
@ -1262,7 +1587,7 @@ func getMockCveInfo(metaDB mTypes.MetaDB, log log.Logger) cveinfo.CveInfo {
|
|||
return true, nil
|
||||
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,
|
||||
username, password, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
username, password, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
) {
|
||||
service.retryCounter += 1
|
||||
|
||||
if service.retryCounter < service.succeedOn || service.succeedOn < 0 {
|
||||
rch <- stringResult{"", zotErrors.ErrCVEDBNotFound}
|
||||
rch <- stringResult{"", zerr.ErrCVEDBNotFound}
|
||||
close(rch)
|
||||
|
||||
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"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
||||
type field struct {
|
||||
Name string `json:"name"`
|
||||
Args []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"args"`
|
||||
}
|
||||
|
||||
type schemaList struct {
|
||||
|
@ -23,9 +27,19 @@ type schemaList struct {
|
|||
QueryType struct {
|
||||
Fields []field `json:"fields"`
|
||||
} `json:"queryType"` //nolint:tagliatelle // graphQL schema
|
||||
Types []typeInfo `json:"types"`
|
||||
} `json:"__schema"` //nolint:tagliatelle // graphQL schema
|
||||
} `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 {
|
||||
|
@ -38,6 +52,54 @@ func containsGQLQuery(queryList []field, query string) bool {
|
|||
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 {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
ctx := context.Background()
|
||||
|
@ -59,7 +121,7 @@ func checkExtEndPoint(config searchConfig) bool {
|
|||
searchEnabled := false
|
||||
|
||||
for _, extension := range discoverResponse.Extensions {
|
||||
if extension.Name == "_zot" {
|
||||
if extension.Name == constants.BaseExtension {
|
||||
for _, endpoint := range extension.Endpoints {
|
||||
if endpoint == constants.FullSearchPrefix {
|
||||
searchEnabled = true
|
||||
|
@ -99,3 +161,80 @@ func checkExtEndPoint(config searchConfig) bool {
|
|||
|
||||
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/spf13/cobra"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
|
@ -24,16 +24,16 @@ func NewImageCommand(searchService SearchService) *cobra.Command {
|
|||
var isSpinner, verifyTLS, verbose, debug bool
|
||||
|
||||
imageCmd := &cobra.Command{
|
||||
Use: "images [config-name]",
|
||||
Short: "List images hosted on the zot registry",
|
||||
Long: `List images hosted on the zot registry`,
|
||||
Use: "image [config-name]",
|
||||
Short: "DEPRECATED (see images)",
|
||||
Long: `DEPRECATED (see images)! List images hosted on the zot registry`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
|
@ -44,12 +44,12 @@ func NewImageCommand(searchService SearchService) *cobra.Command {
|
|||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} 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", "",
|
||||
"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.Flags().StringVarP(user, "user", "u", "", `User Credentials of zot server in "username:password" format`)
|
||||
imageCmd.Flags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
|
||||
imageCmd.Flags().BoolVar(verbose, "verbose", false, "Show verbose output")
|
||||
imageCmd.Flags().BoolVar(debug, "debug", false, "Show debug output")
|
||||
imageCmd.PersistentFlags().StringVar(servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
||||
imageCmd.PersistentFlags().StringVarP(user, "user", "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
imageCmd.PersistentFlags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
|
||||
imageCmd.PersistentFlags().BoolVar(verbose, "verbose", false, "Show verbose output")
|
||||
imageCmd.PersistentFlags().BoolVar(debug, "debug", false, "Show debug output")
|
||||
}
|
||||
|
||||
func searchImage(searchConfig searchConfig) error {
|
||||
|
@ -156,7 +157,7 @@ func searchImage(searchConfig searchConfig) error {
|
|||
}
|
||||
}
|
||||
|
||||
return zotErrors.ErrInvalidFlagsCombination
|
||||
return zerr.ErrInvalidFlagsCombination
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -26,9 +26,10 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"gopkg.in/resty.v1"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
zlog "zotregistry.io/zot/pkg/log"
|
||||
|
@ -74,7 +75,7 @@ func TestSearchImageCmd(t *testing.T) {
|
|||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zotErrors.ErrNoURLProvided)
|
||||
So(err, ShouldEqual, zerr.ErrNoURLProvided)
|
||||
})
|
||||
|
||||
Convey("Test image invalid home directory", t, func() {
|
||||
|
@ -130,7 +131,7 @@ func TestSearchImageCmd(t *testing.T) {
|
|||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||
})
|
||||
|
||||
|
@ -253,7 +254,7 @@ func TestSearchImageCmd(t *testing.T) {
|
|||
imageCmd.SetArgs(args)
|
||||
err := imageCmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zotErrors.ErrInvalidURL)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||
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) {
|
||||
// ------- Define Image1
|
||||
layer11 := []byte{11, 12, 13, 14}
|
||||
|
@ -1606,7 +2165,7 @@ func MockNewImageCommand(searchService SearchService) *cobra.Command {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
if err != nil {
|
||||
|
@ -1616,12 +2175,12 @@ func MockNewImageCommand(searchService SearchService) *cobra.Command {
|
|||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
|
@ -1679,7 +2238,7 @@ func MockSearchImage(searchConfig searchConfig) error {
|
|||
}
|
||||
}
|
||||
|
||||
return zotErrors.ErrInvalidFlagsCombination
|
||||
return zerr.ErrInvalidFlagsCombination
|
||||
}
|
||||
|
||||
func uploadManifest(url string) error {
|
||||
|
@ -1895,7 +2454,73 @@ func uploadManifestDerivedBase(url string) error {
|
|||
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,
|
||||
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 close(channel)
|
||||
|
||||
var catalog [3]string
|
||||
catalog[0] = "python"
|
||||
catalog[1] = "busybox"
|
||||
catalog[2] = "hello-world"
|
||||
fmt.Fprintln(config.resultWriter, "\n\nREPOSITORY NAME")
|
||||
|
||||
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,
|
||||
repo, digest string,
|
||||
) (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,
|
||||
query string,
|
||||
) (*common.GlobalSearch, error) {
|
||||
if service.globalSearchGQLFn != nil {
|
||||
return service.globalSearchGQLFn(ctx, config, username, password, query)
|
||||
}
|
||||
|
||||
return &common.GlobalSearch{
|
||||
Images: []common.ImageSummary{
|
||||
{
|
||||
RepoName: "repo",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Size: "100",
|
||||
Manifests: []common.ManifestSummary{
|
||||
{
|
||||
Digest: godigest.FromString("str").String(),
|
||||
Size: "100",
|
||||
Digest: godigest.FromString("str").String(),
|
||||
Size: "100",
|
||||
ConfigDigest: ispec.DescriptorEmptyJSON.Digest.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Repos: []common.RepoSummary{
|
||||
{
|
||||
Name: "repo",
|
||||
Name: "repo",
|
||||
Size: "100",
|
||||
LastUpdated: time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
}, 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,
|
||||
repo, digest string,
|
||||
) (*common.ReferrersResp, error) {
|
||||
if service.getReferrersGQLFn != nil {
|
||||
return service.getReferrersGQLFn(ctx, config, username, password, repo, digest)
|
||||
}
|
||||
|
||||
return &common.ReferrersResp{
|
||||
ReferrersResult: common.ReferrersResult{
|
||||
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,
|
||||
derivedImage string,
|
||||
) (*common.DerivedImageListResponse, error) {
|
||||
if service.getDerivedImageListGQLFn != nil {
|
||||
return service.getDerivedImageListGQLFn(ctx, config, username, password, derivedImage)
|
||||
}
|
||||
|
||||
imageListGQLResponse := &common.DerivedImageListResponse{}
|
||||
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,
|
||||
derivedImage string,
|
||||
baseImage string,
|
||||
) (*common.BaseImageListResponse, error) {
|
||||
if service.getBaseImageListGQLFn != nil {
|
||||
return service.getBaseImageListGQLFn(ctx, config, username, password, baseImage)
|
||||
}
|
||||
|
||||
imageListGQLResponse := &common.BaseImageListResponse{}
|
||||
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,
|
||||
imageName string,
|
||||
) (*common.ImageListResponse, error) {
|
||||
if service.getImagesGQLFn != nil {
|
||||
return service.getImagesGQLFn(ctx, config, username, password, imageName)
|
||||
}
|
||||
|
||||
imageListGQLResponse := &common.ImageListResponse{}
|
||||
imageListGQLResponse.PaginatedImagesResult.Results = []common.ImageSummary{
|
||||
{
|
||||
|
@ -2029,9 +2687,13 @@ func (service mockService) getImagesGQL(ctx context.Context, config searchConfig
|
|||
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,
|
||||
) (*common.ImagesForDigest, error) {
|
||||
if service.getImagesForDigestGQLFn != nil {
|
||||
return service.getImagesForDigestGQLFn(ctx, config, username, password, digest)
|
||||
}
|
||||
|
||||
imageListGQLResponse := &common.ImagesForDigest{}
|
||||
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,
|
||||
digest string,
|
||||
) (*common.ImagesForCve, error) {
|
||||
if service.getImagesByCveIDGQLFn != nil {
|
||||
return service.getImagesByCveIDGQLFn(ctx, config, username, password, digest)
|
||||
}
|
||||
|
||||
imagesForCve := &common.ImagesForCve{
|
||||
Errors: nil,
|
||||
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,
|
||||
imageName, cveID string,
|
||||
) (*common.ImagesForCve, error) {
|
||||
if service.getTagsForCVEGQLFn != nil {
|
||||
return service.getTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID)
|
||||
}
|
||||
|
||||
images := &common.ImagesForCve{
|
||||
Errors: nil,
|
||||
ImagesForCVEList: struct {
|
||||
|
@ -2082,6 +2752,10 @@ func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchCo
|
|||
}{},
|
||||
}
|
||||
|
||||
if imageName == "" {
|
||||
imageName = "image-name"
|
||||
}
|
||||
|
||||
images.Errors = nil
|
||||
|
||||
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,
|
||||
imageName, cveID string,
|
||||
) (*common.ImageListWithCVEFixedResponse, error) {
|
||||
if service.getFixedTagsForCVEGQLFn != nil {
|
||||
return service.getFixedTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID)
|
||||
}
|
||||
|
||||
fixedTags := &common.ImageListWithCVEFixedResponse{
|
||||
Errors: nil,
|
||||
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,
|
||||
imageName, searchedCVE string,
|
||||
) (*cveResult, error) {
|
||||
if service.getCveByImageGQLFn != nil {
|
||||
return service.getCveByImageGQLFn(ctx, config, username, password, imageName, searchedCVE)
|
||||
}
|
||||
cveRes := &cveResult{}
|
||||
cveRes.Data = cveData{
|
||||
CVEListForImage: cveListForImage{
|
||||
|
@ -2141,6 +2822,7 @@ func (service mockService) getMockedImageByName(imageName string) imageStruct {
|
|||
image := imageStruct{}
|
||||
image.RepoName = imageName
|
||||
image.Tag = "tag"
|
||||
image.MediaType = ispec.MediaTypeImageManifest
|
||||
image.Manifests = []common.ManifestSummary{
|
||||
{
|
||||
Digest: godigest.FromString("Digest").String(),
|
||||
|
@ -2160,6 +2842,12 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig
|
|||
defer wtgrp.Done()
|
||||
defer close(channel)
|
||||
|
||||
if service.getAllImagesFn != nil {
|
||||
service.getAllImagesFn(ctx, config, username, password, channel, wtgrp)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
image := &imageStruct{}
|
||||
image.RepoName = "randomimageName"
|
||||
image.Tag = "tag"
|
||||
|
@ -2192,6 +2880,12 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf
|
|||
defer wtgrp.Done()
|
||||
defer close(channel)
|
||||
|
||||
if service.getImageByNameFn != nil {
|
||||
service.getImageByNameFn(ctx, config, username, password, imageName, channel, wtgrp)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
image := &imageStruct{}
|
||||
image.RepoName = imageName
|
||||
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,
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
|
||||
func (service mockService) getImagesByDigest(ctx context.Context, config searchConfig, username,
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -2288,7 +3018,7 @@ func makeConfigFile(content string) string {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
|
||||
if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
|
||||
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/spf13/cobra"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
const prefix = "Searching... "
|
||||
|
@ -24,13 +25,14 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
|||
Use: "repos [config-name]",
|
||||
Short: "List all repositories",
|
||||
Long: `List all repositories`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
|
@ -41,12 +43,12 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
|||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,9 +98,12 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
|||
|
||||
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
||||
|
||||
repoCmd.Flags().StringVar(&servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
||||
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.AddCommand(NewListReposCommand(searchService))
|
||||
|
||||
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
|
||||
}
|
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/viper"
|
||||
|
||||
"zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
zlog "zotregistry.io/zot/pkg/log"
|
||||
|
@ -213,7 +214,13 @@ func NewCliRootCmd() *cobra.Command {
|
|||
// additional cmds
|
||||
enableCli(rootCmd)
|
||||
// "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
|
||||
}
|
||||
|
@ -225,18 +232,18 @@ func validateStorageConfig(cfg *config.Config, log zlog.Logger) error {
|
|||
|
||||
for _, storageConfig := range cfg.Storage.SubPaths {
|
||||
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]
|
||||
if ok {
|
||||
equal := expConfig.ParamsEqual(storageConfig)
|
||||
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 {
|
||||
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)
|
||||
//nolint: lll
|
||||
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!")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
|
||||
if cfg.Storage.CacheDriver != nil && cfg.Storage.RemoteCache {
|
||||
// local storage with remote caching
|
||||
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
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
}
|
||||
|
||||
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" +
|
||||
"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)
|
||||
//nolint: lll
|
||||
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 {
|
||||
// local storage with remote caching
|
||||
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
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
}
|
||||
|
||||
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," +
|
||||
"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,
|
||||
// but those are both enabled by having the search and ui extensions enabled
|
||||
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
|
||||
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 {
|
||||
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 {
|
||||
//nolint:lll
|
||||
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 {
|
||||
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 {
|
||||
// enforce s3 driver in case of using storage driver
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
|
||||
// enforce filesystem storage in case sync feature is enabled
|
||||
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 {
|
||||
if len(storageConfig.StorageDriver) != 0 {
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -456,23 +463,23 @@ func validateOpenIDConfig(cfg *config.Config, log zlog.Logger) error {
|
|||
if config.IsOpenIDSupported(provider) {
|
||||
if providerConfig.ClientID == "" || providerConfig.Issuer == "" ||
|
||||
len(providerConfig.Scopes) == 0 {
|
||||
log.Error().Err(errors.ErrBadConfig).
|
||||
log.Error().Err(zerr.ErrBadConfig).
|
||||
Msg("OpenID provider config requires clientid, issuer and scopes parameters")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
} else if config.IsOauth2Supported(provider) {
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
} else {
|
||||
log.Error().Err(errors.ErrBadConfig).
|
||||
log.Error().Err(zerr.ErrBadConfig).
|
||||
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 {
|
||||
if (config.HTTP.Auth == nil || (config.HTTP.Auth.HTPasswd.Path == "" && config.HTTP.Auth.LDAP == nil &&
|
||||
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 " +
|
||||
"or using only 'anonymousPolicy' policies")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -730,15 +737,15 @@ func LoadConfiguration(config *config.Config, configPath string) error {
|
|||
log := zlog.NewLogger(config.Log.Level, config.Log.Output)
|
||||
|
||||
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 {
|
||||
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
|
||||
|
@ -803,21 +810,21 @@ func validateLDAP(config *config.Config, log zlog.Logger) error {
|
|||
log.Error().Str("userAttribute", ldap.UserAttribute).
|
||||
Msg("invalid LDAP configuration, missing mandatory key: userAttribute")
|
||||
|
||||
return errors.ErrLDAPConfig
|
||||
return zerr.ErrLDAPConfig
|
||||
}
|
||||
|
||||
if ldap.Address == "" {
|
||||
log.Error().Str("address", ldap.Address).
|
||||
Msg("invalid LDAP configuration, missing mandatory key: address")
|
||||
|
||||
return errors.ErrLDAPConfig
|
||||
return zerr.ErrLDAPConfig
|
||||
}
|
||||
|
||||
if ldap.BaseDN == "" {
|
||||
log.Error().Str("basedn", ldap.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) {
|
||||
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 {
|
||||
// enforce GC params
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
|
||||
if !config.Storage.GC {
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -868,12 +875,12 @@ func validateGC(config *config.Config, log zlog.Logger) error {
|
|||
// subpaths
|
||||
for name, subPath := range config.Storage.SubPaths {
|
||||
if subPath.GC && subPath.GCDelay <= 0 {
|
||||
log.Error().Err(errors.ErrBadConfig).
|
||||
log.Error().Err(zerr.ErrBadConfig).
|
||||
Str("subPath", name).
|
||||
Interface("gcDelay", subPath.GCDelay).
|
||||
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 {
|
||||
// check retry options are configured for sync
|
||||
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")
|
||||
|
||||
return errors.ErrBadConfig
|
||||
return zerr.ErrBadConfig
|
||||
}
|
||||
|
||||
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 == "/" {
|
||||
log.Error().Err(errors.ErrBadConfig).
|
||||
log.Error().Err(zerr.ErrBadConfig).
|
||||
Interface("sync content", content).
|
||||
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) {
|
||||
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(`{
|
||||
"distSpecVersion": "1.1.0-dev",
|
||||
"http": {
|
||||
|
|
|
@ -10,7 +10,8 @@ import (
|
|||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
|
@ -21,7 +22,7 @@ func NewSearchCommand(searchService SearchService) *cobra.Command {
|
|||
|
||||
var isSpinner, verifyTLS, verbose, debug bool
|
||||
|
||||
imageCmd := &cobra.Command{
|
||||
searchCmd := &cobra.Command{
|
||||
Use: "search [config-name]",
|
||||
Short: "Search images and their tags",
|
||||
Long: `Search repos or images
|
||||
|
@ -36,13 +37,14 @@ Example:
|
|||
zli search --subject repo@sha256:f9a0981...
|
||||
zli search --subject repo:tag
|
||||
`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home + "/.zot")
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
|
@ -53,12 +55,12 @@ Example:
|
|||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zotErrors.ErrNoURLProvided
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,27 +109,32 @@ Example:
|
|||
},
|
||||
}
|
||||
|
||||
setupSearchFlags(imageCmd, searchImageParams, &servURL, &user, &outputFormat, &verbose, &debug)
|
||||
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
||||
setupSearchFlags(searchCmd, searchImageParams, &servURL, &user, &outputFormat, &verbose, &debug)
|
||||
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,
|
||||
) {
|
||||
searchImageParams["query"] = imageCmd.Flags().StringP("query", "q", "",
|
||||
searchImageParams["query"] = searchCmd.Flags().StringP("query", "q", "",
|
||||
"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"+
|
||||
"(repo@digest)")
|
||||
|
||||
imageCmd.Flags().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.Flags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
|
||||
imageCmd.Flags().BoolVar(verbose, "verbose", false, "Show verbose output")
|
||||
imageCmd.Flags().BoolVar(debug, "debug", false, "Show debug output")
|
||||
searchCmd.Flags().StringVar(servURL, cmdflags.URLFlag, "", "Specify zot server URL if config-name is not mentioned")
|
||||
searchCmd.Flags().StringVarP(user, cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
searchCmd.PersistentFlags().StringVarP(outputFormat, cmdflags.OutputFormatFlag, "f", "",
|
||||
"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 {
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
func ref[T any](input T) *T {
|
||||
ref := input
|
||||
|
||||
return &ref
|
||||
}
|
||||
|
||||
const (
|
||||
customArtTypeV1 = "application/custom.art.type.v1"
|
||||
customArtTypeV2 = "application/custom.art.type.v2"
|
||||
|
@ -428,7 +422,7 @@ func TestFormatsReferrersCLI(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
|
||||
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}]}`,
|
||||
baseURL))
|
||||
|
@ -446,7 +440,7 @@ func TestFormatsReferrersCLI(t *testing.T) {
|
|||
fmt.Println(buff.String())
|
||||
})
|
||||
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}]}`,
|
||||
baseURL))
|
||||
|
@ -464,7 +458,7 @@ func TestFormatsReferrersCLI(t *testing.T) {
|
|||
fmt.Println(buff.String())
|
||||
})
|
||||
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}]}`,
|
||||
baseURL))
|
||||
|
@ -488,7 +482,7 @@ func TestReferrersCLIErrors(t *testing.T) {
|
|||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
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}]}`)
|
||||
|
||||
|
@ -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/config"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
@ -240,7 +241,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
|||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
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}]}`,
|
||||
baseURL))
|
||||
|
@ -257,7 +258,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
|||
})
|
||||
|
||||
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}]}`,
|
||||
baseURL))
|
||||
|
@ -274,7 +275,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
|||
})
|
||||
|
||||
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}]}`,
|
||||
baseURL))
|
||||
|
@ -296,7 +297,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
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}]}`)
|
||||
|
||||
|
@ -311,7 +312,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("getConfigValue", func() {
|
||||
args := []string{"searchtest", "--output", "invalid", "--query", "repo/alpine"}
|
||||
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||
|
||||
configPath := makeConfigFile(`bad-json`)
|
||||
|
||||
|
@ -358,7 +359,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
})
|
||||
|
||||
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}]}`)
|
||||
|
||||
|
@ -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"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
@ -326,7 +326,7 @@ func (search imagesByDigestSearcherGQL) search(config searchConfig) (bool, error
|
|||
|
||||
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 {
|
||||
return true, err
|
||||
}
|
||||
|
@ -770,7 +770,7 @@ func (search globalSearcherREST) search(config searchConfig) (bool, error) {
|
|||
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,
|
||||
|
@ -813,7 +813,7 @@ func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan strin
|
|||
config.spinner.stopSpinner()
|
||||
cancel()
|
||||
|
||||
errCh <- zotErrors.ErrCLITimeout
|
||||
errCh <- zerr.ErrCLITimeout
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
@ -34,13 +34,13 @@ const (
|
|||
type SearchService interface { //nolint:interfacebloat
|
||||
getImagesGQL(ctx context.Context, config searchConfig, username, password string,
|
||||
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)
|
||||
getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
||||
imageName string, searchedCVE string) (*cveResult, error)
|
||||
getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
|
||||
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)
|
||||
getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
||||
cveID string) (*common.ImageListWithCVEFixedResponse, error)
|
||||
|
@ -260,7 +260,7 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf
|
|||
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,
|
||||
) (*common.ImagesForDigest, error) {
|
||||
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,
|
||||
username, password, imageName, cveID string,
|
||||
username, password, repo, cveID string,
|
||||
) (*common.ImagesForCve, error) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
|
@ -387,7 +387,19 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
|
|||
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,
|
||||
|
@ -537,7 +549,9 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
|||
) {
|
||||
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 isContextDone(ctx) {
|
||||
return
|
||||
|
@ -570,9 +584,17 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
|||
continue
|
||||
}
|
||||
|
||||
shouldMatchTag := imageTag != ""
|
||||
matchesTag := tag == imageTag
|
||||
|
||||
// when the tag is empty we match everything
|
||||
if shouldMatchTag && !matchesTag {
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
for _, image := range result.Results {
|
||||
if !strings.EqualFold(imageName, image.RepoName) {
|
||||
if imageName != "" && !strings.EqualFold(imageName, image.RepoName) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1438,7 +1460,7 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man
|
|||
platform += offset
|
||||
}
|
||||
|
||||
minifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "")
|
||||
manifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "")
|
||||
configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "")
|
||||
imgSize, _ := strconv.ParseUint(manifest.Size, 10, 64)
|
||||
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[colTagIndex] = tagName
|
||||
row[colDigestIndex] = minifestDigestStr
|
||||
row[colDigestIndex] = manifestDigestStr
|
||||
row[colPlatformIndex] = platform
|
||||
row[colSizeIndex] = size
|
||||
row[colIsSignedIndex] = strconv.FormatBool(isSigned)
|
||||
|
@ -1487,23 +1509,23 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man
|
|||
return nil
|
||||
}
|
||||
|
||||
func getPlatformStr(platf common.Platform) string {
|
||||
if platf.Arch == "" && platf.Os == "" {
|
||||
func getPlatformStr(platform common.Platform) string {
|
||||
if platform.Arch == "" && platform.Os == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
platform := platf.Os
|
||||
fullPlatform := platform.Os
|
||||
|
||||
if platf.Arch != "" {
|
||||
platform = platform + "/" + platf.Arch
|
||||
platform = strings.Trim(platform, "/")
|
||||
if platform.Arch != "" {
|
||||
fullPlatform = fullPlatform + "/" + platform.Arch
|
||||
fullPlatform = strings.Trim(fullPlatform, "/")
|
||||
|
||||
if platf.Variant != "" {
|
||||
platform = platform + "/" + platf.Variant
|
||||
if platform.Variant != "" {
|
||||
fullPlatform = fullPlatform + "/" + platform.Variant
|
||||
}
|
||||
}
|
||||
|
||||
return platform
|
||||
return fullPlatform
|
||||
}
|
||||
|
||||
func (img imageStruct) stringJSON() (string, error) {
|
||||
|
@ -1534,12 +1556,12 @@ type catalogResponse struct {
|
|||
|
||||
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
|
||||
if !isURL(serverURL) {
|
||||
return "", zotErrors.ErrInvalidURL
|
||||
return "", zerr.ErrInvalidURL
|
||||
}
|
||||
|
||||
newURL, err := url.Parse(serverURL)
|
||||
if err != nil {
|
||||
return "", zotErrors.ErrInvalidURL
|
||||
return "", zerr.ErrInvalidURL
|
||||
}
|
||||
|
||||
newURL, _ = newURL.Parse(endPoint)
|
||||
|
|
|
@ -111,7 +111,7 @@ func GetRepoReference(repo string) (string, string, bool, error) {
|
|||
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 {
|
||||
if IsTag(ref) {
|
||||
return repo + ":" + ref
|
||||
|
@ -129,3 +129,7 @@ func IsDigest(ref string) bool {
|
|||
func IsTag(ref string) bool {
|
||||
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/viper"
|
||||
|
||||
"zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/exporter/api"
|
||||
)
|
||||
|
||||
|
@ -66,12 +66,12 @@ func loadConfiguration(config *api.Config, configPath string) {
|
|||
|
||||
metaData := &mapstructure.Metadata{}
|
||||
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)
|
||||
}
|
||||
|
||||
if len(metaData.Keys) == 0 || len(metaData.Unused) > 0 {
|
||||
log.Error().Err(errors.ErrBadConfig).Msg("Bad configuration, retry writing it")
|
||||
panic(errors.ErrBadConfig)
|
||||
log.Error().Err(zerr.ErrBadConfig).Msg("Bad configuration, retry writing it")
|
||||
panic(zerr.ErrBadConfig)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func GetExtensions(config *config.Config) distext.ExtensionList {
|
|||
|
||||
if len(endpoints) > 0 {
|
||||
extensions = append(extensions, distext.Extension{
|
||||
Name: "_zot",
|
||||
Name: constants.BaseExtension,
|
||||
URL: "https://github.com/project-zot/zot/blob/" + config.ReleaseTag + "/pkg/extensions/_zot.md",
|
||||
Description: "zot registry extensions",
|
||||
Endpoints: endpoints,
|
||||
|
|
|
@ -30,7 +30,7 @@ type ImageAnnotations struct {
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
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)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repoMetas, manifestMetaMap, _, err := metaDB.SearchRepos(context.Background(), "")
|
||||
reposMeta, manifestMetaMap, _, err := metaDB.SearchRepos(context.Background(), "")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ctx := graphql.WithResponseContext(context.Background(),
|
||||
|
@ -69,7 +69,7 @@ func TestConvertErrors(t *testing.T) {
|
|||
|
||||
_ = convert.RepoMeta2RepoSummary(
|
||||
ctx,
|
||||
repoMetas[0],
|
||||
reposMeta[0],
|
||||
manifestMetaMap,
|
||||
map[string]mTypes.IndexData{},
|
||||
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() {
|
||||
before := time.Time{}
|
||||
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
|
||||
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
|
||||
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,
|
||||
skip SkipQGLField, cveInfo cveinfo.CveInfo, filter mTypes.Filter, pageInput pagination.PageInput,
|
||||
) ([]*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
|
||||
}
|
||||
|
||||
for _, repoMeta := range repoMetas {
|
||||
for _, repoMeta := range reposMeta {
|
||||
repoSummary := RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
|
||||
|
||||
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
|
||||
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
|
||||
repoBlob2Size = make(map[string]int64, 10)
|
||||
|
||||
|
@ -694,7 +694,7 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta mTypes.RepoMetadata
|
|||
skip.Vulnerabilities, repoMeta, manifestMetaMap, indexDataMap, cveInfo)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -599,9 +599,9 @@ func TestCVESearch(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
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.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
|
@ -610,7 +610,7 @@ func TestCVESearch(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
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.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
|
@ -618,7 +618,7 @@ func TestCVESearch(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
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.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
|
@ -635,7 +635,7 @@ func TestCVESearch(t *testing.T) {
|
|||
So(resp, ShouldNotBeNil)
|
||||
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.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
|
@ -643,7 +643,7 @@ func TestCVESearch(t *testing.T) {
|
|||
So(resp, ShouldNotBeNil)
|
||||
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.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
|
@ -651,11 +651,11 @@ func TestCVESearch(t *testing.T) {
|
|||
So(resp, ShouldNotBeNil)
|
||||
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.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.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
|
@ -663,7 +663,7 @@ func TestCVESearch(t *testing.T) {
|
|||
So(resp, ShouldNotBeNil)
|
||||
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.StatusCode(), ShouldEqual, 200)
|
||||
|
||||
|
@ -732,7 +732,7 @@ func TestCVESearch(t *testing.T) {
|
|||
So(resp, ShouldNotBeNil)
|
||||
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.StatusCode(), ShouldEqual, 200)
|
||||
})
|
||||
|
@ -1412,7 +1412,7 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
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
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo100", "CVE100")
|
||||
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
|
||||
|
|
|
@ -123,7 +123,7 @@ func TestGlobalSearch(t *testing.T) {
|
|||
manifestBlob, err := json.Marshal(ispec.Manifest{})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
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{})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: []byte("bad manifest blob"),
|
||||
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{})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
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{})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
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)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: []byte("bad manifest blob"),
|
||||
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{})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
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() {
|
||||
|
@ -776,7 +776,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
manifestBlob := []byte("invalid")
|
||||
|
||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
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{})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||
manifestDigest: {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -829,9 +829,9 @@ func TestImageListForDigest(t *testing.T) {
|
|||
}
|
||||
matchedTags := 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(manifestMetaDatas, manifestDescriptor.Digest)
|
||||
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -839,7 +839,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||
|
||||
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)
|
||||
|
||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||
manifestDigest: {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -908,9 +908,9 @@ func TestImageListForDigest(t *testing.T) {
|
|||
|
||||
matchedTags := 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(manifestMetaDatas, manifestDescriptor.Digest)
|
||||
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -918,7 +918,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||
|
||||
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)
|
||||
|
||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||
manifestDigest: {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -985,9 +985,9 @@ func TestImageListForDigest(t *testing.T) {
|
|||
|
||||
matchedTags := 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(manifestMetaDatas, manifestDescriptor.Digest)
|
||||
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -995,7 +995,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||
|
||||
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: {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -1054,9 +1054,9 @@ func TestImageListForDigest(t *testing.T) {
|
|||
matchedTags := repo.Tags
|
||||
|
||||
for tag, manifestDescriptor := range repo.Tags {
|
||||
if !filterFunc(repo, manifestMetaDatas[manifestDescriptor.Digest]) {
|
||||
if !filterFunc(repo, manifestsMetaData[manifestDescriptor.Digest]) {
|
||||
delete(matchedTags, tag)
|
||||
delete(manifestMetaDatas, manifestDescriptor.Digest)
|
||||
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -1065,7 +1065,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||
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: {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -1123,9 +1123,9 @@ func TestImageListForDigest(t *testing.T) {
|
|||
matchedTags := repo.Tags
|
||||
|
||||
for tag, manifestDescriptor := range repo.Tags {
|
||||
if !filterFunc(repo, manifestMetaDatas[manifestDescriptor.Digest]) {
|
||||
if !filterFunc(repo, manifestsMetaData[manifestDescriptor.Digest]) {
|
||||
delete(matchedTags, tag)
|
||||
delete(manifestMetaDatas, manifestDescriptor.Digest)
|
||||
delete(manifestsMetaData, manifestDescriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -1136,7 +1136,7 @@ func TestImageListForDigest(t *testing.T) {
|
|||
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{})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetaDatas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMetaData := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
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{},
|
||||
map[string]mTypes.IndexData{}, nil
|
||||
}
|
||||
|
||||
return repos, manifestMetaDatas, map[string]mTypes.IndexData{}, nil
|
||||
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
|
||||
},
|
||||
}
|
||||
|
||||
limit := 1
|
||||
ofset := 0
|
||||
offset := 0
|
||||
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
||||
pageInput := gql_generated.PageInput{
|
||||
Limit: &limit,
|
||||
Offset: &ofset,
|
||||
Offset: &offset,
|
||||
SortBy: &sortCriteria,
|
||||
}
|
||||
|
||||
|
@ -1889,7 +1889,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
configBlob1, err := json.Marshal(ispec.Image{
|
||||
|
@ -2076,7 +2076,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
|||
},
|
||||
"CVE2": {
|
||||
ID: "CVE2",
|
||||
Severity: "MEDIM",
|
||||
Severity: "MEDIUM",
|
||||
Title: "Title CVE2",
|
||||
Description: "Description CVE2",
|
||||
},
|
||||
|
@ -2961,7 +2961,7 @@ func TestDerivedImageList(t *testing.T) {
|
|||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -3019,9 +3019,9 @@ func TestDerivedImageList(t *testing.T) {
|
|||
matchedTags := repo.Tags
|
||||
|
||||
for tag, descriptor := range repo.Tags {
|
||||
if !filterFunc(repo, manifestMetas[descriptor.Digest]) {
|
||||
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
|
||||
delete(matchedTags, tag)
|
||||
delete(manifestMetas, descriptor.Digest)
|
||||
delete(manifestsMeta, descriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -3030,7 +3030,7 @@ func TestDerivedImageList(t *testing.T) {
|
|||
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)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -3301,9 +3301,9 @@ func TestBaseImageList(t *testing.T) {
|
|||
matchedTags := repo.Tags
|
||||
|
||||
for tag, descriptor := range repo.Tags {
|
||||
if !filterFunc(repo, manifestMetas[descriptor.Digest]) {
|
||||
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
|
||||
delete(matchedTags, tag)
|
||||
delete(manifestMetas, descriptor.Digest)
|
||||
delete(manifestsMeta, descriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -3312,7 +3312,7 @@ func TestBaseImageList(t *testing.T) {
|
|||
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,
|
||||
|
@ -3416,7 +3416,7 @@ func TestBaseImageList(t *testing.T) {
|
|||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestMetas := map[string]mTypes.ManifestMetadata{
|
||||
manifestsMeta := map[string]mTypes.ManifestMetadata{
|
||||
"digestTag1.0.1": {
|
||||
ManifestBlob: manifestBlob,
|
||||
ConfigBlob: configBlob,
|
||||
|
@ -3467,9 +3467,9 @@ func TestBaseImageList(t *testing.T) {
|
|||
matchedTags := repo.Tags
|
||||
|
||||
for tag, descriptor := range repo.Tags {
|
||||
if !filterFunc(repo, manifestMetas[descriptor.Digest]) {
|
||||
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
|
||||
delete(matchedTags, tag)
|
||||
delete(manifestMetas, descriptor.Digest)
|
||||
delete(manifestsMeta, descriptor.Digest)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -3478,7 +3478,7 @@ func TestBaseImageList(t *testing.T) {
|
|||
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,
|
||||
|
@ -3519,12 +3519,12 @@ func TestExpandedRepoInfo(t *testing.T) {
|
|||
Digest: "goodIndexBadManifests",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
"tagGoodIndex1GoodManfest": {
|
||||
Digest: "goodIndexGoodManfest",
|
||||
"tagGoodIndex1GoodManifest": {
|
||||
Digest: "goodIndexGoodManifest",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
"tagGoodIndex2GoodManfest": {
|
||||
Digest: "goodIndexGoodManfest",
|
||||
"tagGoodIndex2GoodManifest": {
|
||||
Digest: "goodIndexGoodManifest",
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
},
|
||||
},
|
||||
|
@ -3569,7 +3569,7 @@ func TestExpandedRepoInfo(t *testing.T) {
|
|||
return mTypes.IndexData{
|
||||
IndexBlob: goodIndexBadManifestsBlob,
|
||||
}, nil
|
||||
case "goodIndexGoodManfest":
|
||||
case "goodIndexGoodManifest":
|
||||
return mTypes.IndexData{
|
||||
IndexBlob: goodIndexGoodManifestBlob,
|
||||
}, nil
|
||||
|
|
|
@ -6242,8 +6242,8 @@ func TestImageSummary(t *testing.T) {
|
|||
|
||||
WaitTillServerReady(baseURL)
|
||||
|
||||
manifestBlob, errMarsal := json.Marshal(manifest)
|
||||
So(errMarsal, ShouldBeNil)
|
||||
manifestBlob, errMarshal := json.Marshal(manifest)
|
||||
So(errMarshal, ShouldBeNil)
|
||||
So(manifestBlob, ShouldNotBeNil)
|
||||
manifestDigest := godigest.FromBytes(manifestBlob)
|
||||
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() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/extensions/config"
|
||||
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
||||
"zotregistry.io/zot/pkg/extensions/lint"
|
||||
|
@ -92,7 +92,7 @@ func TestSyncInternal(t *testing.T) {
|
|||
|
||||
repositoryReference = fmt.Sprintf("%s/%s:tagged", host, testImage)
|
||||
_, err = parseRepositoryReference(repositoryReference)
|
||||
So(err, ShouldEqual, errors.ErrInvalidRepositoryName)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidRepositoryName)
|
||||
|
||||
repositoryReference = fmt.Sprintf("http://%s/%s", host, testImage)
|
||||
_, err = parseRepositoryReference(repositoryReference)
|
||||
|
@ -341,7 +341,7 @@ func TestLocalRegistry(t *testing.T) {
|
|||
registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
|
||||
SetRepoReferenceFn: func(repo, Reference string, manifestDigest godigest.Digest, mediaType string) error {
|
||||
if Reference == "1.0" {
|
||||
return errors.ErrRepoMetaNotFound
|
||||
return zerr.ErrRepoMetaNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -355,7 +355,7 @@ func TestLocalRegistry(t *testing.T) {
|
|||
Convey("trigger metaDB error on image manifest in CommitImage()", func() {
|
||||
registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
|
||||
SetRepoReferenceFn: func(repo, Reference string, manifestDigest godigest.Digest, mediaType string) error {
|
||||
return errors.ErrRepoMetaNotFound
|
||||
return zerr.ErrRepoMetaNotFound
|
||||
},
|
||||
}, log)
|
||||
|
||||
|
|
|
@ -1156,7 +1156,7 @@ func (bdw *BoltDB) FilterTags(ctx context.Context, filterFunc mTypes.FilterFunc,
|
|||
repoMeta.IsStarred = zcommon.Contains(userStars, repoMeta.Name)
|
||||
|
||||
matchedTags := make(map[string]mTypes.Descriptor)
|
||||
// take all manifestMetas
|
||||
// take all manifestsMeta
|
||||
for tag, descriptor := range repoMeta.Tags {
|
||||
switch descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
glob "github.com/bmatcuk/doublestar/v4" //nolint:gci
|
||||
|
||||
"zotregistry.io/zot/errors"
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
type Key int
|
||||
|
@ -41,7 +41,7 @@ func GetAccessControlContext(ctx context.Context) (*AccessControlContext, error)
|
|||
if authCtx := ctx.Value(authzCtxKey); authCtx != nil {
|
||||
acCtx, ok := authCtx.(AccessControlContext)
|
||||
if !ok {
|
||||
return nil, errors.ErrBadType
|
||||
return nil, zerr.ErrBadType
|
||||
}
|
||||
|
||||
return &acCtx, nil
|
||||
|
@ -110,7 +110,7 @@ func GetAuthnMiddlewareContext(ctx context.Context) (*AuthnMiddlewareContext, er
|
|||
if authnMiddlewareCtx := ctx.Value(authnMiddlewareCtxKey); authnMiddlewareCtx != nil {
|
||||
amCtx, ok := authnMiddlewareCtx.(AuthnMiddlewareContext)
|
||||
if !ok {
|
||||
return nil, errors.ErrBadType
|
||||
return nil, zerr.ErrBadType
|
||||
}
|
||||
|
||||
return &amCtx, nil
|
||||
|
|
|
@ -54,7 +54,7 @@ import (
|
|||
"zotregistry.io/zot/pkg/storage"
|
||||
storageCommon "zotregistry.io/zot/pkg/storage/common"
|
||||
"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/mocks"
|
||||
)
|
||||
|
@ -1437,12 +1437,12 @@ func ListNotarySignatures(reference string, tdir string) ([]godigest.Digest, err
|
|||
|
||||
sigRepo := notreg.NewRepository(remoteRepo)
|
||||
|
||||
artifectDesc, err := sigRepo.Resolve(ctx, reference)
|
||||
artifactDesc, err := sigRepo.Resolve(ctx, reference)
|
||||
if err != nil {
|
||||
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 {
|
||||
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,
|
||||
monitoring.NewMetricsServer(false, log),
|
||||
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
|
||||
},
|
||||
},
|
||||
|
@ -2084,3 +2084,20 @@ func GetDefaultStoreController(rootDir string, log zLog.Logger) storage.StoreCon
|
|||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
type VulnerableConfigBuilder interface {
|
||||
// VulnerableConfig sets the given config while keeping the correct RootFS values for the
|
||||
|
|
Loading…
Reference in a new issue