mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
refactor(cli): remove old cli commands (#1756)
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
parent
18e591f52a
commit
7b1e24c99e
31 changed files with 1455 additions and 4118 deletions
|
@ -159,4 +159,5 @@ var (
|
|||
ErrFileAlreadyCancelled = errors.New("storageDriver: file already cancelled")
|
||||
ErrFileAlreadyClosed = errors.New("storageDriver: file already closed")
|
||||
ErrFileAlreadyCommitted = errors.New("storageDriver: file already committed")
|
||||
ErrInvalidOutputFormat = errors.New("cli: invalid output format")
|
||||
)
|
||||
|
|
|
@ -8,9 +8,7 @@ import "github.com/spf13/cobra"
|
|||
func enableCli(rootCmd *cobra.Command) {
|
||||
rootCmd.AddCommand(NewConfigCommand())
|
||||
rootCmd.AddCommand(NewImageCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewImagesCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewCveCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewCVESCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewCVECommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewRepoCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewSearchCommand(NewSearchService()))
|
||||
}
|
||||
|
|
|
@ -207,8 +207,8 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) {
|
|||
defer p.wtgrp.Done()
|
||||
|
||||
// Check manifest media type
|
||||
header, err := makeHEADRequest(ctx, job.url, job.username, job.password, *job.config.verifyTLS,
|
||||
*job.config.debug)
|
||||
header, err := makeHEADRequest(ctx, job.url, job.username, job.password, job.config.verifyTLS,
|
||||
job.config.debug)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
|
@ -216,7 +216,7 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) {
|
|||
p.outputCh <- stringResult{"", err}
|
||||
}
|
||||
|
||||
verbose := *job.config.verbose
|
||||
verbose := job.config.verbose
|
||||
|
||||
switch header.Get("Content-Type") {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
|
@ -231,7 +231,7 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) {
|
|||
}
|
||||
platformStr := getPlatformStr(image.Manifests[0].Platform)
|
||||
|
||||
str, err := image.string(*job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose)
|
||||
str, err := image.string(job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
|
@ -259,7 +259,7 @@ func (p *requestsPool) doJob(ctx context.Context, job *httpJob) {
|
|||
|
||||
platformStr := getPlatformStr(image.Manifests[0].Platform)
|
||||
|
||||
str, err := image.string(*job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose)
|
||||
str, err := image.string(job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr), verbose)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
|
@ -283,7 +283,7 @@ func fetchImageIndexStruct(ctx context.Context, job *httpJob) (*imageStruct, err
|
|||
var indexContent ispec.Index
|
||||
|
||||
header, err := makeGETRequest(ctx, job.url, job.username, job.password,
|
||||
*job.config.verifyTLS, *job.config.debug, &indexContent, job.config.resultWriter)
|
||||
job.config.verifyTLS, job.config.debug, &indexContent, job.config.resultWriter)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return nil, context.Canceled
|
||||
|
@ -371,10 +371,10 @@ func fetchManifestStruct(ctx context.Context, repo, manifestReference string, se
|
|||
manifestResp := ispec.Manifest{}
|
||||
|
||||
URL := fmt.Sprintf("%s/v2/%s/manifests/%s",
|
||||
*searchConf.servURL, repo, manifestReference)
|
||||
searchConf.servURL, repo, manifestReference)
|
||||
|
||||
header, err := makeGETRequest(ctx, URL, username, password,
|
||||
*searchConf.verifyTLS, *searchConf.debug, &manifestResp, searchConf.resultWriter)
|
||||
searchConf.verifyTLS, searchConf.debug, &manifestResp, searchConf.resultWriter)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return common.ManifestSummary{}, context.Canceled
|
||||
|
@ -460,10 +460,10 @@ func fetchConfig(ctx context.Context, repo, configDigest string, searchConf sear
|
|||
configContent := ispec.Image{}
|
||||
|
||||
URL := fmt.Sprintf("%s/v2/%s/blobs/%s",
|
||||
*searchConf.servURL, repo, configDigest)
|
||||
searchConf.servURL, repo, configDigest)
|
||||
|
||||
_, err := makeGETRequest(ctx, URL, username, password,
|
||||
*searchConf.verifyTLS, *searchConf.debug, &configContent, searchConf.resultWriter)
|
||||
searchConf.verifyTLS, searchConf.debug, &configContent, searchConf.resultWriter)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return ispec.Image{}, context.Canceled
|
||||
|
@ -481,10 +481,10 @@ func isNotationSigned(ctx context.Context, repo, digestStr string, searchConf se
|
|||
var referrers ispec.Index
|
||||
|
||||
URL := fmt.Sprintf("%s/v2/%s/referrers/%s?artifactType=%s",
|
||||
*searchConf.servURL, repo, digestStr, common.ArtifactTypeNotation)
|
||||
searchConf.servURL, repo, digestStr, common.ArtifactTypeNotation)
|
||||
|
||||
_, err := makeGETRequest(ctx, URL, username, password,
|
||||
*searchConf.verifyTLS, *searchConf.debug, &referrers, searchConf.resultWriter)
|
||||
searchConf.verifyTLS, searchConf.debug, &referrers, searchConf.resultWriter)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -502,10 +502,10 @@ func isCosignSigned(ctx context.Context, repo, digestStr string, searchConf sear
|
|||
var result interface{}
|
||||
cosignTag := strings.Replace(digestStr, ":", "-", 1) + "." + remote.SignatureTagSuffix
|
||||
|
||||
URL := fmt.Sprintf("%s/v2/%s/manifests/%s", *searchConf.servURL, repo, cosignTag)
|
||||
URL := fmt.Sprintf("%s/v2/%s/manifests/%s", searchConf.servURL, repo, cosignTag)
|
||||
|
||||
_, err := makeGETRequest(ctx, URL, username, password, *searchConf.verifyTLS,
|
||||
*searchConf.debug, &result, searchConf.resultWriter)
|
||||
_, err := makeGETRequest(ctx, URL, username, password, searchConf.verifyTLS,
|
||||
searchConf.debug, &result, searchConf.resultWriter)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func TestElevatedPrivilegesTLSNewControllerPrivilegedCert(t *testing.T) {
|
|||
BaseSecureURL2, constants.RoutePrefix, constants.ExtCatalogPrefix))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
args := []string{"imagetest"}
|
||||
args := []string{"list", "--config", "imagetest"}
|
||||
imageCmd := NewImageCommand(new(searchService))
|
||||
imageBuff := bytes.NewBufferString("")
|
||||
imageCmd.SetOut(imageBuff)
|
||||
|
|
|
@ -90,7 +90,7 @@ func TestTLSWithAuth(t *testing.T) {
|
|||
|
||||
defer os.RemoveAll(destCertsDir)
|
||||
|
||||
args := []string{"imagetest", "--name", "dummyImageName", "--url", HOST1}
|
||||
args := []string{"name", "dummyImageName", "--url", HOST1}
|
||||
imageCmd := NewImageCommand(new(searchService))
|
||||
imageBuff := bytes.NewBufferString("")
|
||||
imageCmd.SetOut(imageBuff)
|
||||
|
@ -100,7 +100,7 @@ func TestTLSWithAuth(t *testing.T) {
|
|||
So(err, ShouldNotBeNil)
|
||||
So(imageBuff.String(), ShouldContainSubstring, "invalid URL format")
|
||||
|
||||
args = []string{"imagetest"}
|
||||
args = []string{"list", "--config", "imagetest"}
|
||||
configPath = makeConfigFile(
|
||||
fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`,
|
||||
BaseSecureURL1, constants.RoutePrefix, constants.ExtCatalogPrefix))
|
||||
|
@ -115,7 +115,7 @@ func TestTLSWithAuth(t *testing.T) {
|
|||
So(imageBuff.String(), ShouldContainSubstring, "check credentials")
|
||||
|
||||
user := fmt.Sprintf("%s:%s", username, passphrase)
|
||||
args = []string{"imagetest", "-u", user}
|
||||
args = []string{"-u", user, "--config", "imagetest"}
|
||||
configPath = makeConfigFile(
|
||||
fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s%s%s","showspinner":false}]}`,
|
||||
BaseSecureURL1, constants.RoutePrefix, constants.ExtCatalogPrefix))
|
||||
|
@ -170,7 +170,7 @@ func TestTLSWithoutAuth(t *testing.T) {
|
|||
test.CopyTestFiles(sourceCertsDir, destCertsDir)
|
||||
defer os.RemoveAll(destCertsDir)
|
||||
|
||||
args := []string{"imagetest"}
|
||||
args := []string{"list", "--config", "imagetest"}
|
||||
imageCmd := NewImageCommand(new(searchService))
|
||||
imageBuff := bytes.NewBufferString("")
|
||||
imageCmd.SetOut(imageBuff)
|
||||
|
@ -211,7 +211,7 @@ func TestTLSBadCerts(t *testing.T) {
|
|||
BaseSecureURL3, constants.RoutePrefix, constants.ExtCatalogPrefix))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
args := []string{"imagetest"}
|
||||
args := []string{"list", "--config", "imagetest"}
|
||||
imageCmd := NewImageCommand(new(searchService))
|
||||
imageBuff := bytes.NewBufferString("")
|
||||
imageCmd.SetOut(imageBuff)
|
||||
|
|
|
@ -26,12 +26,12 @@ func getDefaultSearchConf(baseURL string) searchConfig {
|
|||
outputFormat := "text"
|
||||
|
||||
return searchConfig{
|
||||
servURL: &baseURL,
|
||||
servURL: baseURL,
|
||||
resultWriter: io.Discard,
|
||||
verifyTLS: &verifyTLS,
|
||||
debug: &debug,
|
||||
verbose: &verbose,
|
||||
outputFormat: &outputFormat,
|
||||
verifyTLS: verifyTLS,
|
||||
debug: debug,
|
||||
verbose: verbose,
|
||||
outputFormat: outputFormat,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
cveDBRetryInterval = 3
|
||||
)
|
||||
|
||||
func NewCveCommand(searchService SearchService) *cobra.Command {
|
||||
searchCveParams := make(map[string]*string)
|
||||
|
||||
var servURL, user, outputFormat string
|
||||
|
||||
var isSpinner, verifyTLS, fixedFlag, verbose, debug bool
|
||||
|
||||
cveCmd := &cobra.Command{
|
||||
Use: "cve [config-name]",
|
||||
Short: "DEPRECATED (see cves)",
|
||||
Long: `DEPRECATED (see cves)! List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
var err error
|
||||
isSpinner, err = parseBooleanConfig(configPath, args[0], showspinnerConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
verifyTLS, err = parseBooleanConfig(configPath, args[0], verifyTLSConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||
spin.Prefix = fmt.Sprintf("Fetching from %s..", servURL)
|
||||
spin.Suffix = "\n\b"
|
||||
|
||||
verbose = false
|
||||
|
||||
searchConfig := searchConfig{
|
||||
params: searchCveParams,
|
||||
searchService: searchService,
|
||||
servURL: &servURL,
|
||||
user: &user,
|
||||
outputFormat: &outputFormat,
|
||||
fixedFlag: &fixedFlag,
|
||||
verifyTLS: &verifyTLS,
|
||||
verbose: &verbose,
|
||||
debug: &debug,
|
||||
resultWriter: cmd.OutOrStdout(),
|
||||
spinner: spinnerState{spin, isSpinner},
|
||||
}
|
||||
|
||||
err = searchCve(searchConfig)
|
||||
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
vars := cveFlagVariables{
|
||||
searchCveParams: searchCveParams,
|
||||
servURL: &servURL,
|
||||
user: &user,
|
||||
outputFormat: &outputFormat,
|
||||
fixedFlag: &fixedFlag,
|
||||
debug: &debug,
|
||||
}
|
||||
|
||||
setupCveFlags(cveCmd, vars)
|
||||
|
||||
return cveCmd
|
||||
}
|
||||
|
||||
func setupCveFlags(cveCmd *cobra.Command, variables cveFlagVariables) {
|
||||
variables.searchCveParams["imageName"] = cveCmd.Flags().StringP("image", "I", "", "List CVEs by IMAGENAME[:TAG]")
|
||||
variables.searchCveParams["cveID"] = cveCmd.Flags().StringP("cve-id", "i", "", "List images affected by a CVE")
|
||||
variables.searchCveParams["searchedCVE"] = cveCmd.Flags().StringP("search", "s", "", "Search specific CVEs by name/id")
|
||||
|
||||
cveCmd.Flags().StringVar(variables.servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
||||
cveCmd.Flags().StringVarP(variables.user, "user", "u", "", `User Credentials of `+
|
||||
`zot server in USERNAME:PASSWORD format`)
|
||||
cveCmd.Flags().StringVarP(variables.outputFormat, "output", "o", "", "Specify output format [text/json/yaml]."+
|
||||
" JSON and YAML format return all info for CVEs")
|
||||
|
||||
cveCmd.Flags().BoolVar(variables.fixedFlag, "fixed", false, "List tags which have fixed a CVE")
|
||||
cveCmd.Flags().BoolVar(variables.debug, "debug", false, "Show debug output")
|
||||
}
|
||||
|
||||
type cveFlagVariables struct {
|
||||
searchCveParams map[string]*string
|
||||
servURL *string
|
||||
user *string
|
||||
outputFormat *string
|
||||
fixedFlag *bool
|
||||
debug *bool
|
||||
}
|
||||
|
||||
func searchCve(searchConfig searchConfig) error {
|
||||
var searchers []searcher
|
||||
|
||||
if checkExtEndPoint(searchConfig) {
|
||||
searchers = getCveSearchersGQL()
|
||||
} else {
|
||||
searchers = getCveSearchers()
|
||||
}
|
||||
|
||||
for _, searcher := range searchers {
|
||||
// there can be CVE DB readiness issues on the server side
|
||||
// we need a retry mechanism for that specific type of errors
|
||||
maxAttempts := 20
|
||||
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
found, err := searcher.search(searchConfig)
|
||||
if !found {
|
||||
// searcher does not support this searchConfig
|
||||
// exit the attempts loop and try a different searcher
|
||||
break
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// searcher matcher search config and results are already printed
|
||||
return nil
|
||||
}
|
||||
|
||||
if i+1 >= maxAttempts {
|
||||
// searcher matches search config but there are errors
|
||||
// this is the last attempt and we cannot retry
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
|
||||
// searches matches search config but CVE DB is not ready server side
|
||||
// wait and retry a few more times
|
||||
fmt.Fprintln(searchConfig.resultWriter,
|
||||
"[warning] CVE DB is not ready [", i, "] - retry in ", cveDBRetryInterval, " seconds")
|
||||
time.Sleep(cveDBRetryInterval * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// an unrecoverable error occurred
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return zerr.ErrInvalidFlagsCombination
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -9,15 +9,21 @@ import (
|
|||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
func NewCVESCommand(searchService SearchService) *cobra.Command {
|
||||
func NewCVECommand(searchService SearchService) *cobra.Command {
|
||||
cvesCmd := &cobra.Command{
|
||||
Use: "cves [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`,
|
||||
}
|
||||
|
||||
cvesCmd.SetUsageTemplate(cvesCmd.UsageTemplate() + usageFooter)
|
||||
|
||||
cvesCmd.PersistentFlags().String(cmdflags.URLFlag, "",
|
||||
"Specify zot server URL if config-name is not mentioned")
|
||||
cvesCmd.PersistentFlags().String(cmdflags.ConfigFlag, "",
|
||||
"Specify the registry configuration to use for connection")
|
||||
cvesCmd.PersistentFlags().StringP(cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
cvesCmd.PersistentFlags().StringP(cmdflags.OutputFormatFlag, "f", "", "Specify output format [text/json/yaml]")
|
||||
cvesCmd.PersistentFlags().Bool(cmdflags.VerboseFlag, false, "Show verbose output")
|
||||
cvesCmd.PersistentFlags().Bool(cmdflags.DebugFlag, false, "Show debug output")
|
||||
|
|
|
@ -22,7 +22,7 @@ func NewCveForImageCommand(searchService SearchService) *cobra.Command {
|
|||
var searchedCVEID string
|
||||
|
||||
cveForImageCmd := &cobra.Command{
|
||||
Use: "image [repo:tag]|[repo@digest]",
|
||||
Use: "list [repo:tag]|[repo@digest]",
|
||||
Short: "List CVEs by REPO:TAG or REPO@DIGEST",
|
||||
Long: `List CVEs by REPO:TAG or REPO@DIGEST`,
|
||||
Args: OneImageWithRefArg,
|
||||
|
@ -52,7 +52,7 @@ func NewImagesByCVEIDCommand(searchService SearchService) *cobra.Command {
|
|||
var repo string
|
||||
|
||||
imagesByCVEIDCmd := &cobra.Command{
|
||||
Use: "cveid [cveId]",
|
||||
Use: "affected [cveId]",
|
||||
Short: "List images affected by a CVE",
|
||||
Long: `List images affected by a CVE`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
@ -42,16 +42,6 @@ type typeField struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func containsGQLQuery(queryList []field, query string) bool {
|
||||
for _, q := range queryList {
|
||||
if q.Name == query {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func containsGQLQueryWithParams(queryList []field, serverGQLTypesList []typeInfo, requiredQueries ...GQLQuery) error {
|
||||
serverGQLTypes := map[string][]typeField{}
|
||||
|
||||
|
@ -100,73 +90,11 @@ func haveSameArgs(query field, reqQuery GQLQuery) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func checkExtEndPoint(config searchConfig) bool {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
ctx := context.Background()
|
||||
|
||||
discoverEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s",
|
||||
constants.RoutePrefix, constants.ExtOciDiscoverPrefix))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
discoverResponse := &distext.ExtensionList{}
|
||||
|
||||
_, err = makeGETRequest(ctx, discoverEndPoint, username, password, *config.verifyTLS,
|
||||
*config.debug, &discoverResponse, config.resultWriter)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
searchEnabled := false
|
||||
|
||||
for _, extension := range discoverResponse.Extensions {
|
||||
if extension.Name == constants.BaseExtension {
|
||||
for _, endpoint := range extension.Endpoints {
|
||||
if endpoint == constants.FullSearchPrefix {
|
||||
searchEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !searchEnabled {
|
||||
return false
|
||||
}
|
||||
|
||||
searchEndPoint, _ := combineServerAndEndpointURL(*config.servURL, constants.FullSearchPrefix)
|
||||
|
||||
query := `
|
||||
{
|
||||
__schema() {
|
||||
queryType {
|
||||
fields {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
queryResponse := &schemaList{}
|
||||
|
||||
err = makeGraphQLRequest(ctx, searchEndPoint, query, username, password, *config.verifyTLS,
|
||||
*config.debug, queryResponse, config.resultWriter)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err = checkResultGraphQLQuery(ctx, err, queryResponse.Errors); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return containsGQLQuery(queryResponse.Data.Schema.QueryType.Fields, "ImageList")
|
||||
}
|
||||
|
||||
func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx := context.Background()
|
||||
|
||||
discoverEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s",
|
||||
discoverEndPoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("%s%s",
|
||||
constants.RoutePrefix, constants.ExtOciDiscoverPrefix))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -174,8 +102,8 @@ func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) err
|
|||
|
||||
discoverResponse := &distext.ExtensionList{}
|
||||
|
||||
_, err = makeGETRequest(ctx, discoverEndPoint, username, password, *config.verifyTLS,
|
||||
*config.debug, &discoverResponse, config.resultWriter)
|
||||
_, err = makeGETRequest(ctx, discoverEndPoint, username, password, config.verifyTLS,
|
||||
config.debug, &discoverResponse, config.resultWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -196,7 +124,7 @@ func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) err
|
|||
return fmt.Errorf("%w: search extension gql endpoints not found", zerr.ErrExtensionNotEnabled)
|
||||
}
|
||||
|
||||
searchEndPoint, _ := combineServerAndEndpointURL(*config.servURL, constants.FullSearchPrefix)
|
||||
searchEndPoint, _ := combineServerAndEndpointURL(config.servURL, constants.FullSearchPrefix)
|
||||
|
||||
schemaQuery := `
|
||||
{
|
||||
|
@ -225,8 +153,8 @@ func CheckExtEndPointQuery(config searchConfig, requiredQueries ...GQLQuery) err
|
|||
|
||||
queryResponse := &schemaList{}
|
||||
|
||||
err = makeGraphQLRequest(ctx, searchEndPoint, schemaQuery, username, password, *config.verifyTLS,
|
||||
*config.debug, queryResponse, config.resultWriter)
|
||||
err = makeGraphQLRequest(ctx, searchEndPoint, schemaQuery, username, password, config.verifyTLS,
|
||||
config.debug, queryResponse, config.resultWriter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gql query failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -37,10 +37,10 @@ func TestGQLQueries(t *testing.T) {
|
|||
defer cm.StopServer()
|
||||
|
||||
searchConfig := searchConfig{
|
||||
servURL: &baseURL,
|
||||
user: ref(""),
|
||||
verifyTLS: ref(false),
|
||||
debug: ref(false),
|
||||
servURL: baseURL,
|
||||
user: "",
|
||||
verifyTLS: false,
|
||||
debug: false,
|
||||
resultWriter: io.Discard,
|
||||
}
|
||||
|
||||
|
|
|
@ -4,103 +4,47 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
const (
|
||||
spinnerDuration = 150 * time.Millisecond
|
||||
usageFooter = `
|
||||
Run 'zli config -h' for details on [config-name] argument
|
||||
`
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
func NewImageCommand(searchService SearchService) *cobra.Command {
|
||||
searchImageParams := make(map[string]*string)
|
||||
|
||||
var servURL, user, outputFormat string
|
||||
|
||||
var isSpinner, verifyTLS, verbose, debug bool
|
||||
|
||||
imageCmd := &cobra.Command{
|
||||
Use: "image [config-name]",
|
||||
Short: "DEPRECATED (see images)",
|
||||
Long: `DEPRECATED (see images)! List images hosted on the zot registry`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
var err error
|
||||
isSpinner, err = parseBooleanConfig(configPath, args[0], showspinnerConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
verifyTLS, err = parseBooleanConfig(configPath, args[0], verifyTLSConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||
spin.Prefix = prefix
|
||||
|
||||
searchConfig := searchConfig{
|
||||
params: searchImageParams,
|
||||
searchService: searchService,
|
||||
servURL: &servURL,
|
||||
user: &user,
|
||||
outputFormat: &outputFormat,
|
||||
verbose: &verbose,
|
||||
debug: &debug,
|
||||
spinner: spinnerState{spin, isSpinner},
|
||||
verifyTLS: &verifyTLS,
|
||||
resultWriter: cmd.OutOrStdout(),
|
||||
}
|
||||
|
||||
err = searchImage(searchConfig)
|
||||
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Use: "image [command]",
|
||||
Short: "List images hosted on the zot registry",
|
||||
Long: `List images hosted on the zot registry`,
|
||||
}
|
||||
|
||||
setupImageFlags(imageCmd, searchImageParams, &servURL, &user, &outputFormat, &verbose, &debug)
|
||||
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
||||
|
||||
imageCmd.PersistentFlags().String(cmdflags.URLFlag, "",
|
||||
"Specify zot server URL if config-name is not mentioned")
|
||||
imageCmd.PersistentFlags().String(cmdflags.ConfigFlag, "",
|
||||
"Specify the registry configuration to use for connection")
|
||||
imageCmd.PersistentFlags().StringP(cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
imageCmd.PersistentFlags().StringP(cmdflags.OutputFormatFlag, "f", "", "Specify output format [text/json/yaml]")
|
||||
imageCmd.PersistentFlags().Bool(cmdflags.VerboseFlag, false, "Show verbose output")
|
||||
imageCmd.PersistentFlags().Bool(cmdflags.DebugFlag, false, "Show debug output")
|
||||
|
||||
imageCmd.AddCommand(NewImageListCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageCVEListCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageBaseCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageDerivedCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageDigestCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageNameCommand(searchService))
|
||||
|
||||
return imageCmd
|
||||
}
|
||||
|
||||
|
@ -117,52 +61,3 @@ func parseBooleanConfig(configPath, configName, configParam string) (bool, error
|
|||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func setupImageFlags(imageCmd *cobra.Command, searchImageParams map[string]*string,
|
||||
servURL, user, outputFormat *string, verbose *bool, debug *bool,
|
||||
) {
|
||||
searchImageParams["imageName"] = imageCmd.Flags().StringP("name", "n", "", "List image details by name")
|
||||
searchImageParams["digest"] = imageCmd.Flags().StringP("digest", "d", "",
|
||||
"List images containing a specific manifest, config, or layer digest")
|
||||
searchImageParams["derivedImage"] = imageCmd.Flags().StringP("derived-images", "D", "",
|
||||
"List images that are derived from given image")
|
||||
searchImageParams["baseImage"] = imageCmd.Flags().StringP("base-images", "b", "",
|
||||
"List images that are base for the given image")
|
||||
|
||||
imageCmd.PersistentFlags().StringVar(servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
|
||||
imageCmd.PersistentFlags().StringVarP(user, "user", "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
imageCmd.PersistentFlags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
|
||||
imageCmd.PersistentFlags().BoolVar(verbose, "verbose", false, "Show verbose output")
|
||||
imageCmd.PersistentFlags().BoolVar(debug, "debug", false, "Show debug output")
|
||||
}
|
||||
|
||||
func searchImage(searchConfig searchConfig) error {
|
||||
var searchers []searcher
|
||||
|
||||
if checkExtEndPoint(searchConfig) {
|
||||
searchers = getImageSearchersGQL()
|
||||
} else {
|
||||
searchers = getImageSearchers()
|
||||
}
|
||||
|
||||
for _, searcher := range searchers {
|
||||
found, err := searcher.search(searchConfig)
|
||||
if found {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return zerr.ErrInvalidFlagsCombination
|
||||
}
|
||||
|
||||
const (
|
||||
spinnerDuration = 150 * time.Millisecond
|
||||
usageFooter = `
|
||||
Run 'zli config -h' for details on [config-name] argument
|
||||
`
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,11 +4,6 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
|
@ -41,7 +36,7 @@ func NewImageCVEListCommand(searchService SearchService) *cobra.Command {
|
|||
var searchedCVEID string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "cve [repo-name:tag][repo-name@digest]",
|
||||
Use: "cve [repo]|[repo-name:tag]|[repo-name@digest]",
|
||||
Short: "List all CVE's of the image",
|
||||
Long: "List all CVE's of the image",
|
||||
Args: OneImageWithRefArg,
|
||||
|
@ -68,7 +63,7 @@ func NewImageCVEListCommand(searchService SearchService) *cobra.Command {
|
|||
|
||||
func NewImageDerivedCommand(searchService SearchService) *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",
|
||||
Long: "List images that are derived from given image",
|
||||
Args: OneImageWithRefArg,
|
||||
|
@ -91,7 +86,7 @@ func NewImageDerivedCommand(searchService SearchService) *cobra.Command {
|
|||
|
||||
func NewImageBaseCommand(searchService SearchService) *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",
|
||||
Long: "List images that are base for the given image",
|
||||
Args: OneImageWithRefArg,
|
||||
|
@ -117,7 +112,9 @@ func NewImageDigestCommand(searchService SearchService) *cobra.Command {
|
|||
Use: "digest [digest]",
|
||||
Short: "List images that contain a blob(manifest, config or layer) with the given digest",
|
||||
Long: "List images that contain a blob(manifest, config or layer) with the given digest",
|
||||
Args: OneDigestArg,
|
||||
Example: `zli image digest 8a1930f0
|
||||
zli image digest sha256:8a1930f0...`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
searchConfig, err := GetSearchConfigFromFlags(cmd, searchService)
|
||||
if err != nil {
|
||||
|
@ -169,116 +166,3 @@ func NewImageNameCommand(searchService SearchService) *cobra.Command {
|
|||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (searchConfig, error) {
|
||||
serverURL, err := GetServerURLFromFlags(cmd)
|
||||
if err != nil {
|
||||
return searchConfig{}, err
|
||||
}
|
||||
|
||||
isSpinner, verifyTLS := GetCliConfigOptions(cmd)
|
||||
|
||||
flags := cmd.Flags()
|
||||
user := defaultIfError(flags.GetString(cmdflags.UserFlag))
|
||||
fixed := defaultIfError(flags.GetBool(cmdflags.FixedFlag))
|
||||
debug := defaultIfError(flags.GetBool(cmdflags.DebugFlag))
|
||||
verbose := defaultIfError(flags.GetBool(cmdflags.VerboseFlag))
|
||||
outputFormat := defaultIfError(flags.GetString(cmdflags.OutputFormatFlag))
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||
spin.Prefix = prefix
|
||||
|
||||
return searchConfig{
|
||||
params: map[string]*string{},
|
||||
searchService: searchService,
|
||||
servURL: &serverURL,
|
||||
user: &user,
|
||||
outputFormat: &outputFormat,
|
||||
verifyTLS: &verifyTLS,
|
||||
fixedFlag: &fixed,
|
||||
verbose: &verbose,
|
||||
debug: &debug,
|
||||
spinner: spinnerState{spin, isSpinner},
|
||||
resultWriter: cmd.OutOrStdout(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func defaultIfError[T any](out T, err error) T {
|
||||
var defaultVal T
|
||||
|
||||
if err != nil {
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func GetCliConfigOptions(cmd *cobra.Command) (bool, bool) {
|
||||
configName, err := cmd.Flags().GetString(cmdflags.ConfigFlag)
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
configDir := path.Join(home, "/.zot")
|
||||
|
||||
isSpinner, err := parseBooleanConfig(configDir, configName, showspinnerConfig)
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
verifyTLS, err := parseBooleanConfig(configDir, configName, verifyTLSConfig)
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
return isSpinner, verifyTLS
|
||||
}
|
||||
|
||||
func GetServerURLFromFlags(cmd *cobra.Command) (string, error) {
|
||||
serverURL, err := cmd.Flags().GetString(cmdflags.URLFlag)
|
||||
if err == nil && serverURL != "" {
|
||||
return serverURL, nil
|
||||
}
|
||||
|
||||
configName, err := cmd.Flags().GetString(cmdflags.ConfigFlag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if configName == "" {
|
||||
return "", fmt.Errorf("%w: specify either '--%s' or '--%s' flags", zerr.ErrNoURLProvided, cmdflags.URLFlag,
|
||||
cmdflags.ConfigFlag)
|
||||
}
|
||||
|
||||
serverURL, err = ReadServerURLFromConfig(configName)
|
||||
if err != nil {
|
||||
return serverURL, fmt.Errorf("reading url from config failed: %w", err)
|
||||
}
|
||||
|
||||
if serverURL == "" {
|
||||
return "", fmt.Errorf("%w: url field from config is empty", zerr.ErrNoURLProvided)
|
||||
}
|
||||
|
||||
return serverURL, nil
|
||||
}
|
||||
|
||||
func ReadServerURLFromConfig(configName string) (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
configDir := path.Join(home, "/.zot")
|
||||
|
||||
urlFromConfig, err := getConfigValue(configDir, configName, "url")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return urlFromConfig, nil
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
func NewImagesCommand(searchService SearchService) *cobra.Command {
|
||||
imageCmd := &cobra.Command{
|
||||
Use: "images [command]",
|
||||
Short: "List images hosted on the zot registry",
|
||||
Long: `List images hosted on the zot registry`,
|
||||
}
|
||||
|
||||
imageCmd.SetUsageTemplate(imageCmd.UsageTemplate() + usageFooter)
|
||||
|
||||
imageCmd.PersistentFlags().StringP(cmdflags.OutputFormatFlag, "f", "", "Specify output format [text/json/yaml]")
|
||||
imageCmd.PersistentFlags().Bool(cmdflags.VerboseFlag, false, "Show verbose output")
|
||||
imageCmd.PersistentFlags().Bool(cmdflags.DebugFlag, false, "Show debug output")
|
||||
|
||||
imageCmd.AddCommand(NewImageListCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageCVEListCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageBaseCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageDerivedCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageDigestCommand(searchService))
|
||||
imageCmd.AddCommand(NewImageNameCommand(searchService))
|
||||
|
||||
return imageCmd
|
||||
}
|
34
pkg/cli/repo_cmd.go
Normal file
34
pkg/cli/repo_cmd.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
const prefix = "Searching... "
|
||||
|
||||
func NewRepoCommand(searchService SearchService) *cobra.Command {
|
||||
repoCmd := &cobra.Command{
|
||||
Use: "repo [config-name]",
|
||||
Short: "List all repositories",
|
||||
Long: `List all repositories`,
|
||||
}
|
||||
|
||||
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
||||
|
||||
repoCmd.PersistentFlags().String(cmdflags.URLFlag, "",
|
||||
"Specify zot server URL if config-name is not mentioned")
|
||||
repoCmd.PersistentFlags().String(cmdflags.ConfigFlag, "",
|
||||
"Specify the registry configuration to use for connection")
|
||||
repoCmd.PersistentFlags().StringP(cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
repoCmd.PersistentFlags().Bool(cmdflags.DebugFlag, false, "Show debug output")
|
||||
|
||||
repoCmd.AddCommand(NewListReposCommand(searchService))
|
||||
|
||||
return repoCmd
|
||||
}
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
package cli
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListReposCommand(searchService SearchService) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
|
@ -15,7 +15,6 @@ import (
|
|||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
||||
|
@ -37,9 +36,8 @@ func TestReposCommand(t *testing.T) {
|
|||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
args := []string{"list"}
|
||||
args := []string{"list", "--config", "repostest"}
|
||||
cmd := NewRepoCommand(mockService{})
|
||||
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "repostest", "")
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
|
@ -1,116 +0,0 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
const prefix = "Searching... "
|
||||
|
||||
func NewRepoCommand(searchService SearchService) *cobra.Command {
|
||||
var servURL, user, outputFormat string
|
||||
|
||||
var isSpinner, verifyTLS, verbose, debug bool
|
||||
|
||||
repoCmd := &cobra.Command{
|
||||
Use: "repos [config-name]",
|
||||
Short: "List all repositories",
|
||||
Long: `List all repositories`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
var err error
|
||||
isSpinner, err = parseBooleanConfig(configPath, args[0], showspinnerConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
verifyTLS, err = parseBooleanConfig(configPath, args[0], verifyTLSConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||
spin.Prefix = prefix
|
||||
|
||||
searchConfig := searchConfig{
|
||||
searchService: searchService,
|
||||
servURL: &servURL,
|
||||
user: &user,
|
||||
outputFormat: &outputFormat,
|
||||
verbose: &verbose,
|
||||
debug: &debug,
|
||||
spinner: spinnerState{spin, isSpinner},
|
||||
verifyTLS: &verifyTLS,
|
||||
resultWriter: cmd.OutOrStdout(),
|
||||
}
|
||||
|
||||
err = listRepos(searchConfig)
|
||||
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
repoCmd.SetUsageTemplate(repoCmd.UsageTemplate() + usageFooter)
|
||||
|
||||
repoCmd.AddCommand(NewListReposCommand(searchService))
|
||||
|
||||
repoCmd.Flags().StringVar(&servURL, cmdflags.URLFlag, "", "Specify zot server URL if config-name is not mentioned")
|
||||
repoCmd.Flags().StringVarP(&user, cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
repoCmd.Flags().BoolVar(&debug, cmdflags.DebugFlag, false, "Show debug output")
|
||||
|
||||
return repoCmd
|
||||
}
|
||||
|
||||
func listRepos(searchConfig searchConfig) error {
|
||||
searcher := new(repoSearcher)
|
||||
err := searcher.searchRepos(searchConfig)
|
||||
|
||||
return err
|
||||
}
|
|
@ -210,16 +210,12 @@ func NewCliRootCmd() *cobra.Command {
|
|||
},
|
||||
}
|
||||
|
||||
rootCmd.SilenceUsage = true
|
||||
|
||||
// additional cmds
|
||||
enableCli(rootCmd)
|
||||
// "version"
|
||||
rootCmd.Flags().BoolVarP(&showVersion, cmdflags.VersionFlag, "v", false, "show the version and exit")
|
||||
rootCmd.PersistentFlags().String(cmdflags.URLFlag, "",
|
||||
"Specify zot server URL if config-name is not mentioned")
|
||||
rootCmd.PersistentFlags().String(cmdflags.ConfigFlag, "",
|
||||
"Specify the repository where to connect")
|
||||
rootCmd.PersistentFlags().StringP(cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
|
|
@ -4,158 +4,32 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
func NewSearchCommand(searchService SearchService) *cobra.Command {
|
||||
searchImageParams := make(map[string]*string)
|
||||
|
||||
var servURL, user, outputFormat string
|
||||
|
||||
var isSpinner, verifyTLS, verbose, debug bool
|
||||
|
||||
searchCmd := &cobra.Command{
|
||||
Use: "search [config-name]",
|
||||
Short: "Search images and their tags",
|
||||
Long: `Search repos or images
|
||||
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.
|
||||
|
||||
# For referrers search specify the referred subject using it's full digest or tag:
|
||||
zli search --subject repo@sha256:f9a0981...
|
||||
zli search --subject repo:tag
|
||||
`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := path.Join(home, "/.zot")
|
||||
if servURL == "" {
|
||||
if len(args) > 0 {
|
||||
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if urlFromConfig == "" {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
|
||||
servURL = urlFromConfig
|
||||
} else {
|
||||
return zerr.ErrNoURLProvided
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
var err error
|
||||
isSpinner, err = parseBooleanConfig(configPath, args[0], showspinnerConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
verifyTLS, err = parseBooleanConfig(configPath, args[0], verifyTLSConfig)
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||
spin.Prefix = prefix
|
||||
|
||||
searchConfig := searchConfig{
|
||||
params: searchImageParams,
|
||||
searchService: searchService,
|
||||
servURL: &servURL,
|
||||
user: &user,
|
||||
outputFormat: &outputFormat,
|
||||
verbose: &verbose,
|
||||
debug: &debug,
|
||||
spinner: spinnerState{spin, isSpinner},
|
||||
verifyTLS: &verifyTLS,
|
||||
resultWriter: cmd.OutOrStdout(),
|
||||
}
|
||||
|
||||
err = globalSearch(searchConfig)
|
||||
|
||||
if err != nil {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Long: `Search repos or images`,
|
||||
}
|
||||
|
||||
setupSearchFlags(searchCmd, searchImageParams, &servURL, &user, &outputFormat, &verbose, &debug)
|
||||
searchCmd.SetUsageTemplate(searchCmd.UsageTemplate() + usageFooter)
|
||||
|
||||
searchCmd.PersistentFlags().String(cmdflags.URLFlag, "",
|
||||
"Specify zot server URL if config-name is not mentioned")
|
||||
searchCmd.PersistentFlags().String(cmdflags.ConfigFlag, "",
|
||||
"Specify the registry configuration to use for connection")
|
||||
searchCmd.PersistentFlags().StringP(cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
searchCmd.PersistentFlags().StringP(cmdflags.OutputFormatFlag, "f", "", "Specify output format [text/json/yaml]")
|
||||
searchCmd.PersistentFlags().Bool(cmdflags.VerboseFlag, false, "Show verbose output")
|
||||
searchCmd.PersistentFlags().Bool(cmdflags.DebugFlag, false, "Show debug output")
|
||||
|
||||
searchCmd.AddCommand(NewSearchQueryCommand(searchService))
|
||||
searchCmd.AddCommand(NewSearchSubjectCommand(searchService))
|
||||
|
||||
return searchCmd
|
||||
}
|
||||
|
||||
func setupSearchFlags(searchCmd *cobra.Command, searchImageParams map[string]*string,
|
||||
servURL, user, outputFormat *string, verbose *bool, debug *bool,
|
||||
) {
|
||||
searchImageParams["query"] = searchCmd.Flags().StringP("query", "q", "",
|
||||
"Specify what repo or image(repo:tag) to be searched")
|
||||
|
||||
searchImageParams["subject"] = searchCmd.Flags().StringP("subject", "s", "",
|
||||
"List all referrers for this subject. The subject can be specified by tag(repo:tag) or by digest"+
|
||||
"(repo@digest)")
|
||||
|
||||
searchCmd.Flags().StringVar(servURL, cmdflags.URLFlag, "", "Specify zot server URL if config-name is not mentioned")
|
||||
searchCmd.Flags().StringVarP(user, cmdflags.UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
searchCmd.PersistentFlags().StringVarP(outputFormat, cmdflags.OutputFormatFlag, "f", "",
|
||||
"Specify output format [text/json/yaml]")
|
||||
searchCmd.PersistentFlags().BoolVar(verbose, cmdflags.VerboseFlag, false, "Show verbose output")
|
||||
searchCmd.PersistentFlags().BoolVar(debug, cmdflags.DebugFlag, false, "Show debug output")
|
||||
}
|
||||
|
||||
func globalSearch(searchConfig searchConfig) error {
|
||||
var searchers []searcher
|
||||
|
||||
if checkExtEndPoint(searchConfig) {
|
||||
searchers = getGlobalSearchersGQL()
|
||||
} else {
|
||||
searchers = getGlobalSearchersREST()
|
||||
}
|
||||
|
||||
for _, searcher := range searchers {
|
||||
found, err := searcher.search(searchConfig)
|
||||
if found {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return zerr.ErrInvalidFlagsCombination
|
||||
}
|
||||
|
|
|
@ -1,596 +0,0 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package cli //nolint:testpackage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
||||
const (
|
||||
customArtTypeV1 = "application/custom.art.type.v1"
|
||||
customArtTypeV2 = "application/custom.art.type.v2"
|
||||
repoName = "repo"
|
||||
)
|
||||
|
||||
func TestReferrersSearchers(t *testing.T) {
|
||||
refSearcherGQL := referrerSearcherGQL{}
|
||||
refSearcher := referrerSearcher{}
|
||||
|
||||
Convey("GQL Searcher", t, func() {
|
||||
Convey("Bad parameters", func() {
|
||||
ok, err := refSearcherGQL.search(searchConfig{params: map[string]*string{
|
||||
"badParam": ref("badParam"),
|
||||
}})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("GetRepoReference fails", func() {
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"subject": ref("bad-subject"),
|
||||
},
|
||||
user: ref("test:pass"),
|
||||
}
|
||||
|
||||
ok, err := refSearcherGQL.search(conf)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("fetchImageDigest for tags fails", func() {
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"subject": ref("repo:tag"),
|
||||
},
|
||||
user: ref("test:pass"),
|
||||
servURL: ref("127.0.0.1:8080"),
|
||||
}
|
||||
|
||||
ok, err := refSearcherGQL.search(conf)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("search service fails", func() {
|
||||
port := test.GetFreePort()
|
||||
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"subject": ref("repo:tag"),
|
||||
},
|
||||
searchService: NewSearchService(),
|
||||
user: ref("test:pass"),
|
||||
servURL: ref("http://127.0.0.1:" + port),
|
||||
verifyTLS: ref(false),
|
||||
debug: ref(false),
|
||||
verbose: ref(false),
|
||||
}
|
||||
|
||||
server := test.StartTestHTTPServer(test.HTTPRoutes{
|
||||
test.RouteHandler{
|
||||
Route: "/v2/{repo}/manifests/{ref}",
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
AllowedMethods: []string{"HEAD"},
|
||||
},
|
||||
}, port)
|
||||
|
||||
defer server.Close()
|
||||
|
||||
ok, err := refSearcherGQL.search(conf)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("REST searcher", t, func() {
|
||||
Convey("Bad parameters", func() {
|
||||
ok, err := refSearcher.search(searchConfig{params: map[string]*string{
|
||||
"badParam": ref("badParam"),
|
||||
}})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("GetRepoReference fails", func() {
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"subject": ref("bad-subject"),
|
||||
},
|
||||
user: ref("test:pass"),
|
||||
}
|
||||
|
||||
ok, err := refSearcher.search(conf)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("fetchImageDigest for tags fails", func() {
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"subject": ref("repo:tag"),
|
||||
},
|
||||
user: ref("test:pass"),
|
||||
servURL: ref("127.0.0.1:1000"),
|
||||
}
|
||||
|
||||
ok, err := refSearcher.search(conf)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("search service fails", func() {
|
||||
port := test.GetFreePort()
|
||||
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"subject": ref("repo:tag"),
|
||||
},
|
||||
searchService: NewSearchService(),
|
||||
user: ref("test:pass"),
|
||||
servURL: ref("http://127.0.0.1:" + port),
|
||||
verifyTLS: ref(false),
|
||||
debug: ref(false),
|
||||
verbose: ref(false),
|
||||
fixedFlag: ref(false),
|
||||
}
|
||||
|
||||
server := test.StartTestHTTPServer(test.HTTPRoutes{
|
||||
test.RouteHandler{
|
||||
Route: "/v2/{repo}/manifests/{ref}",
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
AllowedMethods: []string{"HEAD"},
|
||||
},
|
||||
}, port)
|
||||
|
||||
defer server.Close()
|
||||
|
||||
ok, err := refSearcher.search(conf)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestReferrerCLI(t *testing.T) {
|
||||
Convey("Test GQL", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
defaultVal := true
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repo := repoName
|
||||
image := test.CreateRandomImage()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ref1 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref2 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
ArtifactConfig(customArtTypeV1).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref3 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
ArtifactType(customArtTypeV2).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
err = test.UploadImage(ref1, baseURL, repo, ref1.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref2, baseURL, repo, ref2.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref3, baseURL, repo, ref3.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
args := []string{"reftest", "--subject", repo + "@" + image.DigestStr()}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
|
||||
fmt.Println(buff.String())
|
||||
|
||||
os.Remove(configPath)
|
||||
|
||||
args = []string{"reftest", "--subject", repo + ":" + "tag"}
|
||||
|
||||
configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd = NewSearchCommand(new(searchService))
|
||||
|
||||
buff = &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
|
||||
Convey("Test REST", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
defaultVal := false
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repo := repoName
|
||||
image := test.CreateRandomImage()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ref1 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref2 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
ArtifactConfig(customArtTypeV1).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref3 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
ArtifactType(customArtTypeV2).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
err = test.UploadImage(ref1, baseURL, repo, ref1.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref2, baseURL, repo, ref2.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref3, baseURL, repo, ref3.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// get referrers by digest
|
||||
args := []string{"reftest", "--subject", repo + "@" + image.DigestStr()}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
fmt.Println(buff.String())
|
||||
|
||||
os.Remove(configPath)
|
||||
|
||||
args = []string{"reftest", "--subject", repo + ":" + "tag"}
|
||||
|
||||
configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff = &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestFormatsReferrersCLI(t *testing.T) {
|
||||
Convey("Create server", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
defaultVal := false
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repo := repoName
|
||||
image := test.CreateRandomImage()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// add referrers
|
||||
ref1 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref2 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
ArtifactConfig(customArtTypeV1).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref3 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
ArtifactType(customArtTypeV2).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
err = test.UploadImage(ref1, baseURL, repo, ref1.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref2, baseURL, repo, ref2.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref3, baseURL, repo, ref3.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("JSON format", func() {
|
||||
args := []string{"reftest", "--format", "json", "--subject", repo + "@" + image.DigestStr()}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
Convey("YAML format", func() {
|
||||
args := []string{"reftest", "--format", "yaml", "--subject", repo + "@" + image.DigestStr()}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
Convey("Invalid format", func() {
|
||||
args := []string{"reftest", "--format", "invalid_format", "--subject", repo + "@" + image.DigestStr()}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestReferrersCLIErrors(t *testing.T) {
|
||||
Convey("Errors", t, func() {
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
Convey("no url provided", func() {
|
||||
args := []string{"reftest", "--format", "invalid", "--query", "repo/alpine"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest","showspinner":false}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("getConfigValue", func() {
|
||||
args := []string{"reftest", "--subject", "repo/alpine"}
|
||||
|
||||
configPath := makeConfigFile(`bad-json`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("bad showspinnerConfig ", func() {
|
||||
args := []string{"reftest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("bad verifyTLSConfig ", func() {
|
||||
args := []string{"reftest"}
|
||||
|
||||
configPath := makeConfigFile(
|
||||
`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("url from config is empty", func() {
|
||||
args := []string{"reftest", "--subject", "repo/alpine"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"", "showspinner":false}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("bad params combination", func() {
|
||||
args := []string{"reftest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("no url provided error", func() {
|
||||
args := []string{}
|
||||
|
||||
configPath := makeConfigFile(`bad-json`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func ref[T any](input T) *T {
|
||||
ref := input
|
||||
|
||||
return &ref
|
||||
}
|
|
@ -6,7 +6,6 @@ package cli //nolint:testpackage
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -17,58 +16,408 @@ import (
|
|||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
||||
func TestGlobalSearchers(t *testing.T) {
|
||||
globalSearcher := globalSearcherGQL{}
|
||||
const (
|
||||
customArtTypeV1 = "application/custom.art.type.v1"
|
||||
customArtTypeV2 = "application/custom.art.type.v2"
|
||||
repoName = "repo"
|
||||
)
|
||||
|
||||
Convey("GQL Searcher", t, func() {
|
||||
Convey("Bad parameters", func() {
|
||||
ok, err := globalSearcher.search(searchConfig{params: map[string]*string{
|
||||
"badParam": ref("badParam"),
|
||||
}})
|
||||
func TestReferrerCLI(t *testing.T) {
|
||||
Convey("Test GQL", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
defaultVal := true
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repo := repoName
|
||||
image := test.CreateRandomImage()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ref1 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref2 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
ArtifactConfig(customArtTypeV1).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref3 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
ArtifactType(customArtTypeV2).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
err = test.UploadImage(ref1, baseURL, repo, ref1.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref2, baseURL, repo, ref2.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref3, baseURL, repo, ref3.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
|
||||
fmt.Println(buff.String())
|
||||
|
||||
os.Remove(configPath)
|
||||
|
||||
args = []string{"subject", repo + ":" + "tag", "--config", "reftest"}
|
||||
|
||||
configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd = NewSearchCommand(new(searchService))
|
||||
|
||||
buff = &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
|
||||
Convey("Test REST", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
defaultVal := false
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repo := repoName
|
||||
image := test.CreateRandomImage()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ref1 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref2 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
ArtifactConfig(customArtTypeV1).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref3 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
ArtifactType(customArtTypeV2).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
err = test.UploadImage(ref1, baseURL, repo, ref1.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref2, baseURL, repo, ref2.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref3, baseURL, repo, ref3.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// get referrers by digest
|
||||
args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
fmt.Println(buff.String())
|
||||
|
||||
os.Remove(configPath)
|
||||
|
||||
args = []string{"subject", repo + ":" + "tag", "--config", "reftest"}
|
||||
|
||||
configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff = &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
|
||||
So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
|
||||
So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
|
||||
So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestFormatsReferrersCLI(t *testing.T) {
|
||||
Convey("Create server", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
defaultVal := false
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repo := repoName
|
||||
image := test.CreateRandomImage()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// add referrers
|
||||
ref1 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref2 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
ArtifactConfig(customArtTypeV1).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
ref3 := test.CreateImageWith().
|
||||
RandomLayers(1, 10).
|
||||
RandomConfig().
|
||||
ArtifactType(customArtTypeV2).
|
||||
Subject(image.DescriptorRef()).Build()
|
||||
|
||||
err = test.UploadImage(ref1, baseURL, repo, ref1.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref2, baseURL, repo, ref2.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.UploadImage(ref3, baseURL, repo, ref3.DigestStr())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("JSON format", func() {
|
||||
args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "json", "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeFalse)
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
Convey("YAML format", func() {
|
||||
args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "yaml", "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
fmt.Println(buff.String())
|
||||
})
|
||||
Convey("Invalid format", func() {
|
||||
args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "invalid_format", "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestReferrersCLIErrors(t *testing.T) {
|
||||
Convey("Errors", t, func() {
|
||||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
Convey("no url provided", func() {
|
||||
args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest","showspinner":false}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("global searcher service fail", func() {
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"query": ref("repo"),
|
||||
},
|
||||
searchService: NewSearchService(),
|
||||
user: ref("test:pass"),
|
||||
servURL: ref("127.0.0.1:8080"),
|
||||
verifyTLS: ref(false),
|
||||
debug: ref(false),
|
||||
verbose: ref(false),
|
||||
fixedFlag: ref(false),
|
||||
}
|
||||
ok, err := globalSearcher.search(conf)
|
||||
Convey("getConfigValue", func() {
|
||||
args := []string{"subject", "repo/alpine", "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(`bad-json`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("print images fail", func() {
|
||||
conf := searchConfig{
|
||||
params: map[string]*string{
|
||||
"query": ref("repo"),
|
||||
},
|
||||
user: ref("user:pass"),
|
||||
outputFormat: ref("bad-format"),
|
||||
searchService: mockService{},
|
||||
resultWriter: io.Discard,
|
||||
verbose: ref(false),
|
||||
}
|
||||
ok, err := globalSearcher.search(conf)
|
||||
Convey("bad showspinnerConfig ", func() {
|
||||
args := []string{"query", "repo", "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("bad verifyTLSConfig ", func() {
|
||||
args := []string{"query", "repo", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(
|
||||
`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("url from config is empty", func() {
|
||||
args := []string{"subject", "repo/alpine", "--config", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"", "showspinner":false}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("bad params combination", func() {
|
||||
args := []string{"query", "repo", "reftest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false}]}`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -138,7 +487,7 @@ func TestSearchCLI(t *testing.T) {
|
|||
|
||||
// search by repos
|
||||
|
||||
args := []string{"searchtest", "--query", "test/alpin", "--verbose"}
|
||||
args := []string{"query", "test/alpin", "--verbose", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
@ -165,7 +514,7 @@ func TestSearchCLI(t *testing.T) {
|
|||
|
||||
cmd = NewSearchCommand(new(searchService))
|
||||
|
||||
args = []string{"searchtest", "--query", "repo/alpine:"}
|
||||
args = []string{"query", "repo/alpine:", "--config", "searchtest"}
|
||||
|
||||
configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
@ -241,7 +590,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
|||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
Convey("JSON format", func() {
|
||||
args := []string{"searchtest", "--format", "json", "--query", "repo/alpine"}
|
||||
args := []string{"query", "repo/alpine", "--format", "json", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
@ -258,7 +607,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("YAML format", func() {
|
||||
args := []string{"searchtest", "--format", "yaml", "--query", "repo/alpine"}
|
||||
args := []string{"query", "repo/alpine", "--format", "yaml", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
@ -275,7 +624,7 @@ func TestFormatsSearchCLI(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("Invalid format", func() {
|
||||
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||
args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
|
@ -297,7 +646,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
cmd := NewSearchCommand(new(searchService))
|
||||
|
||||
Convey("no url provided", func() {
|
||||
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||
args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"searchtest","showspinner":false}]}`)
|
||||
|
||||
|
@ -312,7 +661,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("getConfigValue", func() {
|
||||
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||
args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(`bad-json`)
|
||||
|
||||
|
@ -327,7 +676,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("bad showspinnerConfig ", func() {
|
||||
args := []string{"searchtest"}
|
||||
args := []string{"query", "repo/alpine", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(
|
||||
`{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`)
|
||||
|
@ -343,7 +692,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("bad verifyTLSConfig ", func() {
|
||||
args := []string{"searchtest"}
|
||||
args := []string{"query", "repo/alpine", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(
|
||||
`{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`)
|
||||
|
@ -359,7 +708,7 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
})
|
||||
|
||||
Convey("url from config is empty", func() {
|
||||
args := []string{"searchtest", "--format", "invalid", "--query", "repo/alpine"}
|
||||
args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
|
||||
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"searchtest", "url":"", "showspinner":false}]}`)
|
||||
|
||||
|
@ -372,35 +721,6 @@ func TestSearchCLIErrors(t *testing.T) {
|
|||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("no url provided error", func() {
|
||||
args := []string{}
|
||||
|
||||
configPath := makeConfigFile(`bad-json`)
|
||||
|
||||
defer os.Remove(configPath)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("globalSearch without gql active", func() {
|
||||
err := globalSearch(searchConfig{
|
||||
user: ref("t"),
|
||||
servURL: ref("t"),
|
||||
verifyTLS: ref(false),
|
||||
debug: ref(false),
|
||||
params: map[string]*string{
|
||||
"query": ref("t"),
|
||||
},
|
||||
resultWriter: io.Discard,
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -430,9 +750,9 @@ func TestSearchCommandGQL(t *testing.T) {
|
|||
defer os.Remove(configPath)
|
||||
|
||||
Convey("query", func() {
|
||||
args := []string{"query", "repo/al"}
|
||||
args := []string{"query", "repo/al", "--config", "searchtest"}
|
||||
cmd := NewSearchCommand(mockService{})
|
||||
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
|
@ -448,7 +768,7 @@ func TestSearchCommandGQL(t *testing.T) {
|
|||
|
||||
Convey("query command errors", func() {
|
||||
// no url
|
||||
args := []string{"repo/al"}
|
||||
args := []string{"repo/al", "--config", "searchtest"}
|
||||
cmd := NewSearchQueryCommand(mockService{})
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
|
@ -462,9 +782,9 @@ func TestSearchCommandGQL(t *testing.T) {
|
|||
err := test.UploadImage(test.CreateRandomImage(), baseURL, "repo", "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
args := []string{"subject", "repo:tag"}
|
||||
args := []string{"subject", "repo:tag", "--config", "searchtest"}
|
||||
cmd := NewSearchCommand(mockService{})
|
||||
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
|
@ -479,7 +799,7 @@ func TestSearchCommandGQL(t *testing.T) {
|
|||
|
||||
Convey("subject command errors", func() {
|
||||
// no url
|
||||
args := []string{"repo:tag"}
|
||||
args := []string{"repo:tag", "--config", "searchtest"}
|
||||
cmd := NewSearchSubjectCommand(mockService{})
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
|
@ -510,9 +830,9 @@ func TestSearchCommandREST(t *testing.T) {
|
|||
defer os.Remove(configPath)
|
||||
|
||||
Convey("query", func() {
|
||||
args := []string{"query", "repo/al"}
|
||||
args := []string{"query", "repo/al", "--config", "searchtest"}
|
||||
cmd := NewSearchCommand(mockService{})
|
||||
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
|
@ -525,9 +845,9 @@ func TestSearchCommandREST(t *testing.T) {
|
|||
err := test.UploadImage(test.CreateRandomImage(), baseURL, "repo", "tag")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
args := []string{"subject", "repo:tag"}
|
||||
args := []string{"subject", "repo:tag", "--config", "searchtest"}
|
||||
cmd := NewSearchCommand(mockService{})
|
||||
cmd.PersistentFlags().String(cmdflags.ConfigFlag, "searchtest", "")
|
||||
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
|
|
|
@ -15,8 +15,10 @@ import (
|
|||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
)
|
||||
|
||||
const CveDBRetryInterval = 3
|
||||
|
||||
func SearchAllImages(config searchConfig) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
imageErr := make(chan stringResult)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
|
@ -40,7 +42,7 @@ func SearchAllImages(config searchConfig) error {
|
|||
}
|
||||
|
||||
func SearchAllImagesGQL(config searchConfig) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -60,7 +62,7 @@ func SearchAllImagesGQL(config searchConfig) error {
|
|||
}
|
||||
|
||||
func SearchImageByName(config searchConfig, image string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
imageErr := make(chan stringResult)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
|
@ -79,6 +81,10 @@ func SearchImageByName(config searchConfig, image string) error {
|
|||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if strings.Contains(err.Error(), "NAME_UNKNOWN") {
|
||||
return zerr.ErrEmptyRepoList
|
||||
}
|
||||
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
|
@ -86,7 +92,7 @@ func SearchImageByName(config searchConfig, image string) error {
|
|||
}
|
||||
|
||||
func SearchImageByNameGQL(config searchConfig, imageName string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -110,7 +116,7 @@ func SearchImageByNameGQL(config searchConfig, imageName string) error {
|
|||
}
|
||||
|
||||
func SearchImagesByDigest(config searchConfig, digest string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
imageErr := make(chan stringResult)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
|
@ -136,7 +142,7 @@ func SearchImagesByDigest(config searchConfig, digest string) error {
|
|||
}
|
||||
|
||||
func SearchDerivedImageListGQL(config searchConfig, derivedImage string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -157,7 +163,7 @@ func SearchDerivedImageListGQL(config searchConfig, derivedImage string) error {
|
|||
}
|
||||
|
||||
func SearchBaseImageListGQL(config searchConfig, baseImage string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -178,7 +184,7 @@ func SearchBaseImageListGQL(config searchConfig, baseImage string) error {
|
|||
}
|
||||
|
||||
func SearchImagesForDigestGQL(config searchConfig, digest string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -202,7 +208,7 @@ func SearchImagesForDigestGQL(config searchConfig, digest string) error {
|
|||
}
|
||||
|
||||
func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -225,7 +231,7 @@ func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) erro
|
|||
}
|
||||
|
||||
return err
|
||||
}, maxRetries, cveDBRetryInterval*time.Second)
|
||||
}, maxRetries, CveDBRetryInterval*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -238,12 +244,12 @@ func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) erro
|
|||
|
||||
var builder strings.Builder
|
||||
|
||||
if *config.outputFormat == defaultOutputFormat || *config.outputFormat == "" {
|
||||
printCVETableHeader(&builder, *config.verbose, 0, 0, 0)
|
||||
if config.outputFormat == defaultOutputFormat || config.outputFormat == "" {
|
||||
printCVETableHeader(&builder)
|
||||
fmt.Fprint(config.resultWriter, builder.String())
|
||||
}
|
||||
|
||||
out, err := cveList.string(*config.outputFormat)
|
||||
out, err := cveList.string(config.outputFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -254,7 +260,7 @@ func SearchCVEForImageGQL(config searchConfig, image, searchedCveID string) erro
|
|||
}
|
||||
|
||||
func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -278,7 +284,7 @@ func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error {
|
|||
}
|
||||
|
||||
return err
|
||||
}, maxRetries, cveDBRetryInterval*time.Second)
|
||||
}, maxRetries, CveDBRetryInterval*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -293,7 +299,7 @@ func SearchImagesByCVEIDGQL(config searchConfig, repo, cveid string) error {
|
|||
}
|
||||
|
||||
func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -317,7 +323,7 @@ func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error {
|
|||
}
|
||||
|
||||
return err
|
||||
}, maxRetries, cveDBRetryInterval*time.Second)
|
||||
}, maxRetries, CveDBRetryInterval*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -332,7 +338,7 @@ func SearchFixedTagsGQL(config searchConfig, repo, cveid string) error {
|
|||
}
|
||||
|
||||
func GlobalSearchGQL(config searchConfig, query string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer cancel()
|
||||
|
@ -362,7 +368,7 @@ func GlobalSearchGQL(config searchConfig, query string) error {
|
|||
}
|
||||
|
||||
func SearchReferrersGQL(config searchConfig, subject string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
|
||||
repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
|
||||
if err != nil {
|
||||
|
@ -399,7 +405,7 @@ func SearchReferrersGQL(config searchConfig, subject string) error {
|
|||
}
|
||||
|
||||
func SearchReferrers(config searchConfig, subject string) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
|
||||
repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
|
||||
if err != nil {
|
||||
|
@ -435,7 +441,7 @@ func SearchReferrers(config searchConfig, subject string) error {
|
|||
}
|
||||
|
||||
func SearchRepos(config searchConfig) error {
|
||||
username, password := getUsernameAndPassword(*config.user)
|
||||
username, password := getUsernameAndPassword(config.user)
|
||||
repoErr := make(chan stringResult)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
|
|
|
@ -611,14 +611,14 @@ func TestSearchRepos(t *testing.T) {
|
|||
func getMockSearchConfig(buff *bytes.Buffer, mockService mockService) searchConfig {
|
||||
return searchConfig{
|
||||
resultWriter: buff,
|
||||
user: ref(""),
|
||||
user: "",
|
||||
searchService: mockService,
|
||||
servURL: ref("http://127.0.0.1:8000"),
|
||||
outputFormat: ref(""),
|
||||
verifyTLS: ref(false),
|
||||
fixedFlag: ref(false),
|
||||
verbose: ref(false),
|
||||
debug: ref(false),
|
||||
servURL: "http://127.0.0.1:8000",
|
||||
outputFormat: "",
|
||||
verifyTLS: false,
|
||||
fixedFlag: false,
|
||||
verbose: false,
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -685,7 +685,8 @@ func TestUtils(t *testing.T) {
|
|||
Convey("GetConfigOptions", t, func() {
|
||||
// no flags
|
||||
cmd := &cobra.Command{}
|
||||
isSpinner, verifyTLS := GetCliConfigOptions(cmd)
|
||||
isSpinner, verifyTLS, err := GetCliConfigOptions(cmd)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(isSpinner, ShouldBeFalse)
|
||||
So(verifyTLS, ShouldBeFalse)
|
||||
|
||||
|
@ -693,7 +694,8 @@ func TestUtils(t *testing.T) {
|
|||
configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":"bad", "verify-tls": false}]}`)
|
||||
cmd = &cobra.Command{}
|
||||
cmd.Flags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||
isSpinner, verifyTLS = GetCliConfigOptions(cmd)
|
||||
isSpinner, verifyTLS, err = GetCliConfigOptions(cmd)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(isSpinner, ShouldBeFalse)
|
||||
So(verifyTLS, ShouldBeFalse)
|
||||
os.Remove(configPath)
|
||||
|
@ -702,7 +704,8 @@ func TestUtils(t *testing.T) {
|
|||
configPath = makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false, "verify-tls": "bad"}]}`)
|
||||
cmd = &cobra.Command{}
|
||||
cmd.Flags().String(cmdflags.ConfigFlag, "imagetest", "")
|
||||
isSpinner, verifyTLS = GetCliConfigOptions(cmd)
|
||||
isSpinner, verifyTLS, err = GetCliConfigOptions(cmd)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(isSpinner, ShouldBeFalse)
|
||||
So(verifyTLS, ShouldBeFalse)
|
||||
os.Remove(configPath)
|
||||
|
@ -743,17 +746,17 @@ func TestUtils(t *testing.T) {
|
|||
Convey("CheckExtEndPointQuery", t, func() {
|
||||
// invalid url
|
||||
err := CheckExtEndPointQuery(searchConfig{
|
||||
user: ref(""),
|
||||
servURL: ref("bad-url"),
|
||||
user: "",
|
||||
servURL: "bad-url",
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// good url but no connection
|
||||
err = CheckExtEndPointQuery(searchConfig{
|
||||
user: ref(""),
|
||||
servURL: ref("http://127.0.0.1:5000"),
|
||||
verifyTLS: ref(false),
|
||||
debug: ref(false),
|
||||
user: "",
|
||||
servURL: "http://127.0.0.1:5000",
|
||||
verifyTLS: false,
|
||||
debug: false,
|
||||
resultWriter: io.Discard,
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
|
|
@ -6,7 +6,6 @@ package cli
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
|
@ -94,16 +93,3 @@ func OneImageWithRefArg(cmd *cobra.Command, args []string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func OneDigestArg(cmd *cobra.Command, args []string) error {
|
||||
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digest := args[0]
|
||||
if _, err := godigest.Parse(digest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
1169
pkg/cli/searcher.go
1169
pkg/cli/searcher.go
File diff suppressed because it is too large
Load diff
|
@ -38,8 +38,6 @@ type SearchService interface { //nolint:interfacebloat
|
|||
digest string) (*common.ImagesForDigest, error)
|
||||
getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
||||
imageName string, searchedCVE string) (*cveResult, error)
|
||||
getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
|
||||
digest string) (*common.ImagesForCve, error)
|
||||
getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, repo,
|
||||
cveID string) (*common.ImagesForCve, error)
|
||||
getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
||||
|
@ -55,24 +53,29 @@ type SearchService interface { //nolint:interfacebloat
|
|||
|
||||
getAllImages(ctx context.Context, config searchConfig, username, password string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getCveByImage(ctx context.Context, config searchConfig, username, password, imageName, searchedCVE string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getImagesByCveID(ctx context.Context, config searchConfig, username, password, cveid string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getImagesByDigest(ctx context.Context, config searchConfig, username, password, digest string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getFixedTagsForCVE(ctx context.Context, config searchConfig, username, password, imageName, cveid string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getRepos(ctx context.Context, config searchConfig, username, password string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getImageByName(ctx context.Context, config searchConfig, username, password, imageName string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getImageByNameAndCVEID(ctx context.Context, config searchConfig, username, password, imageName, cveid string,
|
||||
channel chan stringResult, wtgrp *sync.WaitGroup)
|
||||
getReferrers(ctx context.Context, config searchConfig, username, password string, repo, digest string,
|
||||
) (referrersResult, error)
|
||||
}
|
||||
|
||||
type searchConfig struct {
|
||||
searchService SearchService
|
||||
servURL string
|
||||
user string
|
||||
outputFormat string
|
||||
verifyTLS bool
|
||||
fixedFlag bool
|
||||
verbose bool
|
||||
debug bool
|
||||
resultWriter io.Writer
|
||||
spinner spinnerState
|
||||
}
|
||||
|
||||
type searchService struct{}
|
||||
|
||||
func NewSearchService() SearchService {
|
||||
|
@ -297,43 +300,6 @@ func (service searchService) getImagesForDigestGQL(ctx context.Context, config s
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (service searchService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username,
|
||||
password, cveID string,
|
||||
) (*common.ImagesForCve, error) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
ImageListForCVE(id: "%s", requestedPage: {sortBy: ALPHABETIC_ASC}) {
|
||||
Results {
|
||||
RepoName Tag
|
||||
Digest
|
||||
MediaType
|
||||
Manifests {
|
||||
Digest
|
||||
ConfigDigest
|
||||
Size
|
||||
Platform {Os Arch}
|
||||
IsSigned
|
||||
Layers {Size Digest}
|
||||
LastUpdated
|
||||
}
|
||||
LastUpdated
|
||||
Size
|
||||
IsSigned
|
||||
}
|
||||
}
|
||||
}`,
|
||||
cveID)
|
||||
result := &common.ImagesForCve{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
|
||||
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
||||
return nil, errResult
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (service searchService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
||||
imageName, searchedCVE string,
|
||||
) (*cveResult, error) {
|
||||
|
@ -443,7 +409,7 @@ func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config s
|
|||
func (service searchService) getReferrers(ctx context.Context, config searchConfig, username, password string,
|
||||
repo, digest string,
|
||||
) (referrersResult, error) {
|
||||
referrersEndpoint, err := combineServerAndEndpointURL(*config.servURL,
|
||||
referrersEndpoint, err := combineServerAndEndpointURL(config.servURL,
|
||||
fmt.Sprintf("/v2/%s/referrers/%s", repo, digest))
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
|
@ -454,8 +420,8 @@ func (service searchService) getReferrers(ctx context.Context, config searchConf
|
|||
}
|
||||
|
||||
referrerResp := &ispec.Index{}
|
||||
_, err = makeGETRequest(ctx, referrersEndpoint, username, password, *config.verifyTLS,
|
||||
*config.debug, &referrerResp, config.resultWriter)
|
||||
_, err = makeGETRequest(ctx, referrersEndpoint, username, password, config.verifyTLS,
|
||||
config.debug, &referrerResp, config.resultWriter)
|
||||
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
|
@ -505,7 +471,7 @@ func (service searchService) getAllImages(ctx context.Context, config searchConf
|
|||
|
||||
catalog := &catalogResponse{}
|
||||
|
||||
catalogEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s",
|
||||
catalogEndPoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("%s%s",
|
||||
constants.RoutePrefix, constants.ExtCatalogPrefix))
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
|
@ -516,8 +482,8 @@ func (service searchService) getAllImages(ctx context.Context, config searchConf
|
|||
return
|
||||
}
|
||||
|
||||
_, err = makeGETRequest(ctx, catalogEndPoint, username, password, *config.verifyTLS,
|
||||
*config.debug, catalog, config.resultWriter)
|
||||
_, err = makeGETRequest(ctx, catalogEndPoint, username, password, config.verifyTLS,
|
||||
config.debug, catalog, config.resultWriter)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
|
@ -551,7 +517,7 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
|||
|
||||
repo, imageTag := common.GetImageDirAndTag(imageName)
|
||||
|
||||
tagListEndpoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("/v2/%s/tags/list", repo))
|
||||
tagListEndpoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("/v2/%s/tags/list", repo))
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
|
@ -562,8 +528,8 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
|||
}
|
||||
|
||||
tagList := &tagListResp{}
|
||||
_, err = makeGETRequest(ctx, tagListEndpoint, username, password, *config.verifyTLS,
|
||||
*config.debug, &tagList, config.resultWriter)
|
||||
_, err = makeGETRequest(ctx, tagListEndpoint, username, password, config.verifyTLS,
|
||||
config.debug, &tagList, config.resultWriter)
|
||||
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
|
@ -598,79 +564,6 @@ func getImage(ctx context.Context, config searchConfig, username, password, imag
|
|||
}
|
||||
}
|
||||
|
||||
func (service searchService) getImagesByCveID(ctx context.Context, config searchConfig, username,
|
||||
password, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
) {
|
||||
defer wtgrp.Done()
|
||||
defer close(rch)
|
||||
|
||||
query := fmt.Sprintf(
|
||||
`{
|
||||
ImageListForCVE(id: "%s") {
|
||||
Results {
|
||||
RepoName Tag
|
||||
Digest
|
||||
MediaType
|
||||
Manifests {
|
||||
Digest
|
||||
ConfigDigest
|
||||
Size
|
||||
Platform {Os Arch}
|
||||
IsSigned
|
||||
Layers {Size Digest}
|
||||
LastUpdated
|
||||
}
|
||||
LastUpdated
|
||||
Size
|
||||
IsSigned
|
||||
}
|
||||
}
|
||||
}`,
|
||||
cveid)
|
||||
|
||||
result := &common.ImagesForCve{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", err}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if result.Errors != nil || err != nil {
|
||||
var errBuilder strings.Builder
|
||||
|
||||
for _, err := range result.Errors {
|
||||
fmt.Fprintln(&errBuilder, err.Message)
|
||||
}
|
||||
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var localWg sync.WaitGroup
|
||||
|
||||
rlim := newSmoothRateLimiter(&localWg, rch)
|
||||
localWg.Add(1)
|
||||
|
||||
go rlim.startRateLimiter(ctx)
|
||||
|
||||
for _, image := range result.Results {
|
||||
localWg.Add(1)
|
||||
|
||||
go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg)
|
||||
}
|
||||
|
||||
localWg.Wait()
|
||||
}
|
||||
|
||||
func (service searchService) getImagesByDigest(ctx context.Context, config searchConfig, username,
|
||||
password string, digest string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
) {
|
||||
|
@ -744,209 +637,6 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc
|
|||
localWg.Wait()
|
||||
}
|
||||
|
||||
func (service searchService) getImageByNameAndCVEID(ctx context.Context, config searchConfig, username,
|
||||
password, imageName, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
) {
|
||||
defer wtgrp.Done()
|
||||
defer close(rch)
|
||||
|
||||
query := fmt.Sprintf(
|
||||
`{
|
||||
ImageListForCVE(id: "%s") {
|
||||
Results {
|
||||
RepoName Tag
|
||||
Digest
|
||||
MediaType
|
||||
Manifests {
|
||||
Digest
|
||||
ConfigDigest
|
||||
Size
|
||||
Platform {Os Arch}
|
||||
IsSigned
|
||||
Layers {Size Digest}
|
||||
LastUpdated
|
||||
}
|
||||
LastUpdated
|
||||
Size
|
||||
IsSigned
|
||||
}
|
||||
}
|
||||
}`,
|
||||
cveid)
|
||||
|
||||
result := &common.ImagesForCve{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", err}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if result.Errors != nil {
|
||||
var errBuilder strings.Builder
|
||||
|
||||
for _, err := range result.Errors {
|
||||
fmt.Fprintln(&errBuilder, err.Message)
|
||||
}
|
||||
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var localWg sync.WaitGroup
|
||||
|
||||
rlim := newSmoothRateLimiter(&localWg, rch)
|
||||
localWg.Add(1)
|
||||
|
||||
go rlim.startRateLimiter(ctx)
|
||||
|
||||
for _, image := range result.Results {
|
||||
if imageName != "" && !strings.EqualFold(imageName, image.RepoName) {
|
||||
continue
|
||||
}
|
||||
|
||||
localWg.Add(1)
|
||||
|
||||
go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg)
|
||||
}
|
||||
|
||||
localWg.Wait()
|
||||
}
|
||||
|
||||
func (service searchService) getCveByImage(ctx context.Context, config searchConfig, username, password,
|
||||
imageName, searchedCVE string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
) {
|
||||
defer wtgrp.Done()
|
||||
defer close(rch)
|
||||
|
||||
query := fmt.Sprintf(`{ CVEListForImage (image:"%s", searchedCVE:"%s")`+
|
||||
` { Tag CVEList { Id Title Severity Description `+
|
||||
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName, searchedCVE)
|
||||
result := &cveResult{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", err}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if result.Errors != nil {
|
||||
var errBuilder strings.Builder
|
||||
|
||||
for _, err := range result.Errors {
|
||||
fmt.Fprintln(&errBuilder, err.Message)
|
||||
}
|
||||
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
result.Data.CVEListForImage.CVEList = groupCVEsBySeverity(result.Data.CVEListForImage.CVEList)
|
||||
|
||||
str, err := result.string(*config.outputFormat)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", err}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{str, nil}
|
||||
}
|
||||
|
||||
func (service searchService) getFixedTagsForCVE(ctx context.Context, config searchConfig,
|
||||
username, password, imageName, cveid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
) {
|
||||
defer wtgrp.Done()
|
||||
defer close(rch)
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
ImageListWithCVEFixed (id: "%s", image: "%s") {
|
||||
Results {
|
||||
RepoName Tag
|
||||
Digest
|
||||
MediaType
|
||||
Manifests {
|
||||
Digest
|
||||
ConfigDigest
|
||||
Size
|
||||
Platform {Os Arch}
|
||||
IsSigned
|
||||
Layers {Size Digest}
|
||||
LastUpdated
|
||||
}
|
||||
LastUpdated
|
||||
Size
|
||||
IsSigned
|
||||
}
|
||||
}
|
||||
}`, cveid, imageName)
|
||||
|
||||
result := &common.ImageListWithCVEFixedResponse{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", err}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if result.Errors != nil {
|
||||
var errBuilder strings.Builder
|
||||
|
||||
for _, err := range result.Errors {
|
||||
fmt.Fprintln(&errBuilder, err.Message)
|
||||
}
|
||||
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
}
|
||||
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var localWg sync.WaitGroup
|
||||
|
||||
rlim := newSmoothRateLimiter(&localWg, rch)
|
||||
localWg.Add(1)
|
||||
|
||||
go rlim.startRateLimiter(ctx)
|
||||
|
||||
for _, img := range result.Results {
|
||||
localWg.Add(1)
|
||||
|
||||
go addManifestCallToPool(ctx, config, rlim, username, password, imageName, img.Tag, rch, &localWg)
|
||||
}
|
||||
|
||||
localWg.Wait()
|
||||
}
|
||||
|
||||
func groupCVEsBySeverity(cveList []cve) []cve {
|
||||
var (
|
||||
unknown = make([]cve, 0)
|
||||
|
@ -1006,13 +696,13 @@ func (service searchService) makeGraphQLQuery(ctx context.Context,
|
|||
config searchConfig, username, password, query string,
|
||||
resultPtr interface{},
|
||||
) error {
|
||||
endPoint, err := combineServerAndEndpointURL(*config.servURL, constants.FullSearchPrefix)
|
||||
endPoint, err := combineServerAndEndpointURL(config.servURL, constants.FullSearchPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = makeGraphQLRequest(ctx, endPoint, query, username, password, *config.verifyTLS,
|
||||
*config.debug, resultPtr, config.resultWriter)
|
||||
err = makeGraphQLRequest(ctx, endPoint, query, username, password, config.verifyTLS,
|
||||
config.debug, resultPtr, config.resultWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1053,7 +743,7 @@ func addManifestCallToPool(ctx context.Context, config searchConfig, pool *reque
|
|||
) {
|
||||
defer wtgrp.Done()
|
||||
|
||||
manifestEndpoint, err := combineServerAndEndpointURL(*config.servURL,
|
||||
manifestEndpoint, err := combineServerAndEndpointURL(config.servURL,
|
||||
fmt.Sprintf("/v2/%s/manifests/%s", imageName, tagName))
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
|
@ -1121,7 +811,7 @@ func (cve cveResult) string(format string) (string, error) {
|
|||
case ymlFormat, yamlFormat:
|
||||
return cve.stringYAML()
|
||||
default:
|
||||
return "", ErrInvalidOutputFormat
|
||||
return "", zerr.ErrInvalidOutputFormat
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1180,7 +870,7 @@ func (ref referrersResult) string(format string, maxArtifactTypeLen int) (string
|
|||
case ymlFormat, yamlFormat:
|
||||
return ref.stringYAML()
|
||||
default:
|
||||
return "", ErrInvalidOutputFormat
|
||||
return "", zerr.ErrInvalidOutputFormat
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1244,7 +934,7 @@ func (repo repoStruct) string(format string, maxImgNameLen, maxTimeLen int, verb
|
|||
case ymlFormat, yamlFormat:
|
||||
return repo.stringYAML()
|
||||
default:
|
||||
return "", ErrInvalidOutputFormat
|
||||
return "", zerr.ErrInvalidOutputFormat
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1336,7 +1026,7 @@ func (img imageStruct) string(format string, maxImgNameLen, maxTagLen, maxPlatfo
|
|||
case ymlFormat, yamlFormat:
|
||||
return img.stringYAML()
|
||||
default:
|
||||
return "", ErrInvalidOutputFormat
|
||||
return "", zerr.ErrInvalidOutputFormat
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1663,7 +1353,7 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig,
|
|||
|
||||
catalog := &catalogResponse{}
|
||||
|
||||
catalogEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s",
|
||||
catalogEndPoint, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("%s%s",
|
||||
constants.RoutePrefix, constants.ExtCatalogPrefix))
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
|
@ -1674,8 +1364,8 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig,
|
|||
return
|
||||
}
|
||||
|
||||
_, err = makeGETRequest(ctx, catalogEndPoint, username, password, *config.verifyTLS,
|
||||
*config.debug, catalog, config.resultWriter)
|
||||
_, err = makeGETRequest(ctx, catalogEndPoint, username, password, config.verifyTLS,
|
||||
config.debug, catalog, config.resultWriter)
|
||||
if err != nil {
|
||||
if isContextDone(ctx) {
|
||||
return
|
||||
|
|
481
pkg/cli/utils.go
Normal file
481
pkg/cli/utils.go
Normal file
|
@ -0,0 +1,481 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
"zotregistry.io/zot/pkg/cli/cmdflags"
|
||||
)
|
||||
|
||||
const (
|
||||
sizeColumn = "SIZE"
|
||||
)
|
||||
|
||||
func ref[T any](input T) *T {
|
||||
ref := input
|
||||
|
||||
return &ref
|
||||
}
|
||||
|
||||
func fetchImageDigest(repo, ref, username, password string, config searchConfig) (string, error) {
|
||||
url, err := combineServerAndEndpointURL(config.servURL, fmt.Sprintf("/v2/%s/manifests/%s", repo, ref))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res, err := makeHEADRequest(context.Background(), url, username, password, config.verifyTLS, false)
|
||||
|
||||
digestStr := res.Get(constants.DistContentDigestKey)
|
||||
|
||||
return digestStr, err
|
||||
}
|
||||
|
||||
func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan stringResult,
|
||||
cancel context.CancelFunc, printHeader printHeader, errCh chan error,
|
||||
) {
|
||||
var foundResult bool
|
||||
|
||||
defer wg.Done()
|
||||
config.spinner.startSpinner()
|
||||
|
||||
for {
|
||||
select {
|
||||
case result, ok := <-imageErr:
|
||||
config.spinner.stopSpinner()
|
||||
|
||||
if !ok {
|
||||
cancel()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if result.Err != nil {
|
||||
cancel()
|
||||
errCh <- result.Err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !foundResult && (config.outputFormat == defaultOutputFormat || config.outputFormat == "") {
|
||||
var builder strings.Builder
|
||||
|
||||
printHeader(&builder, config.verbose, 0, 0, 0)
|
||||
fmt.Fprint(config.resultWriter, builder.String())
|
||||
}
|
||||
|
||||
foundResult = true
|
||||
|
||||
fmt.Fprint(config.resultWriter, result.StrValue)
|
||||
case <-time.After(waitTimeout):
|
||||
config.spinner.stopSpinner()
|
||||
cancel()
|
||||
|
||||
errCh <- zerr.ErrCLITimeout
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUsernameAndPassword(user string) (string, string) {
|
||||
if strings.Contains(user, ":") {
|
||||
split := strings.Split(user, ":")
|
||||
|
||||
return split[0], split[1]
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
type spinnerState struct {
|
||||
spinner *spinner.Spinner
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func (spinner *spinnerState) startSpinner() {
|
||||
if spinner.enabled {
|
||||
spinner.spinner.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (spinner *spinnerState) stopSpinner() {
|
||||
if spinner.enabled && spinner.spinner.Active() {
|
||||
spinner.spinner.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
waitTimeout = httpTimeout + 5*time.Second
|
||||
)
|
||||
|
||||
type stringResult struct {
|
||||
StrValue string
|
||||
Err error
|
||||
}
|
||||
|
||||
type printHeader func(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen, maxPlatformLen int)
|
||||
|
||||
func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen, maxPlatformLen int) {
|
||||
table := getImageTableWriter(writer)
|
||||
|
||||
table.SetColMinWidth(colImageNameIndex, imageNameWidth)
|
||||
table.SetColMinWidth(colTagIndex, tagWidth)
|
||||
table.SetColMinWidth(colPlatformIndex, platformWidth)
|
||||
table.SetColMinWidth(colDigestIndex, digestWidth)
|
||||
table.SetColMinWidth(colSizeIndex, sizeWidth)
|
||||
table.SetColMinWidth(colIsSignedIndex, isSignedWidth)
|
||||
|
||||
if verbose {
|
||||
table.SetColMinWidth(colConfigIndex, configWidth)
|
||||
table.SetColMinWidth(colLayersIndex, layersWidth)
|
||||
}
|
||||
|
||||
row := make([]string, 8) //nolint:gomnd
|
||||
|
||||
// adding spaces so that repository and tag columns are aligned
|
||||
// in case the name/tag are fully shown and too long
|
||||
var offset string
|
||||
if maxImageNameLen > len("REPOSITORY") {
|
||||
offset = strings.Repeat(" ", maxImageNameLen-len("REPOSITORY"))
|
||||
row[colImageNameIndex] = "REPOSITORY" + offset
|
||||
} else {
|
||||
row[colImageNameIndex] = "REPOSITORY"
|
||||
}
|
||||
|
||||
if maxTagLen > len("TAG") {
|
||||
offset = strings.Repeat(" ", maxTagLen-len("TAG"))
|
||||
row[colTagIndex] = "TAG" + offset
|
||||
} else {
|
||||
row[colTagIndex] = "TAG"
|
||||
}
|
||||
|
||||
if maxPlatformLen > len("OS/ARCH") {
|
||||
offset = strings.Repeat(" ", maxPlatformLen-len("OS/ARCH"))
|
||||
row[colPlatformIndex] = "OS/ARCH" + offset
|
||||
} else {
|
||||
row[colPlatformIndex] = "OS/ARCH"
|
||||
}
|
||||
|
||||
row[colDigestIndex] = "DIGEST"
|
||||
row[colSizeIndex] = sizeColumn
|
||||
row[colIsSignedIndex] = "SIGNED"
|
||||
|
||||
if verbose {
|
||||
row[colConfigIndex] = "CONFIG"
|
||||
row[colLayersIndex] = "LAYERS"
|
||||
}
|
||||
|
||||
table.Append(row)
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func printCVETableHeader(writer io.Writer) {
|
||||
table := getCVETableWriter(writer)
|
||||
row := make([]string, 3) //nolint:gomnd
|
||||
row[colCVEIDIndex] = "ID"
|
||||
row[colCVESeverityIndex] = "SEVERITY"
|
||||
row[colCVETitleIndex] = "TITLE"
|
||||
|
||||
table.Append(row)
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func printReferrersTableHeader(config searchConfig, writer io.Writer, maxArtifactTypeLen int) {
|
||||
if config.outputFormat != "" && config.outputFormat != defaultOutputFormat {
|
||||
return
|
||||
}
|
||||
|
||||
table := getReferrersTableWriter(writer)
|
||||
|
||||
table.SetColMinWidth(refArtifactTypeIndex, maxArtifactTypeLen)
|
||||
table.SetColMinWidth(refDigestIndex, digestWidth)
|
||||
table.SetColMinWidth(refSizeIndex, sizeWidth)
|
||||
|
||||
row := make([]string, refRowWidth)
|
||||
|
||||
// adding spaces so that repository and tag columns are aligned
|
||||
// in case the name/tag are fully shown and too long
|
||||
var offset string
|
||||
|
||||
if maxArtifactTypeLen > len("ARTIFACT TYPE") {
|
||||
offset = strings.Repeat(" ", maxArtifactTypeLen-len("ARTIFACT TYPE"))
|
||||
row[refArtifactTypeIndex] = "ARTIFACT TYPE" + offset
|
||||
} else {
|
||||
row[refArtifactTypeIndex] = "ARTIFACT TYPE"
|
||||
}
|
||||
|
||||
row[refDigestIndex] = "DIGEST"
|
||||
row[refSizeIndex] = sizeColumn
|
||||
|
||||
table.Append(row)
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func printRepoTableHeader(writer io.Writer, repoMaxLen, maxTimeLen int, verbose bool) {
|
||||
table := getRepoTableWriter(writer)
|
||||
|
||||
table.SetColMinWidth(repoNameIndex, repoMaxLen)
|
||||
table.SetColMinWidth(repoSizeIndex, sizeWidth)
|
||||
table.SetColMinWidth(repoLastUpdatedIndex, maxTimeLen)
|
||||
table.SetColMinWidth(repoDownloadsIndex, sizeWidth)
|
||||
table.SetColMinWidth(repoStarsIndex, sizeWidth)
|
||||
|
||||
if verbose {
|
||||
table.SetColMinWidth(repoPlatformsIndex, platformWidth)
|
||||
}
|
||||
|
||||
row := make([]string, repoRowWidth)
|
||||
|
||||
// adding spaces so that repository and tag columns are aligned
|
||||
// in case the name/tag are fully shown and too long
|
||||
var offset string
|
||||
|
||||
if repoMaxLen > len("NAME") {
|
||||
offset = strings.Repeat(" ", repoMaxLen-len("NAME"))
|
||||
row[repoNameIndex] = "NAME" + offset
|
||||
} else {
|
||||
row[repoNameIndex] = "NAME"
|
||||
}
|
||||
|
||||
if repoMaxLen > len("LAST UPDATED") {
|
||||
offset = strings.Repeat(" ", repoMaxLen-len("LAST UPDATED"))
|
||||
row[repoLastUpdatedIndex] = "LAST UPDATED" + offset
|
||||
} else {
|
||||
row[repoLastUpdatedIndex] = "LAST UPDATED"
|
||||
}
|
||||
|
||||
row[repoSizeIndex] = sizeColumn
|
||||
row[repoDownloadsIndex] = "DOWNLOADS"
|
||||
row[repoStarsIndex] = "STARS"
|
||||
|
||||
if verbose {
|
||||
row[repoPlatformsIndex] = "PLATFORMS"
|
||||
}
|
||||
|
||||
table.Append(row)
|
||||
table.Render()
|
||||
}
|
||||
|
||||
func printReferrersResult(config searchConfig, referrersList referrersResult, maxArtifactTypeLen int) error {
|
||||
out, err := referrersList.string(config.outputFormat, maxArtifactTypeLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(config.resultWriter, out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printImageResult(config searchConfig, imageList []imageStruct) error {
|
||||
var builder strings.Builder
|
||||
maxImgNameLen := 0
|
||||
maxTagLen := 0
|
||||
maxPlatformLen := 0
|
||||
|
||||
if len(imageList) > 0 {
|
||||
for i := range imageList {
|
||||
if maxImgNameLen < len(imageList[i].RepoName) {
|
||||
maxImgNameLen = len(imageList[i].RepoName)
|
||||
}
|
||||
|
||||
if maxTagLen < len(imageList[i].Tag) {
|
||||
maxTagLen = len(imageList[i].Tag)
|
||||
}
|
||||
|
||||
for j := range imageList[i].Manifests {
|
||||
platform := imageList[i].Manifests[j].Platform.Os + "/" + imageList[i].Manifests[j].Platform.Arch
|
||||
|
||||
if maxPlatformLen < len(platform) {
|
||||
maxPlatformLen = len(platform)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.outputFormat == defaultOutputFormat || config.outputFormat == "" {
|
||||
printImageTableHeader(&builder, config.verbose, maxImgNameLen, maxTagLen, maxPlatformLen)
|
||||
}
|
||||
|
||||
fmt.Fprint(config.resultWriter, builder.String())
|
||||
}
|
||||
|
||||
for i := range imageList {
|
||||
img := imageList[i]
|
||||
verbose := config.verbose
|
||||
|
||||
out, err := img.string(config.outputFormat, maxImgNameLen, maxTagLen, maxPlatformLen, verbose)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(config.resultWriter, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printRepoResults(config searchConfig, repoList []repoStruct) error {
|
||||
maxRepoNameLen := 0
|
||||
maxTimeLen := 0
|
||||
|
||||
for _, repo := range repoList {
|
||||
if maxRepoNameLen < len(repo.Name) {
|
||||
maxRepoNameLen = len(repo.Name)
|
||||
}
|
||||
|
||||
if maxTimeLen < len(repo.LastUpdated.String()) {
|
||||
maxTimeLen = len(repo.LastUpdated.String())
|
||||
}
|
||||
}
|
||||
|
||||
if len(repoList) > 0 && (config.outputFormat == defaultOutputFormat || config.outputFormat == "") {
|
||||
printRepoTableHeader(config.resultWriter, maxRepoNameLen, maxTimeLen, config.verbose)
|
||||
}
|
||||
|
||||
for _, repo := range repoList {
|
||||
out, err := repo.string(config.outputFormat, maxRepoNameLen, maxTimeLen, config.verbose)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(config.resultWriter, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSearchConfigFromFlags(cmd *cobra.Command, searchService SearchService) (searchConfig, error) {
|
||||
serverURL, err := GetServerURLFromFlags(cmd)
|
||||
if err != nil {
|
||||
return searchConfig{}, err
|
||||
}
|
||||
|
||||
isSpinner, verifyTLS, err := GetCliConfigOptions(cmd)
|
||||
if err != nil {
|
||||
return searchConfig{}, err
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
user := defaultIfError(flags.GetString(cmdflags.UserFlag))
|
||||
fixed := defaultIfError(flags.GetBool(cmdflags.FixedFlag))
|
||||
debug := defaultIfError(flags.GetBool(cmdflags.DebugFlag))
|
||||
verbose := defaultIfError(flags.GetBool(cmdflags.VerboseFlag))
|
||||
outputFormat := defaultIfError(flags.GetString(cmdflags.OutputFormatFlag))
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
|
||||
spin.Prefix = prefix
|
||||
|
||||
return searchConfig{
|
||||
searchService: searchService,
|
||||
servURL: serverURL,
|
||||
user: user,
|
||||
outputFormat: outputFormat,
|
||||
verifyTLS: verifyTLS,
|
||||
fixedFlag: fixed,
|
||||
verbose: verbose,
|
||||
debug: debug,
|
||||
spinner: spinnerState{spin, isSpinner},
|
||||
resultWriter: cmd.OutOrStdout(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func defaultIfError[T any](out T, err error) T {
|
||||
var defaultVal T
|
||||
|
||||
if err != nil {
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func GetCliConfigOptions(cmd *cobra.Command) (bool, bool, error) {
|
||||
configName, err := cmd.Flags().GetString(cmdflags.ConfigFlag)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if configName == "" {
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
configDir := path.Join(home, "/.zot")
|
||||
|
||||
isSpinner, err := parseBooleanConfig(configDir, configName, showspinnerConfig)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
verifyTLS, err := parseBooleanConfig(configDir, configName, verifyTLSConfig)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
return isSpinner, verifyTLS, nil
|
||||
}
|
||||
|
||||
func GetServerURLFromFlags(cmd *cobra.Command) (string, error) {
|
||||
serverURL, err := cmd.Flags().GetString(cmdflags.URLFlag)
|
||||
if err == nil && serverURL != "" {
|
||||
return serverURL, nil
|
||||
}
|
||||
|
||||
configName, err := cmd.Flags().GetString(cmdflags.ConfigFlag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if configName == "" {
|
||||
return "", fmt.Errorf("%w: specify either '--%s' or '--%s' flags", zerr.ErrNoURLProvided, cmdflags.URLFlag,
|
||||
cmdflags.ConfigFlag)
|
||||
}
|
||||
|
||||
serverURL, err = ReadServerURLFromConfig(configName)
|
||||
if err != nil {
|
||||
return serverURL, fmt.Errorf("reading url from config failed: %w", err)
|
||||
}
|
||||
|
||||
if serverURL == "" {
|
||||
return "", fmt.Errorf("%w: url field from config is empty", zerr.ErrNoURLProvided)
|
||||
}
|
||||
|
||||
_, err = url.Parse(serverURL)
|
||||
|
||||
return serverURL, err
|
||||
}
|
||||
|
||||
func ReadServerURLFromConfig(configName string) (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
configDir := path.Join(home, "/.zot")
|
||||
|
||||
urlFromConfig, err := getConfigValue(configDir, configName, "url")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return urlFromConfig, nil
|
||||
}
|
|
@ -84,10 +84,12 @@ function teardown_file() {
|
|||
run curl http://127.0.0.1:8080/v2/golang/tags/list
|
||||
[ "$status" -eq 0 ]
|
||||
[ $(echo "${lines[-1]}" | jq '.tags[]') = '"1.20"' ]
|
||||
run ${ZLI_PATH} cve ${REGISTRY_NAME} -I golang:1.20
|
||||
run ${ZLI_PATH} cve list golang:1.20 --config ${REGISTRY_NAME}
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
found=0
|
||||
echo ${lines[@]}
|
||||
|
||||
found=0
|
||||
for i in "${lines[@]}"
|
||||
do
|
||||
|
||||
|
|
Loading…
Reference in a new issue