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")
|
||||
ErrFileAlreadyCommitted = errors.New("storageDriver: file already committed")
|
||||
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
|
||||
}
|
||||
|
||||
func isURL(str string) bool {
|
||||
u, err := url.Parse(str)
|
||||
func validateURL(str string) error {
|
||||
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 != ""
|
||||
} // from https://stackoverflow.com/a/55551215
|
||||
return err
|
||||
}
|
||||
|
||||
if parsedURL.Scheme == "" || parsedURL.Host == "" {
|
||||
return fmt.Errorf("%w: scheme not provided (ex: https://)", zerr.ErrInvalidURL)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type requestsPool struct {
|
||||
jobs chan *httpJob
|
||||
|
|
|
@ -98,7 +98,7 @@ func TestTLSWithAuth(t *testing.T) {
|
|||
imageCmd.SetArgs(args)
|
||||
err = imageCmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(imageBuff.String(), ShouldContainSubstring, "invalid URL format")
|
||||
So(imageBuff.String(), ShouldContainSubstring, "scheme not provided")
|
||||
|
||||
args = []string{"list", "--config", "imagetest"}
|
||||
configPath = makeConfigFile(
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
package cmdflags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
||||
const (
|
||||
URLFlag = "url"
|
||||
ConfigFlag = "config"
|
||||
|
@ -10,4 +18,144 @@ const (
|
|||
VersionFlag = "version"
|
||||
DebugFlag = "debug"
|
||||
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
|
||||
}
|
||||
|
||||
if !isURL(url) {
|
||||
return zerr.ErrInvalidURL
|
||||
if err := validateURL(url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if configNameExists(configs, configName) {
|
||||
|
|
|
@ -160,7 +160,7 @@ func TestConfigCmdMain(t *testing.T) {
|
|||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
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() {
|
||||
|
|
|
@ -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) {
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
|
|
@ -14,6 +14,7 @@ func NewCVECommand(searchService SearchService) *cobra.Command {
|
|||
Use: "cve [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`,
|
||||
RunE: ShowSuggestionsIfUnknownCommand,
|
||||
}
|
||||
|
||||
cvesCmd.SetUsageTemplate(cvesCmd.UsageTemplate() + usageFooter)
|
||||
|
|
|
@ -19,7 +19,10 @@ const (
|
|||
)
|
||||
|
||||
func NewCveForImageCommand(searchService SearchService) *cobra.Command {
|
||||
var searchedCVEID string
|
||||
var (
|
||||
searchedCVEID string
|
||||
cveListSortFlag = cmdflags.CVEListSortFlag(cmdflags.SortBySeverity)
|
||||
)
|
||||
|
||||
cveForImageCmd := &cobra.Command{
|
||||
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().Var(&cveListSortFlag, cmdflags.SortByFlag,
|
||||
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.CVEListSortOptionsStr()))
|
||||
|
||||
return cveForImageCmd
|
||||
}
|
||||
|
||||
func NewImagesByCVEIDCommand(searchService SearchService) *cobra.Command {
|
||||
var repo string
|
||||
var (
|
||||
repo string
|
||||
imageListSortFlag = cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
)
|
||||
|
||||
imagesByCVEIDCmd := &cobra.Command{
|
||||
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().Var(&imageListSortFlag, cmdflags.SortByFlag,
|
||||
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.ImageListSortOptionsStr()))
|
||||
|
||||
return imagesByCVEIDCmd
|
||||
}
|
||||
|
||||
func NewFixedTagsCommand(searchService SearchService) *cobra.Command {
|
||||
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
fixedTagsCmd := &cobra.Command{
|
||||
Use: "fixed [repo] [cveId]",
|
||||
Short: "List tags where a CVE is fixedRetryWithContext",
|
||||
Long: `List tags where a CVE is fixedRetryWithContext`,
|
||||
Short: "List tags where a CVE is fixed",
|
||||
Long: `List tags where a CVE is fixed`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ func NewImageCommand(searchService SearchService) *cobra.Command {
|
|||
Use: "image [command]",
|
||||
Short: "List images hosted on the zot registry",
|
||||
Long: `List images hosted on the zot registry`,
|
||||
RunE: ShowSuggestionsIfUnknownCommand,
|
||||
}
|
||||
|
||||
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
||||
|
|
|
@ -131,7 +131,7 @@ func TestSearchImageCmd(t *testing.T) {
|
|||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidURL)
|
||||
So(strings.Contains(err.Error(), zerr.ErrInvalidURL.Error()), ShouldBeTrue)
|
||||
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) {
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
@ -2620,6 +2694,7 @@ func getTestSearchConfig(url string, searchService SearchService) searchConfig {
|
|||
|
||||
return searchConfig{
|
||||
searchService: searchService,
|
||||
sortBy: "alpha-asc",
|
||||
servURL: url,
|
||||
user: user,
|
||||
outputFormat: outputFormat,
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
|
@ -12,7 +14,9 @@ import (
|
|||
)
|
||||
|
||||
func NewImageListCommand(searchService SearchService) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all images",
|
||||
Long: "List all images",
|
||||
|
@ -30,10 +34,18 @@ func NewImageListCommand(searchService SearchService) *cobra.Command {
|
|||
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 {
|
||||
var searchedCVEID string
|
||||
var (
|
||||
searchedCVEID string
|
||||
cveListSortFlag = cmdflags.CVEListSortFlag(cmdflags.SortBySeverity)
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
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().Var(&cveListSortFlag, cmdflags.SortByFlag,
|
||||
fmt.Sprintf("Options for sorting the output: [%s]", cmdflags.CVEListSortOptionsStr()))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewImageDerivedCommand(searchService SearchService) *cobra.Command {
|
||||
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "derived [repo-name:tag]|[repo-name@digest]",
|
||||
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
|
||||
}
|
||||
|
||||
func NewImageBaseCommand(searchService SearchService) *cobra.Command {
|
||||
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "base [repo-name:tag]|[repo-name@digest]",
|
||||
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
|
||||
}
|
||||
|
||||
func NewImageDigestCommand(searchService SearchService) *cobra.Command {
|
||||
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "digest [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
|
||||
}
|
||||
|
||||
func NewImageNameCommand(searchService SearchService) *cobra.Command {
|
||||
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "name [repo:tag]",
|
||||
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
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ func NewRepoCommand(searchService SearchService) *cobra.Command {
|
|||
Use: "repo [config-name]",
|
||||
Short: "List all repositories",
|
||||
Long: `List all repositories`,
|
||||
RunE: ShowSuggestionsIfUnknownCommand,
|
||||
}
|
||||
|
||||
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
||||
|
|
|
@ -4,10 +4,16 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
func NewListReposCommand(searchService SearchService) *cobra.Command {
|
||||
repoListSortFlag := cmdflags.RepoListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
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
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ func TestReposCommand(t *testing.T) {
|
|||
cm.StartAndWait(conf.HTTP.Port)
|
||||
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}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
@ -42,12 +47,55 @@ func TestReposCommand(t *testing.T) {
|
|||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
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")
|
||||
|
||||
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 {
|
||||
searchCmd := &cobra.Command{
|
||||
Use: "search [config-name]",
|
||||
Use: "search [command]",
|
||||
Short: "Search images and their tags",
|
||||
Long: `Search repos or images`,
|
||||
RunE: ShowSuggestionsIfUnknownCommand,
|
||||
}
|
||||
|
||||
searchCmd.SetUsageTemplate(searchCmd.UsageTemplate() + usageFooter)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "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"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
||||
func NewSearchSubjectCommand(searchService SearchService) *cobra.Command {
|
||||
imageCmd := &cobra.Command{
|
||||
imageListSortFlag := cmdflags.ImageListSortFlag(cmdflags.SortByAlphabeticAsc)
|
||||
|
||||
cmd := &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" +
|
||||
|
@ -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 {
|
||||
imageCmd := &cobra.Command{
|
||||
Use: "query",
|
||||
imageSearchSortFlag := cmdflags.ImageSearchSortFlag(cmdflags.SortByRelevance)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "query [repo]|[repo:tag]",
|
||||
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 ":"`,
|
||||
zli search query "test/repo:2.1."`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||
|
@ -70,14 +75,17 @@ func NewSearchQueryCommand(searchService SearchService) *cobra.Command {
|
|||
}
|
||||
|
||||
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 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 {
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
||||
|
@ -68,6 +69,7 @@ type searchConfig struct {
|
|||
servURL string
|
||||
user string
|
||||
outputFormat string
|
||||
sortBy string
|
||||
verifyTLS bool
|
||||
fixedFlag bool
|
||||
verbose bool
|
||||
|
@ -87,7 +89,7 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
|
|||
) (*common.DerivedImageListResponse, error) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
DerivedImageList(image:"%s", requestedPage: {sortBy: ALPHABETIC_ASC}){
|
||||
DerivedImageList(image:"%s", requestedPage: {sortBy: %s}){
|
||||
Results{
|
||||
RepoName Tag
|
||||
Digest
|
||||
|
@ -106,7 +108,7 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
|
|||
IsSigned
|
||||
}
|
||||
}
|
||||
}`, derivedImage)
|
||||
}`, derivedImage, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||
|
||||
result := &common.DerivedImageListResponse{}
|
||||
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) {
|
||||
GQLQuery := fmt.Sprintf(`
|
||||
{
|
||||
GlobalSearch(query:"%s"){
|
||||
GlobalSearch(query:"%s", requestedPage: {sortBy: %s}){
|
||||
Images {
|
||||
RepoName
|
||||
Tag
|
||||
|
@ -178,7 +180,7 @@ func (service searchService) globalSearchGQL(ctx context.Context, config searchC
|
|||
StarCount
|
||||
}
|
||||
}
|
||||
}`, query)
|
||||
}`, query, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||
|
||||
result := &common.GlobalSearchResultResp{}
|
||||
|
||||
|
@ -195,7 +197,7 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
|
|||
) (*common.BaseImageListResponse, error) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
BaseImageList(image:"%s", requestedPage: {sortBy: ALPHABETIC_ASC}){
|
||||
BaseImageList(image:"%s", requestedPage: {sortBy: %s}){
|
||||
Results{
|
||||
RepoName Tag
|
||||
Digest
|
||||
|
@ -214,7 +216,7 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
|
|||
IsSigned
|
||||
}
|
||||
}
|
||||
}`, baseImage)
|
||||
}`, baseImage, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||
|
||||
result := &common.BaseImageListResponse{}
|
||||
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) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
ImageList(repo: "%s", requestedPage: {sortBy: ALPHABETIC_ASC}) {
|
||||
ImageList(repo: "%s", requestedPage: {sortBy: %s}) {
|
||||
Results {
|
||||
RepoName Tag
|
||||
Digest
|
||||
|
@ -250,8 +252,7 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf
|
|||
IsSigned
|
||||
}
|
||||
}
|
||||
}`,
|
||||
imageName)
|
||||
}`, imageName, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||
result := &common.ImageListResponse{}
|
||||
|
||||
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) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
ImageListForDigest(id: "%s", requestedPage: {sortBy: ALPHABETIC_ASC}) {
|
||||
ImageListForDigest(id: "%s", requestedPage: {sortBy: %s}) {
|
||||
Results {
|
||||
RepoName Tag
|
||||
Digest
|
||||
|
@ -287,8 +288,7 @@ func (service searchService) getImagesForDigestGQL(ctx context.Context, config s
|
|||
IsSigned
|
||||
}
|
||||
}
|
||||
}`,
|
||||
digest)
|
||||
}`, digest, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||
result := &common.ImagesForDigest{}
|
||||
|
||||
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,
|
||||
imageName, searchedCVE string,
|
||||
) (*cveResult, error) {
|
||||
query := fmt.Sprintf(`{ CVEListForImage (image:"%s", searchedCVE:"%s")`+
|
||||
` { Tag CVEList { Id Title Severity Description `+
|
||||
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName, searchedCVE)
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
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{}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
result.Data.CVEListForImage.CVEList = groupCVEsBySeverity(result.Data.CVEListForImage.CVEList)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
@ -324,7 +328,7 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
|
|||
) (*common.ImagesForCve, error) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
ImageListForCVE(id: "%s") {
|
||||
ImageListForCVE(id: "%s", requestedPage: {sortBy: %s}) {
|
||||
Results {
|
||||
RepoName Tag
|
||||
Digest
|
||||
|
@ -344,7 +348,7 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
|
|||
}
|
||||
}
|
||||
}`,
|
||||
cveID)
|
||||
cveID, cmdflags.Flag2SortCriteria(config.sortBy))
|
||||
result := &common.ImagesForCve{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
|
@ -637,50 +641,6 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc
|
|||
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 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
@ -1245,8 +1205,8 @@ type catalogResponse struct {
|
|||
}
|
||||
|
||||
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
|
||||
if !isURL(serverURL) {
|
||||
return "", zerr.ErrInvalidURL
|
||||
if err := validateURL(serverURL); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newURL, err := url.Parse(serverURL)
|
||||
|
@ -1375,10 +1335,16 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig,
|
|||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(config.resultWriter, "\n\nREPOSITORY NAME")
|
||||
fmt.Fprintln(config.resultWriter, "\nREPOSITORY NAME")
|
||||
|
||||
for _, repo := range catalog.Repositories {
|
||||
fmt.Fprintln(config.resultWriter, repo)
|
||||
if config.sortBy == cmdflags.SortByAlphabeticAsc {
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
@ -376,6 +375,7 @@ func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (
|
|||
debug := defaultIfError(flags.GetBool(cmdflags.DebugFlag))
|
||||
verbose := defaultIfError(flags.GetBool(cmdflags.VerboseFlag))
|
||||
outputFormat := defaultIfError(flags.GetString(cmdflags.OutputFormatFlag))
|
||||
sortBy := defaultIfError(flags.GetString(cmdflags.SortByFlag))
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||
spin.Prefix = prefix
|
||||
|
@ -389,6 +389,7 @@ func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (
|
|||
fixedFlag: fixed,
|
||||
verbose: verbose,
|
||||
debug: debug,
|
||||
sortBy: sortBy,
|
||||
spinner: spinnerState{spin, isSpinner},
|
||||
resultWriter: cmd.OutOrStdout(),
|
||||
}, nil
|
||||
|
@ -459,9 +460,11 @@ func GetServerURLFromFlags(cmd *cobra.Command) (string, error) {
|
|||
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) {
|
||||
|
@ -479,3 +482,22 @@ func ReadServerURLFromConfig(configName string) (string, error) {
|
|||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
|
@ -115,7 +116,10 @@ func (mb *BaseMultiarchBuilder) Build() MultiarchImage {
|
|||
}
|
||||
}
|
||||
|
||||
version := 2
|
||||
|
||||
index := ispec.Index{
|
||||
Versioned: specs.Versioned{SchemaVersion: version},
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
Manifests: manifests,
|
||||
Annotations: mb.annotations,
|
||||
|
|
Loading…
Reference in a new issue