0
Fork 0
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:
LaurentiuNiculae 2023-09-08 15:12:47 +03:00 committed by GitHub
parent 18e591f52a
commit 7b1e24c99e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1455 additions and 4118 deletions

View file

@ -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")
)

View file

@ -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()))
}

View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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,
}
}

View file

@ -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

View file

@ -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")

View file

@ -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 {

View file

@ -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)
}

View file

@ -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,
}

View file

@ -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

View file

@ -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
}

View file

@ -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
View 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
}

View file

@ -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{

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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())

View file

@ -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)

View file

@ -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
}

File diff suppressed because it is too large Load diff

View file

@ -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
View 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
}

View file

@ -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