0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-30 22:34:13 -05:00

cli: add option to ignore TLS verification

adds a property in config : "verify-tls"
This commit is contained in:
Tanmay Naik 2020-07-17 15:42:22 -04:00
parent e0cdc6b6a4
commit 6285a730a1
6 changed files with 148 additions and 90 deletions

View file

@ -2,6 +2,7 @@ package cli
import ( import (
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
@ -14,17 +15,23 @@ import (
zotErrors "github.com/anuvu/zot/errors" zotErrors "github.com/anuvu/zot/errors"
) )
var httpClient *http.Client = createHTTPClient() //nolint: gochecknoglobals var httpClient *http.Client //nolint: gochecknoglobals
const httpTimeout = 5 * time.Second const httpTimeout = 5 * time.Second
func createHTTPClient() *http.Client { func createHTTPClient(verifyTLS bool) *http.Client {
var tr = http.DefaultTransport.(*http.Transport).Clone()
if !verifyTLS {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint: gosec
}
return &http.Client{ return &http.Client{
Timeout: httpTimeout, Timeout: httpTimeout,
Transport: tr,
} }
} }
func makeGETRequest(url, username, password string, resultsPtr interface{}) (http.Header, error) { func makeGETRequest(url, username, password string, verifyTLS bool, resultsPtr interface{}) (http.Header, error) {
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
@ -33,6 +40,10 @@ func makeGETRequest(url, username, password string, resultsPtr interface{}) (htt
req.SetBasicAuth(username, password) req.SetBasicAuth(username, password)
if httpClient == nil {
httpClient = createHTTPClient(verifyTLS)
}
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -74,9 +85,9 @@ type manifestJob struct {
url string url string
username string username string
password string password string
outputFormat string
imageName string imageName string
tagName string tagName string
config searchConfig
manifestResp manifestResponse manifestResp manifestResponse
} }
@ -116,7 +127,7 @@ func (p *requestsPool) startRateLimiter() {
func (p *requestsPool) doJob(job *manifestJob) { func (p *requestsPool) doJob(job *manifestJob) {
defer p.waitGroup.Done() defer p.waitGroup.Done()
header, err := makeGETRequest(job.url, job.username, job.password, &job.manifestResp) header, err := makeGETRequest(job.url, job.username, job.password, *job.config.verifyTLS, &job.manifestResp)
if err != nil { if err != nil {
if isContextDone(p.context) { if isContextDone(p.context) {
return return
@ -143,7 +154,7 @@ func (p *requestsPool) doJob(job *manifestJob) {
}, },
} }
str, err := image.string(job.outputFormat) str, err := image.string(*job.config.outputFormat)
if err != nil { if err != nil {
if isContextDone(p.context) { if isContextDone(p.context) {
return return

View file

@ -205,6 +205,7 @@ func addConfig(configPath, configName, url string) error {
configMap := make(map[string]interface{}) configMap := make(map[string]interface{})
configMap["url"] = url configMap["url"] = url
configMap[nameKey] = configName configMap[nameKey] = configName
addDefaultConfigs(configMap)
configs = append(configs, configMap) configs = append(configs, configMap)
err = saveConfigMapToFile(configPath, configs) err = saveConfigMapToFile(configPath, configs)
@ -215,6 +216,16 @@ func addConfig(configPath, configName, url string) error {
return nil return nil
} }
func addDefaultConfigs(config map[string]interface{}) {
if _, ok := config[showspinnerConfig]; !ok {
config[showspinnerConfig] = true
}
if _, ok := config[verifyTLSConfig]; !ok {
config[verifyTLSConfig] = true
}
}
func getConfigValue(configPath, configName, key string) (string, error) { func getConfigValue(configPath, configName, key string) (string, error) {
configs, err := getConfigMapFromFile(configPath) configs, err := getConfigMapFromFile(configPath)
if err != nil { if err != nil {
@ -227,6 +238,7 @@ func getConfigValue(configPath, configName, key string) (string, error) {
for _, val := range configs { for _, val := range configs {
configMap := val.(map[string]interface{}) configMap := val.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey] name := configMap[nameKey]
if name == configName { if name == configName {
@ -257,6 +269,7 @@ func resetConfigValue(configPath, configName, key string) error {
for _, val := range configs { for _, val := range configs {
configMap := val.(map[string]interface{}) configMap := val.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey] name := configMap[nameKey]
if name == configName { if name == configName {
@ -290,6 +303,7 @@ func setConfigValue(configPath, configName, key, value string) error {
for _, val := range configs { for _, val := range configs {
configMap := val.(map[string]interface{}) configMap := val.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey] name := configMap[nameKey]
if name == configName { if name == configName {
@ -326,6 +340,7 @@ func getAllConfig(configPath, configName string) (string, error) {
for _, value := range configs { for _, value := range configs {
configMap := value.(map[string]interface{}) configMap := value.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey] name := configMap[nameKey]
if name == configName { if name == configName {
@ -353,7 +368,8 @@ const (
supportedOptions = ` supportedOptions = `
Useful variables: Useful variables:
url zot server URL url zot server URL
showspinner show spinner while loading data [true/false]` showspinner show spinner while loading data [true/false]
verify-tls verify TLS Certificate verification of the server [default: true]`
nameKey = "_name" nameKey = "_name"
@ -361,6 +377,9 @@ Useful variables:
oneArg = 1 oneArg = 1
twoArgs = 2 twoArgs = 2
threeArgs = 3 threeArgs = 3
showspinnerConfig = "showspinner"
verifyTLSConfig = "verify-tls"
) )
var ( var (

View file

@ -14,11 +14,9 @@ import (
func NewImageCommand(searchService ImageSearchService) *cobra.Command { func NewImageCommand(searchService ImageSearchService) *cobra.Command {
searchImageParams := make(map[string]*string) searchImageParams := make(map[string]*string)
var servURL string var servURL, user, outputFormat string
var user string var isSpinner, verifyTLS bool
var outputFormat string
var imageCmd = &cobra.Command{ var imageCmd = &cobra.Command{
Use: "images [config-name]", Use: "images [config-name]",
@ -47,20 +45,35 @@ func NewImageCommand(searchService ImageSearchService) *cobra.Command {
} }
} }
var isSpinner bool
if len(args) > 0 { if len(args) > 0 {
var err error var err error
isSpinner, err = isSpinnerEnabled(configPath, args[0]) 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 { if err != nil {
cmd.SilenceUsage = true cmd.SilenceUsage = true
return err return err
} }
} else {
isSpinner = true
} }
err = searchImage(cmd, searchImageParams, searchService, &servURL, &user, &outputFormat, isSpinner) spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
spin.Prefix = "Searching... "
searchConfig := searchConfig{
params: searchImageParams,
searchService: searchService,
servURL: &servURL,
user: &user,
outputFormat: &outputFormat,
spinner: spinnerState{spin, isSpinner},
verifyTLS: &verifyTLS,
resultWriter: cmd.OutOrStdout(),
}
err = searchImage(searchConfig)
if err != nil { if err != nil {
cmd.SilenceUsage = true cmd.SilenceUsage = true
@ -77,22 +90,18 @@ func NewImageCommand(searchService ImageSearchService) *cobra.Command {
return imageCmd return imageCmd
} }
func isSpinnerEnabled(configPath, configName string) (bool, error) { func parseBooleanConfig(configPath, configName, configParam string) (bool, error) {
spinnerConfig, err := getConfigValue(configPath, configName, "showspinner") config, err := getConfigValue(configPath, configName, configParam)
if err != nil { if err != nil {
return false, err return false, err
} }
if spinnerConfig == "" { val, err := strconv.ParseBool(config)
return true, nil // spinner is enabled by default
}
isSpinner, err := strconv.ParseBool(spinnerConfig)
if err != nil { if err != nil {
return false, err return false, err
} }
return isSpinner, nil return val, nil
} }
func setupCmdFlags(imageCmd *cobra.Command, searchImageParams map[string]*string, servURL, user, outputFormat *string) { func setupCmdFlags(imageCmd *cobra.Command, searchImageParams map[string]*string, servURL, user, outputFormat *string) {
@ -103,14 +112,9 @@ func setupCmdFlags(imageCmd *cobra.Command, searchImageParams map[string]*string
imageCmd.Flags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]") imageCmd.Flags().StringVarP(outputFormat, "output", "o", "", "Specify output format [text/json/yaml]")
} }
func searchImage(cmd *cobra.Command, params map[string]*string, func searchImage(searchConfig searchConfig) error {
service ImageSearchService, servURL, user, outputFormat *string, isSpinner bool) error {
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
spin.Prefix = "Searching... "
for _, searcher := range getSearchers() { for _, searcher := range getSearchers() {
found, err := searcher.search(params, service, servURL, user, outputFormat, found, err := searcher.search(searchConfig)
cmd.OutOrStdout(), spinnerState{spin, isSpinner})
if found { if found {
if err != nil { if err != nil {
return err return err

View file

@ -439,8 +439,8 @@ func uploadManifest(url string) {
type mockService struct{} type mockService struct{}
func (service mockService) getAllImages(ctx context.Context, serverURL, username, password, func (service mockService) getAllImages(ctx context.Context, config searchConfig, username, password string,
outputFormat string, channel chan imageListResult, wg *sync.WaitGroup) { channel chan imageListResult, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
image := &imageStruct{} image := &imageStruct{}
@ -453,7 +453,7 @@ func (service mockService) getAllImages(ctx context.Context, serverURL, username
}, },
} }
str, err := image.string(outputFormat) str, err := image.string(*config.outputFormat)
if err != nil { if err != nil {
channel <- imageListResult{"", err} channel <- imageListResult{"", err}
return return
@ -461,8 +461,8 @@ func (service mockService) getAllImages(ctx context.Context, serverURL, username
channel <- imageListResult{str, nil} channel <- imageListResult{str, nil}
} }
func (service mockService) getImageByName(ctx context.Context, serverURL, username, password, func (service mockService) getImageByName(ctx context.Context, config searchConfig,
imageName, outputFormat string, channel chan imageListResult, wg *sync.WaitGroup) { username, password, imageName string, channel chan imageListResult, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
image := &imageStruct{} image := &imageStruct{}
@ -475,7 +475,7 @@ func (service mockService) getImageByName(ctx context.Context, serverURL, userna
}, },
} }
str, err := image.string(outputFormat) str, err := image.string(*config.outputFormat)
if err != nil { if err != nil {
channel <- imageListResult{"", err} channel <- imageListResult{"", err}
return return

View file

@ -22,8 +22,7 @@ func getSearchers() []searcher {
} }
type searcher interface { type searcher interface {
search(params map[string]*string, searchService ImageSearchService, search(searchConfig searchConfig) (bool, error)
servURL, user, outputFormat *string, stdWriter io.Writer, spinner spinnerState) (bool, error)
} }
func canSearch(params map[string]*string, requiredParams *set) bool { func canSearch(params map[string]*string, requiredParams *set) bool {
@ -38,15 +37,25 @@ func canSearch(params map[string]*string, requiredParams *set) bool {
return true return true
} }
type searchConfig struct {
params map[string]*string
searchService ImageSearchService
servURL *string
user *string
outputFormat *string
verifyTLS *bool
resultWriter io.Writer
spinner spinnerState
}
type allImagesSearcher struct{} type allImagesSearcher struct{}
func (search allImagesSearcher) search(params map[string]*string, searchService ImageSearchService, func (search allImagesSearcher) search(config searchConfig) (bool, error) {
servURL, user, outputFormat *string, stdWriter io.Writer, spinner spinnerState) (bool, error) { if !canSearch(config.params, newSet("")) {
if !canSearch(params, newSet("")) {
return false, nil return false, nil
} }
username, password := getUsernameAndPassword(*user) username, password := getUsernameAndPassword(*config.user)
imageErr := make(chan imageListResult) imageErr := make(chan imageListResult)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -54,12 +63,12 @@ func (search allImagesSearcher) search(params map[string]*string, searchService
wg.Add(1) wg.Add(1)
go searchService.getAllImages(ctx, *servURL, username, password, *outputFormat, imageErr, &wg) go config.searchService.getAllImages(ctx, config, username, password, imageErr, &wg)
wg.Add(1) wg.Add(1)
var errCh chan error = make(chan error, 1) var errCh chan error = make(chan error, 1)
go collectImages(outputFormat, stdWriter, &wg, imageErr, cancel, spinner, errCh) go collectImages(config, &wg, imageErr, cancel, errCh)
wg.Wait() wg.Wait()
select { select {
case err := <-errCh: case err := <-errCh:
@ -71,14 +80,12 @@ func (search allImagesSearcher) search(params map[string]*string, searchService
type imageByNameSearcher struct{} type imageByNameSearcher struct{}
func (search imageByNameSearcher) search(params map[string]*string, func (search imageByNameSearcher) search(config searchConfig) (bool, error) {
searchService ImageSearchService, servURL, user, outputFormat *string, if !canSearch(config.params, newSet("imageName")) {
stdWriter io.Writer, spinner spinnerState) (bool, error) {
if !canSearch(params, newSet("imageName")) {
return false, nil return false, nil
} }
username, password := getUsernameAndPassword(*user) username, password := getUsernameAndPassword(*config.user)
imageErr := make(chan imageListResult) imageErr := make(chan imageListResult)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -86,11 +93,11 @@ func (search imageByNameSearcher) search(params map[string]*string,
wg.Add(1) wg.Add(1)
go searchService.getImageByName(ctx, *servURL, username, password, *params["imageName"], *outputFormat, imageErr, &wg) go config.searchService.getImageByName(ctx, config, username, password, *config.params["imageName"], imageErr, &wg)
wg.Add(1) wg.Add(1)
var errCh chan error = make(chan error, 1) var errCh chan error = make(chan error, 1)
go collectImages(outputFormat, stdWriter, &wg, imageErr, cancel, spinner, errCh) go collectImages(config, &wg, imageErr, cancel, errCh)
wg.Wait() wg.Wait()
@ -102,38 +109,44 @@ func (search imageByNameSearcher) search(params map[string]*string,
} }
} }
func collectImages(outputFormat *string, stdWriter io.Writer, wg *sync.WaitGroup, func collectImages(config searchConfig, wg *sync.WaitGroup, imageErr chan imageListResult,
imageErr chan imageListResult, cancel context.CancelFunc, spinner spinnerState, errCh chan error) { cancel context.CancelFunc, errCh chan error) {
var foundResult bool var foundResult bool
defer wg.Done() defer wg.Done()
spinner.startSpinner() config.spinner.startSpinner()
for { for {
select { select {
case result := <-imageErr: case result, ok := <-imageErr:
config.spinner.stopSpinner()
if !ok {
cancel()
return
}
if result.Err != nil { if result.Err != nil {
spinner.stopSpinner()
cancel() cancel()
errCh <- result.Err errCh <- result.Err
return return
} }
spinner.stopSpinner() if !foundResult && (*config.outputFormat == "text" || *config.outputFormat == "") {
if !foundResult && (*outputFormat == "text" || *outputFormat == "") {
var builder strings.Builder var builder strings.Builder
printImageTableHeader(&builder) printImageTableHeader(&builder)
fmt.Fprint(stdWriter, builder.String()) fmt.Fprint(config.resultWriter, builder.String())
} }
foundResult = true foundResult = true
fmt.Fprint(stdWriter, result.StrValue) fmt.Fprint(config.resultWriter, result.StrValue)
case <-time.After(waitTimeout): case <-time.After(waitTimeout):
cancel() cancel()
config.spinner.stopSpinner()
return return
} }
} }
@ -211,5 +224,5 @@ func printImageTableHeader(writer io.Writer) {
} }
const ( const (
waitTimeout = 2 * time.Second waitTimeout = 6 * time.Second
) )

View file

@ -17,9 +17,9 @@ import (
) )
type ImageSearchService interface { type ImageSearchService interface {
getAllImages(ctx context.Context, serverURL, username, password, getAllImages(ctx context.Context, config searchConfig, username, password string,
outputFormat string, channel chan imageListResult, wg *sync.WaitGroup) channel chan imageListResult, wg *sync.WaitGroup)
getImageByName(ctx context.Context, serverURL, username, password, imageName, outputFormat string, getImageByName(ctx context.Context, config searchConfig, username, password, imageName string,
channel chan imageListResult, wg *sync.WaitGroup) channel chan imageListResult, wg *sync.WaitGroup)
} }
type searchService struct{} type searchService struct{}
@ -28,27 +28,32 @@ func NewImageSearchService() ImageSearchService {
return searchService{} return searchService{}
} }
func (service searchService) getImageByName(ctx context.Context, url, username, password, func (service searchService) getImageByName(ctx context.Context, config searchConfig,
imageName, outputFormat string, c chan imageListResult, wg *sync.WaitGroup) { username, password, imageName string, c chan imageListResult, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
defer close(c)
p := newSmoothRateLimiter(ctx, wg, c) var localWg sync.WaitGroup
p := newSmoothRateLimiter(ctx, &localWg, c)
wg.Add(1) localWg.Add(1)
go p.startRateLimiter() go p.startRateLimiter()
wg.Add(1) localWg.Add(1)
go getImage(ctx, url, username, password, imageName, outputFormat, c, wg, p) go getImage(ctx, config, username, password, imageName, c, &localWg, p)
localWg.Wait()
} }
func (service searchService) getAllImages(ctx context.Context, url, username, password, func (service searchService) getAllImages(ctx context.Context, config searchConfig, username, password string,
outputFormat string, c chan imageListResult, wg *sync.WaitGroup) { c chan imageListResult, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
defer close(c)
catalog := &catalogResponse{} catalog := &catalogResponse{}
catalogEndPoint, err := combineServerAndEndpointURL(url, "/v2/_catalog") catalogEndPoint, err := combineServerAndEndpointURL(*config.servURL, "/v2/_catalog")
if err != nil { if err != nil {
if isContextDone(ctx) { if isContextDone(ctx) {
return return
@ -58,7 +63,7 @@ func (service searchService) getAllImages(ctx context.Context, url, username, pa
return return
} }
_, err = makeGETRequest(catalogEndPoint, username, password, catalog) _, err = makeGETRequest(catalogEndPoint, username, password, *config.verifyTLS, catalog)
if err != nil { if err != nil {
if isContextDone(ctx) { if isContextDone(ctx) {
return return
@ -68,23 +73,28 @@ func (service searchService) getAllImages(ctx context.Context, url, username, pa
return return
} }
p := newSmoothRateLimiter(ctx, wg, c) var localWg sync.WaitGroup
wg.Add(1) p := newSmoothRateLimiter(ctx, &localWg, c)
localWg.Add(1)
go p.startRateLimiter() go p.startRateLimiter()
for _, repo := range catalog.Repositories { for _, repo := range catalog.Repositories {
wg.Add(1) localWg.Add(1)
go getImage(ctx, url, username, password, repo, outputFormat, c, wg, p) go getImage(ctx, config, username, password, repo, c, &localWg, p)
} }
localWg.Wait()
} }
func getImage(ctx context.Context, url, username, password, imageName, outputFormat string,
func getImage(ctx context.Context, config searchConfig, username, password, imageName string,
c chan imageListResult, wg *sync.WaitGroup, pool *requestsPool) { c chan imageListResult, wg *sync.WaitGroup, pool *requestsPool) {
defer wg.Done() defer wg.Done()
tagListEndpoint, err := combineServerAndEndpointURL(url, fmt.Sprintf("/v2/%s/tags/list", imageName)) tagListEndpoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("/v2/%s/tags/list", imageName))
if err != nil { if err != nil {
if isContextDone(ctx) { if isContextDone(ctx) {
return return
@ -95,7 +105,7 @@ func getImage(ctx context.Context, url, username, password, imageName, outputFor
} }
tagsList := &tagListResp{} tagsList := &tagListResp{}
_, err = makeGETRequest(tagListEndpoint, username, password, &tagsList) _, err = makeGETRequest(tagListEndpoint, username, password, *config.verifyTLS, &tagsList)
if err != nil { if err != nil {
if isContextDone(ctx) { if isContextDone(ctx) {
@ -109,7 +119,7 @@ func getImage(ctx context.Context, url, username, password, imageName, outputFor
for _, tag := range tagsList.Tags { for _, tag := range tagsList.Tags {
wg.Add(1) wg.Add(1)
go addManifestCallToPool(ctx, pool, url, username, password, imageName, tag, outputFormat, c, wg) go addManifestCallToPool(ctx, config, pool, username, password, imageName, tag, c, wg)
} }
} }
@ -122,13 +132,14 @@ func isContextDone(ctx context.Context) bool {
} }
} }
func addManifestCallToPool(ctx context.Context, p *requestsPool, url, username, password, imageName, func addManifestCallToPool(ctx context.Context, config searchConfig, p *requestsPool, username, password, imageName,
tagName, outputFormat string, c chan imageListResult, wg *sync.WaitGroup) { tagName string, c chan imageListResult, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
resultManifest := manifestResponse{} resultManifest := manifestResponse{}
manifestEndpoint, err := combineServerAndEndpointURL(url, fmt.Sprintf("/v2/%s/manifests/%s", imageName, tagName)) manifestEndpoint, err := combineServerAndEndpointURL(*config.servURL,
fmt.Sprintf("/v2/%s/manifests/%s", imageName, tagName))
if err != nil { if err != nil {
if isContextDone(ctx) { if isContextDone(ctx) {
return return
@ -143,7 +154,7 @@ func addManifestCallToPool(ctx context.Context, p *requestsPool, url, username,
password: password, password: password,
tagName: tagName, tagName: tagName,
manifestResp: resultManifest, manifestResp: resultManifest,
outputFormat: outputFormat, config: config,
} }
wg.Add(1) wg.Add(1)