mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
feat(cli): add sort-by flag to sub commands (#1768)
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
parent
c210e3f377
commit
aae8b7b4e3
23 changed files with 714 additions and 98 deletions
|
@ -160,4 +160,6 @@ var (
|
||||||
ErrFileAlreadyClosed = errors.New("storageDriver: file already closed")
|
ErrFileAlreadyClosed = errors.New("storageDriver: file already closed")
|
||||||
ErrFileAlreadyCommitted = errors.New("storageDriver: file already committed")
|
ErrFileAlreadyCommitted = errors.New("storageDriver: file already committed")
|
||||||
ErrInvalidOutputFormat = errors.New("cli: invalid output format")
|
ErrInvalidOutputFormat = errors.New("cli: invalid output format")
|
||||||
|
ErrFlagValueUnsupported = errors.New("supported values ")
|
||||||
|
ErrUnknownSubcommand = errors.New("cli: unknown subcommand")
|
||||||
)
|
)
|
||||||
|
|
|
@ -149,11 +149,22 @@ func doHTTPRequest(req *http.Request, verifyTLS bool, debug bool,
|
||||||
return resp.Header, nil
|
return resp.Header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isURL(str string) bool {
|
func validateURL(str string) error {
|
||||||
u, err := url.Parse(str)
|
parsedURL, err := url.Parse(str)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "first path segment in URL cannot contain colon") {
|
||||||
|
return fmt.Errorf("%w: scheme not provided (ex: https://)", zerr.ErrInvalidURL)
|
||||||
|
}
|
||||||
|
|
||||||
return err == nil && u.Scheme != "" && u.Host != ""
|
return err
|
||||||
} // from https://stackoverflow.com/a/55551215
|
}
|
||||||
|
|
||||||
|
if parsedURL.Scheme == "" || parsedURL.Host == "" {
|
||||||
|
return fmt.Errorf("%w: scheme not provided (ex: https://)", zerr.ErrInvalidURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type requestsPool struct {
|
type requestsPool struct {
|
||||||
jobs chan *httpJob
|
jobs chan *httpJob
|
||||||
|
|
|
@ -98,7 +98,7 @@ func TestTLSWithAuth(t *testing.T) {
|
||||||
imageCmd.SetArgs(args)
|
imageCmd.SetArgs(args)
|
||||||
err = imageCmd.Execute()
|
err = imageCmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(imageBuff.String(), ShouldContainSubstring, "invalid URL format")
|
So(imageBuff.String(), ShouldContainSubstring, "scheme not provided")
|
||||||
|
|
||||||
args = []string{"list", "--config", "imagetest"}
|
args = []string{"list", "--config", "imagetest"}
|
||||||
configPath = makeConfigFile(
|
configPath = makeConfigFile(
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
package cmdflags
|
package cmdflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
URLFlag = "url"
|
URLFlag = "url"
|
||||||
ConfigFlag = "config"
|
ConfigFlag = "config"
|
||||||
|
@ -10,4 +18,144 @@ const (
|
||||||
VersionFlag = "version"
|
VersionFlag = "version"
|
||||||
DebugFlag = "debug"
|
DebugFlag = "debug"
|
||||||
SearchedCVEID = "cve-id"
|
SearchedCVEID = "cve-id"
|
||||||
|
SortByFlag = "sort-by"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SortByRelevance = "relevance"
|
||||||
|
SortByUpdateTime = "update-time"
|
||||||
|
SortByAlphabeticAsc = "alpha-asc"
|
||||||
|
SortByAlphabeticDsc = "alpha-dsc"
|
||||||
|
SortBySeverity = "severity"
|
||||||
|
)
|
||||||
|
|
||||||
|
const stringType = "string"
|
||||||
|
|
||||||
|
func ImageListSortOptions() []string {
|
||||||
|
return []string{SortByUpdateTime, SortByAlphabeticAsc, SortByAlphabeticDsc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageListSortOptionsStr() string {
|
||||||
|
return strings.Join(ImageListSortOptions(), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageSearchSortOptions() []string {
|
||||||
|
return []string{SortByRelevance, SortByUpdateTime, SortByAlphabeticAsc, SortByAlphabeticDsc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageSearchSortOptionsStr() string {
|
||||||
|
return strings.Join(ImageSearchSortOptions(), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CVEListSortOptions() []string {
|
||||||
|
return []string{SortByAlphabeticAsc, SortByAlphabeticDsc, SortBySeverity}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CVEListSortOptionsStr() string {
|
||||||
|
return strings.Join(CVEListSortOptions(), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RepoListSortOptions() []string {
|
||||||
|
return []string{SortByAlphabeticAsc, SortByAlphabeticDsc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RepoListSortOptionsStr() string {
|
||||||
|
return strings.Join(RepoListSortOptions(), ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flag2SortCriteria(sortBy string) string {
|
||||||
|
switch sortBy {
|
||||||
|
case SortByRelevance:
|
||||||
|
return "RELEVANCE"
|
||||||
|
case SortByUpdateTime:
|
||||||
|
return "UPDATE_TIME"
|
||||||
|
case SortByAlphabeticAsc:
|
||||||
|
return "ALPHABETIC_ASC"
|
||||||
|
case SortByAlphabeticDsc:
|
||||||
|
return "ALPHABETIC_DSC"
|
||||||
|
case SortBySeverity:
|
||||||
|
return "SEVERITY"
|
||||||
|
default:
|
||||||
|
return "BAD_SORT_CRITERIA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CVEListSortFlag string
|
||||||
|
|
||||||
|
func (e *CVEListSortFlag) String() string {
|
||||||
|
return string(*e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CVEListSortFlag) Set(val string) error {
|
||||||
|
if !common.Contains(CVEListSortOptions(), val) {
|
||||||
|
return fmt.Errorf("%w %s", zerr.ErrFlagValueUnsupported, CVEListSortOptionsStr())
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = CVEListSortFlag(val)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CVEListSortFlag) Type() string {
|
||||||
|
return stringType
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageListSortFlag string
|
||||||
|
|
||||||
|
func (e *ImageListSortFlag) String() string {
|
||||||
|
return string(*e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ImageListSortFlag) Set(val string) error {
|
||||||
|
if !common.Contains(ImageListSortOptions(), val) {
|
||||||
|
return fmt.Errorf("%w %s", zerr.ErrFlagValueUnsupported, ImageListSortOptionsStr())
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = ImageListSortFlag(val)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ImageListSortFlag) Type() string {
|
||||||
|
return stringType
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageSearchSortFlag string
|
||||||
|
|
||||||
|
func (e *ImageSearchSortFlag) String() string {
|
||||||
|
return string(*e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ImageSearchSortFlag) Set(val string) error {
|
||||||
|
if !common.Contains(ImageSearchSortOptions(), val) {
|
||||||
|
return fmt.Errorf("%w %s", zerr.ErrFlagValueUnsupported, ImageSearchSortOptionsStr())
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = ImageSearchSortFlag(val)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ImageSearchSortFlag) Type() string {
|
||||||
|
return stringType
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepoListSortFlag string
|
||||||
|
|
||||||
|
func (e *RepoListSortFlag) String() string {
|
||||||
|
return string(*e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RepoListSortFlag) Set(val string) error {
|
||||||
|
if !common.Contains(RepoListSortOptions(), val) {
|
||||||
|
return fmt.Errorf("%w %s", zerr.ErrFlagValueUnsupported, RepoListSortOptionsStr())
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = RepoListSortFlag(val)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RepoListSortFlag) Type() string {
|
||||||
|
return stringType
|
||||||
|
}
|
||||||
|
|
45
pkg/cli/cmdflags/flags_test.go
Normal file
45
pkg/cli/cmdflags/flags_test.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package cmdflags_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
. "zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
|
gql_gen "zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSortFlagsMapping(t *testing.T) {
|
||||||
|
// We do this to not import the whole gql_gen in the CLI
|
||||||
|
Convey("Make sure the sort-by values map correctly to the gql enum type", t, func() {
|
||||||
|
So(Flag2SortCriteria(SortByRelevance), ShouldResemble, string(gql_gen.SortCriteriaRelevance))
|
||||||
|
So(Flag2SortCriteria(SortByUpdateTime), ShouldResemble, string(gql_gen.SortCriteriaUpdateTime))
|
||||||
|
So(Flag2SortCriteria(SortByAlphabeticAsc), ShouldResemble, string(gql_gen.SortCriteriaAlphabeticAsc))
|
||||||
|
So(Flag2SortCriteria(SortByAlphabeticDsc), ShouldResemble, string(gql_gen.SortCriteriaAlphabeticDsc))
|
||||||
|
So(Flag2SortCriteria(SortBySeverity), ShouldResemble, string(gql_gen.SortCriteriaSeverity))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortFlags(t *testing.T) {
|
||||||
|
Convey("Flags", t, func() {
|
||||||
|
cveSortFlag := CVEListSortFlag("")
|
||||||
|
err := cveSortFlag.Set("bad-flag")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
imageListSortFlag := ImageListSortFlag("")
|
||||||
|
err = imageListSortFlag.Set("bad-flag")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
imageSearchSortFlag := ImageSearchSortFlag("")
|
||||||
|
err = imageSearchSortFlag.Set("bad-flag")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
repoListSearchFlag := RepoListSortFlag("")
|
||||||
|
err = repoListSearchFlag.Set("bad-flag")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Flag2SortCriteria", t, func() {
|
||||||
|
So(Flag2SortCriteria("bad-flag"), ShouldResemble, "BAD_SORT_CRITERIA")
|
||||||
|
})
|
||||||
|
}
|
|
@ -248,8 +248,8 @@ func addConfig(configPath, configName, url string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isURL(url) {
|
if err := validateURL(url); err != nil {
|
||||||
return zerr.ErrInvalidURL
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if configNameExists(configs, configName) {
|
if configNameExists(configs, configName) {
|
||||||
|
|
|
@ -160,7 +160,7 @@ func TestConfigCmdMain(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
So(strings.Contains(err.Error(), zerr.ErrInvalidURL.Error()), ShouldBeTrue)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Test remove config entry successfully", t, func() {
|
Convey("Test remove config entry successfully", t, func() {
|
||||||
|
|
|
@ -910,6 +910,159 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCVESort(t *testing.T) {
|
||||||
|
rootDir := t.TempDir()
|
||||||
|
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},
|
||||||
|
CVE: &extconf.CVEConfig{
|
||||||
|
UpdateInterval: 2,
|
||||||
|
Trivy: &extconf.TrivyConfig{
|
||||||
|
DBRepository: "ghcr.io/project-zot/trivy-db",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = rootDir
|
||||||
|
|
||||||
|
image1 := test.CreateRandomImage()
|
||||||
|
|
||||||
|
storeController := test.GetDefaultStoreController(rootDir, ctlr.Log)
|
||||||
|
|
||||||
|
err := test.WriteImageToFileSystem(image1, "repo", "tag", storeController)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if err := ctlr.Init(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
severities := map[string]int{
|
||||||
|
"UNKNOWN": 0,
|
||||||
|
"LOW": 1,
|
||||||
|
"MEDIUM": 2,
|
||||||
|
"HIGH": 3,
|
||||||
|
"CRITICAL": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlr.CveInfo = cveinfo.BaseCveInfo{
|
||||||
|
Log: ctlr.Log,
|
||||||
|
MetaDB: mocks.MetaDBMock{},
|
||||||
|
Scanner: mocks.CveScannerMock{
|
||||||
|
CompareSeveritiesFn: func(severity1, severity2 string) int {
|
||||||
|
return severities[severity2] - severities[severity1]
|
||||||
|
},
|
||||||
|
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||||
|
return map[string]cvemodel.CVE{
|
||||||
|
"CVE-2023-1255": {
|
||||||
|
ID: "CVE-2023-1255",
|
||||||
|
Severity: "LOW",
|
||||||
|
Title: "Input buffer over-read in AES-XTS implementation and testing",
|
||||||
|
},
|
||||||
|
"CVE-2023-2650": {
|
||||||
|
ID: "CVE-2023-2650",
|
||||||
|
Severity: "MEDIUM",
|
||||||
|
Title: "Possible DoS translating ASN.1 object identifier and executer",
|
||||||
|
},
|
||||||
|
"CVE-2023-2975": {
|
||||||
|
ID: "CVE-2023-2975",
|
||||||
|
Severity: "HIGH",
|
||||||
|
Title: "AES-SIV cipher implementation contains a bug that can break",
|
||||||
|
},
|
||||||
|
"CVE-2023-3446": {
|
||||||
|
ID: "CVE-2023-3446",
|
||||||
|
Severity: "CRITICAL",
|
||||||
|
Title: "Excessive time spent checking DH keys and parenthesis",
|
||||||
|
},
|
||||||
|
"CVE-2023-3817": {
|
||||||
|
ID: "CVE-2023-3817",
|
||||||
|
Severity: "MEDIUM",
|
||||||
|
Title: "Excessive time spent checking DH q parameter and arguments",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer ctlr.Shutdown()
|
||||||
|
|
||||||
|
test.WaitTillServerReady(baseURL)
|
||||||
|
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
|
||||||
|
Convey("test sorting", t, func() {
|
||||||
|
args := []string{"list", "repo:tag", "--sort-by", "severity", "--url", baseURL}
|
||||||
|
cmd := NewCVECommand(new(searchService))
|
||||||
|
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, ShouldResemble,
|
||||||
|
"ID SEVERITY TITLE "+
|
||||||
|
"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
|
||||||
|
"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
|
||||||
|
"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
|
||||||
|
"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+
|
||||||
|
"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...")
|
||||||
|
|
||||||
|
args = []string{"list", "repo:tag", "--sort-by", "alpha-asc", "--url", baseURL}
|
||||||
|
cmd = NewCVECommand(new(searchService))
|
||||||
|
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, ShouldResemble,
|
||||||
|
"ID SEVERITY TITLE "+
|
||||||
|
"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat... "+
|
||||||
|
"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
|
||||||
|
"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
|
||||||
|
"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
|
||||||
|
"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ...")
|
||||||
|
|
||||||
|
args = []string{"list", "repo:tag", "--sort-by", "alpha-dsc", "--url", baseURL}
|
||||||
|
cmd = NewCVECommand(new(searchService))
|
||||||
|
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, ShouldResemble,
|
||||||
|
"ID SEVERITY TITLE "+
|
||||||
|
"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+
|
||||||
|
"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
|
||||||
|
"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
|
||||||
|
"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
|
||||||
|
"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCVECommandGQL(t *testing.T) {
|
func TestCVECommandGQL(t *testing.T) {
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
baseURL := test.GetBaseURL(port)
|
baseURL := test.GetBaseURL(port)
|
||||||
|
|
|
@ -14,6 +14,7 @@ func NewCVECommand(searchService SearchService) *cobra.Command {
|
||||||
Use: "cve [command]",
|
Use: "cve [command]",
|
||||||
Short: "Lookup CVEs in images hosted on the zot registry",
|
Short: "Lookup CVEs in images hosted on the zot registry",
|
||||||
Long: `List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
|
Long: `List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
|
||||||
|
RunE: ShowSuggestionsIfUnknownCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
cvesCmd.SetUsageTemplate(cvesCmd.UsageTemplate() + usageFooter)
|
cvesCmd.SetUsageTemplate(cvesCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
|
@ -19,7 +19,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCveForImageCommand(searchService SearchService) *cobra.Command {
|
func NewCveForImageCommand(searchService SearchService) *cobra.Command {
|
||||||
var searchedCVEID string
|
var (
|
||||||
|
searchedCVEID string
|
||||||
|
cveListSortFlag = cmdflags.CVEListSortFlag(cmdflags.SortBySeverity)
|
||||||
|
)
|
||||||
|
|
||||||
cveForImageCmd := &cobra.Command{
|
cveForImageCmd := &cobra.Command{
|
||||||
Use: "list [repo:tag]|[repo@digest]",
|
Use: "list [repo:tag]|[repo@digest]",
|
||||||
|
@ -44,12 +47,17 @@ func NewCveForImageCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
cveForImageCmd.Flags().StringVar(&searchedCVEID, cmdflags.SearchedCVEID, "", "Search for a specific CVE by name/id")
|
cveForImageCmd.Flags().StringVar(&searchedCVEID, cmdflags.SearchedCVEID, "", "Search for a specific CVE by name/id")
|
||||||
|
cveForImageCmd.Flags().Var(&cveListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.CVEListSortOptionsStr()))
|
||||||
|
|
||||||
return cveForImageCmd
|
return cveForImageCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImagesByCVEIDCommand(searchService SearchService) *cobra.Command {
|
func NewImagesByCVEIDCommand(searchService SearchService) *cobra.Command {
|
||||||
var repo string
|
var (
|
||||||
|
repo string
|
||||||
|
imageListSortFlag = cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
)
|
||||||
|
|
||||||
imagesByCVEIDCmd := &cobra.Command{
|
imagesByCVEIDCmd := &cobra.Command{
|
||||||
Use: "affected [cveId]",
|
Use: "affected [cveId]",
|
||||||
|
@ -84,15 +92,19 @@ func NewImagesByCVEIDCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
imagesByCVEIDCmd.Flags().StringVar(&repo, "repo", "", "Search for a specific CVE by name/id")
|
imagesByCVEIDCmd.Flags().StringVar(&repo, "repo", "", "Search for a specific CVE by name/id")
|
||||||
|
imagesByCVEIDCmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
return imagesByCVEIDCmd
|
return imagesByCVEIDCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
|
func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
fixedTagsCmd := &cobra.Command{
|
fixedTagsCmd := &cobra.Command{
|
||||||
Use: "fixed [repo] [cveId]",
|
Use: "fixed [repo] [cveId]",
|
||||||
Short: "List tags where a CVE is fixedRetryWithContext",
|
Short: "List tags where a CVE is fixed",
|
||||||
Long: `List tags where a CVE is fixedRetryWithContext`,
|
Long: `List tags where a CVE is fixed`,
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
const argCount = 2
|
const argCount = 2
|
||||||
|
|
||||||
|
@ -124,5 +136,8 @@ func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fixedTagsCmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
return fixedTagsCmd
|
return fixedTagsCmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ func NewImageCommand(searchService SearchService) *cobra.Command {
|
||||||
Use: "image [command]",
|
Use: "image [command]",
|
||||||
Short: "List images hosted on the zot registry",
|
Short: "List images hosted on the zot registry",
|
||||||
Long: `List images hosted on the zot registry`,
|
Long: `List images hosted on the zot registry`,
|
||||||
|
RunE: ShowSuggestionsIfUnknownCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
|
@ -131,7 +131,7 @@ func TestSearchImageCmd(t *testing.T) {
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
So(strings.Contains(err.Error(), zerr.ErrInvalidURL.Error()), ShouldBeTrue)
|
||||||
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
So(buff.String(), ShouldContainSubstring, "invalid URL format")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1357,6 +1357,80 @@ func runDisplayIndexTests(baseURL string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImagesSortFlag(t *testing.T) {
|
||||||
|
rootDir := t.TempDir()
|
||||||
|
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},
|
||||||
|
CVE: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = rootDir
|
||||||
|
|
||||||
|
image1 := test.CreateImageWith().DefaultLayers().
|
||||||
|
ImageConfig(ispec.Image{Created: test.DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}).Build()
|
||||||
|
|
||||||
|
image2 := test.CreateImageWith().DefaultLayers().
|
||||||
|
ImageConfig(ispec.Image{Created: test.DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}).Build()
|
||||||
|
|
||||||
|
storeController := test.GetDefaultStoreController(rootDir, ctlr.Log)
|
||||||
|
|
||||||
|
err := test.WriteImageToFileSystem(image1, "a-repo", "tag1", storeController)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = test.WriteImageToFileSystem(image2, "b-repo", "tag2", storeController)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("Sorting", t, func() {
|
||||||
|
args := []string{"list", "--sort-by", "alpha-asc", "--url", baseURL}
|
||||||
|
cmd := NewImageCommand(new(searchService))
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
str := buff.String()
|
||||||
|
So(strings.Index(str, "a-repo"), ShouldBeLessThan, strings.Index(str, "b-repo"))
|
||||||
|
|
||||||
|
args = []string{"list", "--sort-by", "alpha-dsc", "--url", baseURL}
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
str = buff.String()
|
||||||
|
So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-repo"))
|
||||||
|
|
||||||
|
args = []string{"list", "--sort-by", "update-time", "--url", baseURL}
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
str = buff.String()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-repo"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestImagesCommandGQL(t *testing.T) {
|
func TestImagesCommandGQL(t *testing.T) {
|
||||||
port := test.GetFreePort()
|
port := test.GetFreePort()
|
||||||
baseURL := test.GetBaseURL(port)
|
baseURL := test.GetBaseURL(port)
|
||||||
|
@ -2620,6 +2694,7 @@ func getTestSearchConfig(url string, searchService SearchService) searchConfig {
|
||||||
|
|
||||||
return searchConfig{
|
return searchConfig{
|
||||||
searchService: searchService,
|
searchService: searchService,
|
||||||
|
sortBy: "alpha-asc",
|
||||||
servURL: url,
|
servURL: url,
|
||||||
user: user,
|
user: user,
|
||||||
outputFormat: outputFormat,
|
outputFormat: outputFormat,
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
@ -12,7 +14,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewImageListCommand(searchService SearchService) *cobra.Command {
|
func NewImageListCommand(searchService SearchService) *cobra.Command {
|
||||||
return &cobra.Command{
|
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List all images",
|
Short: "List all images",
|
||||||
Long: "List all images",
|
Long: "List all images",
|
||||||
|
@ -30,10 +34,18 @@ func NewImageListCommand(searchService SearchService) *cobra.Command {
|
||||||
return SearchAllImages(searchConfig)
|
return SearchAllImages(searchConfig)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageCVEListCommand(searchService SearchService) *cobra.Command {
|
func NewImageCVEListCommand(searchService SearchService) *cobra.Command {
|
||||||
var searchedCVEID string
|
var (
|
||||||
|
searchedCVEID string
|
||||||
|
cveListSortFlag = cmdflags.CVEListSortFlag(cmdflags.SortBySeverity)
|
||||||
|
)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "cve [repo]|[repo-name:tag]|[repo-name@digest]",
|
Use: "cve [repo]|[repo-name:tag]|[repo-name@digest]",
|
||||||
|
@ -57,11 +69,15 @@ func NewImageCVEListCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVar(&searchedCVEID, cmdflags.SearchedCVEID, "", "Search for a specific CVE by name/id")
|
cmd.Flags().StringVar(&searchedCVEID, cmdflags.SearchedCVEID, "", "Search for a specific CVE by name/id")
|
||||||
|
cmd.Flags().Var(&cveListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.CVEListSortOptionsStr()))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageDerivedCommand(searchService SearchService) *cobra.Command {
|
func NewImageDerivedCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "derived [repo-name:tag]|[repo-name@digest]",
|
Use: "derived [repo-name:tag]|[repo-name@digest]",
|
||||||
Short: "List images that are derived from given image",
|
Short: "List images that are derived from given image",
|
||||||
|
@ -81,10 +97,15 @@ func NewImageDerivedCommand(searchService SearchService) *cobra.Command {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageBaseCommand(searchService SearchService) *cobra.Command {
|
func NewImageBaseCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "base [repo-name:tag]|[repo-name@digest]",
|
Use: "base [repo-name:tag]|[repo-name@digest]",
|
||||||
Short: "List images that are base for the given image",
|
Short: "List images that are base for the given image",
|
||||||
|
@ -104,10 +125,15 @@ func NewImageBaseCommand(searchService SearchService) *cobra.Command {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageDigestCommand(searchService SearchService) *cobra.Command {
|
func NewImageDigestCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "digest [digest]",
|
Use: "digest [digest]",
|
||||||
Short: "List images that contain a blob(manifest, config or layer) with the given digest",
|
Short: "List images that contain a blob(manifest, config or layer) with the given digest",
|
||||||
|
@ -129,10 +155,15 @@ zli image digest sha256:8a1930f0...`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageNameCommand(searchService SearchService) *cobra.Command {
|
func NewImageNameCommand(searchService SearchService) *cobra.Command {
|
||||||
|
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "name [repo:tag]",
|
Use: "name [repo:tag]",
|
||||||
Short: "List image details by name",
|
Short: "List image details by name",
|
||||||
|
@ -164,5 +195,8 @@ func NewImageNameCommand(searchService SearchService) *cobra.Command {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
||||||
Use: "repo [config-name]",
|
Use: "repo [config-name]",
|
||||||
Short: "List all repositories",
|
Short: "List all repositories",
|
||||||
Long: `List all repositories`,
|
Long: `List all repositories`,
|
||||||
|
RunE: ShowSuggestionsIfUnknownCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
|
@ -4,10 +4,16 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewListReposCommand(searchService SearchService) *cobra.Command {
|
func NewListReposCommand(searchService SearchService) *cobra.Command {
|
||||||
|
repoListSortFlag := cmdflags.RepoListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List all repositories",
|
Short: "List all repositories",
|
||||||
|
@ -23,5 +29,8 @@ func NewListReposCommand(searchService SearchService) *cobra.Command {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Var(&repoListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.RepoListSortOptionsStr()))
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,11 @@ func TestReposCommand(t *testing.T) {
|
||||||
cm.StartAndWait(conf.HTTP.Port)
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
defer cm.StopServer()
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
err := test.UploadImage(test.CreateRandomImage(), baseURL, "repo1", "tag1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = test.UploadImage(test.CreateRandomImage(), baseURL, "repo2", "tag2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"repostest","url":"%s","showspinner":false}]}`,
|
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"repostest","url":"%s","showspinner":false}]}`,
|
||||||
baseURL))
|
baseURL))
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
@ -42,12 +47,55 @@ func TestReposCommand(t *testing.T) {
|
||||||
cmd.SetOut(buff)
|
cmd.SetOut(buff)
|
||||||
cmd.SetErr(buff)
|
cmd.SetErr(buff)
|
||||||
cmd.SetArgs(args)
|
cmd.SetArgs(args)
|
||||||
err := cmd.Execute()
|
err = cmd.Execute()
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
actual := strings.TrimSpace(str)
|
actual := strings.TrimSpace(str)
|
||||||
So(actual, ShouldContainSubstring, "repo1")
|
So(actual, ShouldContainSubstring, "repo1")
|
||||||
So(actual, ShouldContainSubstring, "repo2")
|
So(actual, ShouldContainSubstring, "repo2")
|
||||||
|
|
||||||
|
args = []string{"list", "--sort-by", "alpha-dsc", "--config", "repostest"}
|
||||||
|
cmd = NewRepoCommand(new(searchService))
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space = regexp.MustCompile(`\s+`)
|
||||||
|
str = space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual = strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo1")
|
||||||
|
So(actual, ShouldContainSubstring, "repo2")
|
||||||
|
So(strings.Index(actual, "repo2"), ShouldBeLessThan, strings.Index(actual, "repo1"))
|
||||||
|
|
||||||
|
args = []string{"list", "--sort-by", "alpha-asc", "--config", "repostest"}
|
||||||
|
cmd = NewRepoCommand(new(searchService))
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
space = regexp.MustCompile(`\s+`)
|
||||||
|
str = space.ReplaceAllString(buff.String(), " ")
|
||||||
|
actual = strings.TrimSpace(str)
|
||||||
|
So(actual, ShouldContainSubstring, "repo1")
|
||||||
|
So(actual, ShouldContainSubstring, "repo2")
|
||||||
|
So(strings.Index(actual, "repo1"), ShouldBeLessThan, strings.Index(actual, "repo2"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestions(t *testing.T) {
|
||||||
|
Convey("Suggestions", t, func() {
|
||||||
|
space := regexp.MustCompile(`\s+`)
|
||||||
|
suggestion := ShowSuggestionsIfUnknownCommand(NewRepoCommand(mockService{}), []string{"bad-command"})
|
||||||
|
str := space.ReplaceAllString(suggestion.Error(), " ")
|
||||||
|
So(str, ShouldContainSubstring, "unknown subcommand")
|
||||||
|
|
||||||
|
suggestion = ShowSuggestionsIfUnknownCommand(NewRepoCommand(mockService{}), []string{"listt"})
|
||||||
|
str = space.ReplaceAllString(suggestion.Error(), " ")
|
||||||
|
So(str, ShouldContainSubstring, "Did you mean this? list")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,10 @@ import (
|
||||||
|
|
||||||
func NewSearchCommand(searchService SearchService) *cobra.Command {
|
func NewSearchCommand(searchService SearchService) *cobra.Command {
|
||||||
searchCmd := &cobra.Command{
|
searchCmd := &cobra.Command{
|
||||||
Use: "search [config-name]",
|
Use: "search [command]",
|
||||||
Short: "Search images and their tags",
|
Short: "Search images and their tags",
|
||||||
Long: `Search repos or images`,
|
Long: `Search repos or images`,
|
||||||
|
RunE: ShowSuggestionsIfUnknownCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
searchCmd.SetUsageTemplate(searchCmd.UsageTemplate() + usageFooter)
|
searchCmd.SetUsageTemplate(searchCmd.UsageTemplate() + usageFooter)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
@ -862,3 +863,70 @@ func TestSearchCommandREST(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchSort(t *testing.T) {
|
||||||
|
rootDir := t.TempDir()
|
||||||
|
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},
|
||||||
|
CVE: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = rootDir
|
||||||
|
|
||||||
|
image1 := test.CreateImageWith().DefaultLayers().
|
||||||
|
ImageConfig(ispec.Image{Created: test.DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
image2 := test.CreateImageWith().DefaultLayers().
|
||||||
|
ImageConfig(ispec.Image{Created: test.DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}).
|
||||||
|
Build()
|
||||||
|
|
||||||
|
storeController := test.GetDefaultStoreController(rootDir, ctlr.Log)
|
||||||
|
|
||||||
|
err := test.WriteImageToFileSystem(image1, "b-repo", "tag2", storeController)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = test.WriteImageToFileSystem(image2, "a-test-repo", "tag2", storeController)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
Convey("test sorting", t, func() {
|
||||||
|
args := []string{"query", "repo", "--sort-by", "relevance", "--url", baseURL}
|
||||||
|
cmd := NewSearchCommand(new(searchService))
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err := cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
str := buff.String()
|
||||||
|
So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-test-repo"))
|
||||||
|
|
||||||
|
args = []string{"query", "repo", "--sort-by", "alpha-asc", "--url", baseURL}
|
||||||
|
cmd = NewSearchCommand(new(searchService))
|
||||||
|
buff = bytes.NewBufferString("")
|
||||||
|
cmd.SetOut(buff)
|
||||||
|
cmd.SetErr(buff)
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
err = cmd.Execute()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
str = buff.String()
|
||||||
|
So(strings.Index(str, "a-test-repo"), ShouldBeLessThan, strings.Index(str, "b-repo"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -9,11 +9,14 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
zcommon "zotregistry.io/zot/pkg/common"
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewSearchSubjectCommand(searchService SearchService) *cobra.Command {
|
func NewSearchSubjectCommand(searchService SearchService) *cobra.Command {
|
||||||
imageCmd := &cobra.Command{
|
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
Use: "subject [repo:tag]|[repo@digest]",
|
Use: "subject [repo:tag]|[repo@digest]",
|
||||||
Short: "List all referrers for this subject.",
|
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" +
|
Long: `List all referrers for this subject. The subject can be specified by tag(repo:tag) or by digest" +
|
||||||
|
@ -36,22 +39,24 @@ func NewSearchSubjectCommand(searchService SearchService) *cobra.Command {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageCmd
|
cmd.Flags().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||||
|
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSearchQueryCommand(searchService SearchService) *cobra.Command {
|
func NewSearchQueryCommand(searchService SearchService) *cobra.Command {
|
||||||
imageCmd := &cobra.Command{
|
imageSearchSortFlag := cmdflags.ImageSearchSortFlag(cmdflags.SortByRelevance)
|
||||||
Use: "query",
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "query [repo]|[repo:tag]",
|
||||||
Short: "Fuzzy search for repos and their tags.",
|
Short: "Fuzzy search for repos and their tags.",
|
||||||
Long: "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
|
Example: `# For repo search specify a substring of the repo name without the tag
|
||||||
zli search query "test/repo"
|
zli search query "test/repo"
|
||||||
|
|
||||||
# For image search specify the full repo name followed by the tag or a prefix of the tag.
|
# 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."
|
zli search query "test/repo:2.1."`,
|
||||||
|
|
||||||
# To search all tags in all repos.
|
|
||||||
zli search query ":"`,
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||||
|
@ -70,14 +75,17 @@ func NewSearchQueryCommand(searchService SearchService) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := CheckExtEndPointQuery(searchConfig, GlobalSearchQuery()); err != nil {
|
if err := CheckExtEndPointQuery(searchConfig, GlobalSearchQuery()); err != nil {
|
||||||
return fmt.Errorf("%w: '%s'", err, CVEListForImageQuery().Name)
|
return fmt.Errorf("%w: '%s'", err, GlobalSearchQuery().Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GlobalSearchGQL(searchConfig, args[0])
|
return GlobalSearchGQL(searchConfig, args[0])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageCmd
|
cmd.Flags().Var(&imageSearchSortFlag, cmdflags.SortByFlag,
|
||||||
|
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageSearchSortOptionsStr()))
|
||||||
|
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func OneImageWithRefArg(cmd *cobra.Command, args []string) error {
|
func OneImageWithRefArg(cmd *cobra.Command, args []string) error {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api/constants"
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
|
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||||
"zotregistry.io/zot/pkg/common"
|
"zotregistry.io/zot/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ type searchConfig struct {
|
||||||
servURL string
|
servURL string
|
||||||
user string
|
user string
|
||||||
outputFormat string
|
outputFormat string
|
||||||
|
sortBy string
|
||||||
verifyTLS bool
|
verifyTLS bool
|
||||||
fixedFlag bool
|
fixedFlag bool
|
||||||
verbose bool
|
verbose bool
|
||||||
|
@ -87,7 +89,7 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
|
||||||
) (*common.DerivedImageListResponse, error) {
|
) (*common.DerivedImageListResponse, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
DerivedImageList(image:"%s", requestedPage: {sortBy: ALPHABETIC_ASC}){
|
DerivedImageList(image:"%s", requestedPage: {sortBy: %s}){
|
||||||
Results{
|
Results{
|
||||||
RepoName Tag
|
RepoName Tag
|
||||||
Digest
|
Digest
|
||||||
|
@ -106,7 +108,7 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
|
||||||
IsSigned
|
IsSigned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, derivedImage)
|
}`, derivedImage, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||||
|
|
||||||
result := &common.DerivedImageListResponse{}
|
result := &common.DerivedImageListResponse{}
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
|
@ -150,7 +152,7 @@ func (service searchService) globalSearchGQL(ctx context.Context, config searchC
|
||||||
) (*common.GlobalSearch, error) {
|
) (*common.GlobalSearch, error) {
|
||||||
GQLQuery := fmt.Sprintf(`
|
GQLQuery := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
GlobalSearch(query:"%s"){
|
GlobalSearch(query:"%s", requestedPage: {sortBy: %s}){
|
||||||
Images {
|
Images {
|
||||||
RepoName
|
RepoName
|
||||||
Tag
|
Tag
|
||||||
|
@ -178,7 +180,7 @@ func (service searchService) globalSearchGQL(ctx context.Context, config searchC
|
||||||
StarCount
|
StarCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, query)
|
}`, query, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||||
|
|
||||||
result := &common.GlobalSearchResultResp{}
|
result := &common.GlobalSearchResultResp{}
|
||||||
|
|
||||||
|
@ -195,7 +197,7 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
|
||||||
) (*common.BaseImageListResponse, error) {
|
) (*common.BaseImageListResponse, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
BaseImageList(image:"%s", requestedPage: {sortBy: ALPHABETIC_ASC}){
|
BaseImageList(image:"%s", requestedPage: {sortBy: %s}){
|
||||||
Results{
|
Results{
|
||||||
RepoName Tag
|
RepoName Tag
|
||||||
Digest
|
Digest
|
||||||
|
@ -214,7 +216,7 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
|
||||||
IsSigned
|
IsSigned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`, baseImage)
|
}`, baseImage, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||||
|
|
||||||
result := &common.BaseImageListResponse{}
|
result := &common.BaseImageListResponse{}
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
|
@ -231,7 +233,7 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf
|
||||||
) (*common.ImageListResponse, error) {
|
) (*common.ImageListResponse, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
ImageList(repo: "%s", requestedPage: {sortBy: ALPHABETIC_ASC}) {
|
ImageList(repo: "%s", requestedPage: {sortBy: %s}) {
|
||||||
Results {
|
Results {
|
||||||
RepoName Tag
|
RepoName Tag
|
||||||
Digest
|
Digest
|
||||||
|
@ -250,8 +252,7 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf
|
||||||
IsSigned
|
IsSigned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`, imageName, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||||
imageName)
|
|
||||||
result := &common.ImageListResponse{}
|
result := &common.ImageListResponse{}
|
||||||
|
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
|
@ -268,7 +269,7 @@ func (service searchService) getImagesForDigestGQL(ctx context.Context, config s
|
||||||
) (*common.ImagesForDigest, error) {
|
) (*common.ImagesForDigest, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
ImageListForDigest(id: "%s", requestedPage: {sortBy: ALPHABETIC_ASC}) {
|
ImageListForDigest(id: "%s", requestedPage: {sortBy: %s}) {
|
||||||
Results {
|
Results {
|
||||||
RepoName Tag
|
RepoName Tag
|
||||||
Digest
|
Digest
|
||||||
|
@ -287,8 +288,7 @@ func (service searchService) getImagesForDigestGQL(ctx context.Context, config s
|
||||||
IsSigned
|
IsSigned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`, digest, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||||
digest)
|
|
||||||
result := &common.ImagesForDigest{}
|
result := &common.ImagesForDigest{}
|
||||||
|
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
|
@ -303,9 +303,15 @@ func (service searchService) getImagesForDigestGQL(ctx context.Context, config s
|
||||||
func (service searchService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
func (service searchService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
||||||
imageName, searchedCVE string,
|
imageName, searchedCVE string,
|
||||||
) (*cveResult, error) {
|
) (*cveResult, error) {
|
||||||
query := fmt.Sprintf(`{ CVEListForImage (image:"%s", searchedCVE:"%s")`+
|
query := fmt.Sprintf(`
|
||||||
` { Tag CVEList { Id Title Severity Description `+
|
{
|
||||||
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName, searchedCVE)
|
CVEListForImage (image:"%s", searchedCVE:"%s", requestedPage: {sortBy: %s}) {
|
||||||
|
Tag CVEList {
|
||||||
|
Id Title Severity Description
|
||||||
|
PackageList {Name InstalledVersion FixedVersion}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, imageName, searchedCVE, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||||
result := &cveResult{}
|
result := &cveResult{}
|
||||||
|
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
|
@ -314,8 +320,6 @@ func (service searchService) getCveByImageGQL(ctx context.Context, config search
|
||||||
return nil, errResult
|
return nil, errResult
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Data.CVEListForImage.CVEList = groupCVEsBySeverity(result.Data.CVEListForImage.CVEList)
|
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,7 +328,7 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
|
||||||
) (*common.ImagesForCve, error) {
|
) (*common.ImagesForCve, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
ImageListForCVE(id: "%s") {
|
ImageListForCVE(id: "%s", requestedPage: {sortBy: %s}) {
|
||||||
Results {
|
Results {
|
||||||
RepoName Tag
|
RepoName Tag
|
||||||
Digest
|
Digest
|
||||||
|
@ -344,7 +348,7 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
cveID)
|
cveID, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||||
result := &common.ImagesForCve{}
|
result := &common.ImagesForCve{}
|
||||||
|
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
|
@ -637,50 +641,6 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc
|
||||||
localWg.Wait()
|
localWg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func groupCVEsBySeverity(cveList []cve) []cve {
|
|
||||||
var (
|
|
||||||
unknown = make([]cve, 0)
|
|
||||||
none = make([]cve, 0)
|
|
||||||
high = make([]cve, 0)
|
|
||||||
med = make([]cve, 0)
|
|
||||||
low = make([]cve, 0)
|
|
||||||
critical = make([]cve, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, cve := range cveList {
|
|
||||||
switch cve.Severity {
|
|
||||||
case "NONE":
|
|
||||||
none = append(none, cve)
|
|
||||||
|
|
||||||
case "LOW":
|
|
||||||
low = append(low, cve)
|
|
||||||
|
|
||||||
case "MEDIUM":
|
|
||||||
med = append(med, cve)
|
|
||||||
|
|
||||||
case "HIGH":
|
|
||||||
high = append(high, cve)
|
|
||||||
|
|
||||||
case "CRITICAL":
|
|
||||||
critical = append(critical, cve)
|
|
||||||
|
|
||||||
default:
|
|
||||||
unknown = append(unknown, cve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vulnsCount := len(unknown) + len(none) + len(high) + len(med) + len(low) + len(critical)
|
|
||||||
vulns := make([]cve, 0, vulnsCount)
|
|
||||||
|
|
||||||
vulns = append(vulns, critical...)
|
|
||||||
vulns = append(vulns, high...)
|
|
||||||
vulns = append(vulns, med...)
|
|
||||||
vulns = append(vulns, low...)
|
|
||||||
vulns = append(vulns, none...)
|
|
||||||
vulns = append(vulns, unknown...)
|
|
||||||
|
|
||||||
return vulns
|
|
||||||
}
|
|
||||||
|
|
||||||
func isContextDone(ctx context.Context) bool {
|
func isContextDone(ctx context.Context) bool {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -1245,8 +1205,8 @@ type catalogResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
|
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
|
||||||
if !isURL(serverURL) {
|
if err := validateURL(serverURL); err != nil {
|
||||||
return "", zerr.ErrInvalidURL
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
newURL, err := url.Parse(serverURL)
|
newURL, err := url.Parse(serverURL)
|
||||||
|
@ -1375,10 +1335,16 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(config.resultWriter, "\n\nREPOSITORY NAME")
|
fmt.Fprintln(config.resultWriter, "\nREPOSITORY NAME")
|
||||||
|
|
||||||
for _, repo := range catalog.Repositories {
|
if config.sortBy == cmdflags.SortByAlphabeticAsc {
|
||||||
fmt.Fprintln(config.resultWriter, repo)
|
for i := 0; i < len(catalog.Repositories); i++ {
|
||||||
|
fmt.Fprintln(config.resultWriter, catalog.Repositories[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := len(catalog.Repositories) - 1; i >= 0; i-- {
|
||||||
|
fmt.Fprintln(config.resultWriter, catalog.Repositories[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -376,6 +375,7 @@ func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (
|
||||||
debug := defaultIfError(flags.GetBool(cmdflags.DebugFlag))
|
debug := defaultIfError(flags.GetBool(cmdflags.DebugFlag))
|
||||||
verbose := defaultIfError(flags.GetBool(cmdflags.VerboseFlag))
|
verbose := defaultIfError(flags.GetBool(cmdflags.VerboseFlag))
|
||||||
outputFormat := defaultIfError(flags.GetString(cmdflags.OutputFormatFlag))
|
outputFormat := defaultIfError(flags.GetString(cmdflags.OutputFormatFlag))
|
||||||
|
sortBy := defaultIfError(flags.GetString(cmdflags.SortByFlag))
|
||||||
|
|
||||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||||
spin.Prefix = prefix
|
spin.Prefix = prefix
|
||||||
|
@ -389,6 +389,7 @@ func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (
|
||||||
fixedFlag: fixed,
|
fixedFlag: fixed,
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
debug: debug,
|
debug: debug,
|
||||||
|
sortBy: sortBy,
|
||||||
spinner: spinnerState{spin, isSpinner},
|
spinner: spinnerState{spin, isSpinner},
|
||||||
resultWriter: cmd.OutOrStdout(),
|
resultWriter: cmd.OutOrStdout(),
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -459,9 +460,11 @@ func GetServerURLFromFlags(cmd *cobra.Command) (string, error) {
|
||||||
return "", fmt.Errorf("%w: url field from config is empty", zerr.ErrNoURLProvided)
|
return "", fmt.Errorf("%w: url field from config is empty", zerr.ErrNoURLProvided)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = url.Parse(serverURL)
|
if err := validateURL(serverURL); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
return serverURL, err
|
return serverURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadServerURLFromConfig(configName string) (string, error) {
|
func ReadServerURLFromConfig(configName string) (string, error) {
|
||||||
|
@ -479,3 +482,22 @@ func ReadServerURLFromConfig(configName string) (string, error) {
|
||||||
|
|
||||||
return urlFromConfig, nil
|
return urlFromConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSuggestionsString(suggestions []string) string {
|
||||||
|
if len(suggestions) > 0 {
|
||||||
|
return "\n\nDid you mean this?\n" + "\t" + strings.Join(suggestions, "\n\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowSuggestionsIfUnknownCommand(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return cmd.Help()
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.SuggestionsMinimumDistance = 2
|
||||||
|
suggestions := GetSuggestionsString(cmd.SuggestionsFor(args[0]))
|
||||||
|
|
||||||
|
return fmt.Errorf("%w '%s' for '%s'%s", zerr.ErrUnknownSubcommand, args[0], cmd.Name(), suggestions)
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,10 @@ func SortByAlphabeticDsc(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j i
|
||||||
|
|
||||||
func SortBySeverity(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
func SortBySeverity(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
||||||
return func(i, j int) bool {
|
return func(i, j int) bool {
|
||||||
|
if cveInfo.CompareSeverities(pageBuffer[i].Severity, pageBuffer[j].Severity) == 0 {
|
||||||
|
return pageBuffer[i].ID < pageBuffer[j].ID
|
||||||
|
}
|
||||||
|
|
||||||
return cveInfo.CompareSeverities(pageBuffer[i].Severity, pageBuffer[j].Severity) < 0
|
return cveInfo.CompareSeverities(pageBuffer[i].Severity, pageBuffer[j].Severity) < 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/opencontainers/image-spec/specs-go"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||||
|
@ -115,7 +116,10 @@ func (mb *BaseMultiarchBuilder) Build() MultiarchImage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version := 2
|
||||||
|
|
||||||
index := ispec.Index{
|
index := ispec.Index{
|
||||||
|
Versioned: specs.Versioned{SchemaVersion: version},
|
||||||
MediaType: ispec.MediaTypeImageIndex,
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
Manifests: manifests,
|
Manifests: manifests,
|
||||||
Annotations: mb.annotations,
|
Annotations: mb.annotations,
|
||||||
|
|
Loading…
Reference in a new issue