0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

Merge pull request #118 from tsnaik/cli-tls-verify

cli: add option to ignore TLS verification
This commit is contained in:
Ramkumar Chinchani 2020-07-17 15:53:09 -07:00 committed by GitHub
commit b2ef9ab124
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 148 additions and 90 deletions

View file

@ -2,6 +2,7 @@ package cli
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"io/ioutil"
@ -14,17 +15,23 @@ import (
zotErrors "github.com/anuvu/zot/errors"
)
var httpClient *http.Client = createHTTPClient() //nolint: gochecknoglobals
var httpClient *http.Client //nolint: gochecknoglobals
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{
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)
if err != nil {
@ -33,6 +40,10 @@ func makeGETRequest(url, username, password string, resultsPtr interface{}) (htt
req.SetBasicAuth(username, password)
if httpClient == nil {
httpClient = createHTTPClient(verifyTLS)
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
@ -74,9 +85,9 @@ type manifestJob struct {
url string
username string
password string
outputFormat string
imageName string
tagName string
config searchConfig
manifestResp manifestResponse
}
@ -116,7 +127,7 @@ func (p *requestsPool) startRateLimiter() {
func (p *requestsPool) doJob(job *manifestJob) {
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 isContextDone(p.context) {
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 isContextDone(p.context) {
return

View file

@ -205,6 +205,7 @@ func addConfig(configPath, configName, url string) error {
configMap := make(map[string]interface{})
configMap["url"] = url
configMap[nameKey] = configName
addDefaultConfigs(configMap)
configs = append(configs, configMap)
err = saveConfigMapToFile(configPath, configs)
@ -215,6 +216,16 @@ func addConfig(configPath, configName, url string) error {
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) {
configs, err := getConfigMapFromFile(configPath)
if err != nil {
@ -227,6 +238,7 @@ func getConfigValue(configPath, configName, key string) (string, error) {
for _, val := range configs {
configMap := val.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey]
if name == configName {
@ -257,6 +269,7 @@ func resetConfigValue(configPath, configName, key string) error {
for _, val := range configs {
configMap := val.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey]
if name == configName {
@ -290,6 +303,7 @@ func setConfigValue(configPath, configName, key, value string) error {
for _, val := range configs {
configMap := val.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey]
if name == configName {
@ -326,6 +340,7 @@ func getAllConfig(configPath, configName string) (string, error) {
for _, value := range configs {
configMap := value.(map[string]interface{})
addDefaultConfigs(configMap)
name := configMap[nameKey]
if name == configName {
@ -353,7 +368,8 @@ const (
supportedOptions = `
Useful variables:
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"
@ -361,6 +377,9 @@ Useful variables:
oneArg = 1
twoArgs = 2
threeArgs = 3
showspinnerConfig = "showspinner"
verifyTLSConfig = "verify-tls"
)
var (

View file

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

View file

@ -439,8 +439,8 @@ func uploadManifest(url string) {
type mockService struct{}
func (service mockService) getAllImages(ctx context.Context, serverURL, username, password,
outputFormat string, channel chan imageListResult, wg *sync.WaitGroup) {
func (service mockService) getAllImages(ctx context.Context, config searchConfig, username, password string,
channel chan imageListResult, wg *sync.WaitGroup) {
defer wg.Done()
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 {
channel <- imageListResult{"", err}
return
@ -461,8 +461,8 @@ func (service mockService) getAllImages(ctx context.Context, serverURL, username
channel <- imageListResult{str, nil}
}
func (service mockService) getImageByName(ctx context.Context, serverURL, username, password,
imageName, outputFormat string, channel chan imageListResult, wg *sync.WaitGroup) {
func (service mockService) getImageByName(ctx context.Context, config searchConfig,
username, password, imageName string, channel chan imageListResult, wg *sync.WaitGroup) {
defer wg.Done()
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 {
channel <- imageListResult{"", err}
return

View file

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

View file

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