resolve conflicts

Merge branch 'master' of https://github.com/helm/helm

Signed-off-by: Sarvani Mounika sarvanimounika@gmail.com
pull/7728/head
yakkala sarvani mounika 5 years ago
commit 22f971417b

@ -232,8 +232,9 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan
3. Assigning reviews
- Once a review has the `awaiting review` label, maintainers will review them as schedule
permits. The maintainer who takes the issue should self-request a review.
- Any PR with the `size/large` label requires 2 review approvals from maintainers before it can
be merged. Those with `size/medium` or `size/small` are per the judgement of the maintainers.
- PRs from a community member with the label `size/S` or larger requires 2 review approvals from
maintainers before it can be merged. Those with `size/XS` are per the judgement of the
maintainers. For more detail see the [Size Labels](#size-labels) section.
4. Reviewing/Discussion
- All reviews will be completed using Github review tool.
- A "Comment" review should be used when there are questions about the code that should be
@ -313,15 +314,16 @@ makes 30 lines of changes in 1 file, but it changes key functionality, it will l
feature, but requires another 150 lines of tests to cover all cases, could be labeled as `size/S`
even though the number of lines is greater than defined below.
PRs submitted by a core maintainer, regardless of size, only requires approval from one additional
maintainer. This ensures there are at least two maintainers who are aware of any significant PRs
introduced to the codebase.
Any changes from the community labeled as `size/S` or larger should be thoroughly tested before
merging and always requires approval from 2 core maintainers. PRs submitted by a core maintainer,
regardless of size, only requires approval from one additional maintainer. This ensures there are at
least two maintainers who are aware of any significant PRs introduced to the codebase.
| Label | Description |
| ----- | ----------- |
| `size/XS` | Denotes a PR that changes 0-9 lines, ignoring generated files. Very little testing may be required depending on the change. |
| `size/S` | Denotes a PR that changes 10-29 lines, ignoring generated files. Only small amounts of manual testing may be required. |
| `size/M` | Denotes a PR that changes 30-99 lines, ignoring generated files. Manual validation should be required. |
| `size/L` | Denotes a PR that changes 100-499 lines, ignoring generated files. This should be thoroughly tested before merging and always requires 2 approvals. |
| `size/XL` | Denotes a PR that changes 500-999 lines, ignoring generated files. This should be thoroughly tested before merging and always requires 2 approvals. |
| `size/XXL` | Denotes a PR that changes 1000+ lines, ignoring generated files. This should be thoroughly tested before merging and always requires 2 approvals. |
| `size/L` | Denotes a PR that changes 100-499 lines, ignoring generated files. |
| `size/XL` | Denotes a PR that changes 500-999 lines, ignoring generated files. |
| `size/XXL` | Denotes a PR that changes 1000+ lines, ignoring generated files. |

@ -1,4 +1,5 @@
BINDIR := $(CURDIR)/bin
INSTALL_PATH ?= /usr/local/bin
DIST_DIRS := find * -type d -exec
TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum
@ -49,6 +50,7 @@ endif
LDFLAGS += -X helm.sh/helm/v3/internal/version.metadata=${VERSION_METADATA}
LDFLAGS += -X helm.sh/helm/v3/internal/version.gitCommit=${GIT_COMMIT}
LDFLAGS += -X helm.sh/helm/v3/internal/version.gitTreeState=${GIT_DIRTY}
LDFLAGS += $(EXT_LDFLAGS)
.PHONY: all
all: build
@ -62,6 +64,13 @@ build: $(BINDIR)/$(BINNAME)
$(BINDIR)/$(BINNAME): $(SRC)
GO111MODULE=on go build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
# ------------------------------------------------------------------------------
# install
.PHONY: install
install: build
@install "$(BINDIR)/$(BINNAME)" "$(INSTALL_PATH)/$(BINNAME)"
# ------------------------------------------------------------------------------
# test
@ -156,7 +165,7 @@ fetch-dist:
.PHONY: sign
sign:
for f in _dist/*.{gz,zip,sha256,sha256sum} ; do \
for f in $$(ls _dist/*.{gz,zip,sha256,sha256sum} 2>/dev/null) ; do \
gpg --armor --detach-sign $${f} ; \
done
@ -169,7 +178,7 @@ sign:
# removed in Helm v4.
.PHONY: checksum
checksum:
for f in _dist/*.{gz,zip} ; do \
for f in $$(ls _dist/*.{gz,zip} 2>/dev/null) ; do \
shasum -a 256 "$${f}" | sed 's/_dist\///' > "$${f}.sha256sum" ; \
shasum -a 256 "$${f}" | awk '{print $$1}' > "$${f}.sha256" ; \
done

@ -107,6 +107,7 @@ func (o *createOptions) run(out io.Writer) error {
return chartutil.CreateFrom(cfile, filepath.Dir(o.name), lstarter)
}
chartutil.Stderr = out
_, err := chartutil.Create(chartname, filepath.Dir(o.name))
return err
}

@ -28,7 +28,7 @@ import (
)
func TestDependencyBuildCmd(t *testing.T) {
srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
defer srv.Stop()
if err != nil {
t.Fatal(err)

@ -33,7 +33,7 @@ import (
)
func TestDependencyUpdateCmd(t *testing.T) {
srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
if err != nil {
t.Fatal(err)
}
@ -121,7 +121,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
defer resetEnv()()
defer ensure.HelmHome(t)()
srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
if err != nil {
t.Fatal(err)
}

@ -17,6 +17,7 @@ limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"path/filepath"
@ -24,6 +25,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/klog"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
@ -69,7 +71,7 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
formatNames = append(formatNames, format)
}
}
return formatNames, cobra.ShellCompDirectiveDefault
return formatNames, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
@ -155,3 +157,24 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
return versions, cobra.ShellCompDirectiveNoFileComp
}
// addKlogFlags adds flags from k8s.io/klog
// marks the flags as hidden to avoid polluting the help text
func addKlogFlags(fs *pflag.FlagSet) {
local := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(local)
local.VisitAll(func(fl *flag.Flag) {
fl.Name = normalize(fl.Name)
if fs.Lookup(fl.Name) != nil {
return
}
newflag := pflag.PFlagFromGoFlag(fl)
newflag.Hidden = true
fs.AddFlag(newflag)
})
}
// normalize replaces underscores with hyphens
func normalize(s string) string {
return strings.ReplaceAll(s, "_", "-")
}

@ -17,7 +17,6 @@ limitations under the License.
package main // import "helm.sh/helm/v3/cmd/helm"
import (
"flag"
"fmt"
"io/ioutil"
"log"
@ -25,8 +24,6 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/klog"
"sigs.k8s.io/yaml"
// Import to initialize client auth plugins.
@ -61,17 +58,7 @@ func warning(format string, v ...interface{}) {
fmt.Fprintf(os.Stderr, format, v...)
}
func initKubeLogs() {
pflag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc)
gofs := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(gofs)
pflag.CommandLine.AddGoFlagSet(gofs)
pflag.CommandLine.Set("logtostderr", "true")
}
func main() {
initKubeLogs()
actionConfig := new(action.Configuration)
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
if err != nil {
@ -101,11 +88,6 @@ func main() {
}
}
// wordSepNormalizeFunc changes all flags that contain "_" separators
func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
return pflag.NormalizedName(strings.ReplaceAll(name, "_", "-"))
}
func checkOCIFeatureGate() func(_ *cobra.Command, _ []string) error {
return func(_ *cobra.Command, _ []string) error {
if !FeatureGateOCI.IsEnabled() {

@ -59,7 +59,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
found, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
fmt.Fprintf(os.Stderr, "failed to load plugins: %s\n", err)
return
}
@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{}
unknown := []string{}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--registry-config", "--repository-cache", "--repository-config"}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--registry-config", "--repository-cache", "--repository-config"}
knownArg := func(a string) bool {
for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") {

@ -114,6 +114,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package")
f.StringVar(&client.Key, "key", "", "name of the key to use when signing. Used if --sign is true")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring")
f.StringVar(&client.PassphraseFile, "passphrase-file", "", `location of a file which contains the passphrase for the signing key. Use "-" in order to read from stdin.`)
f.StringVar(&client.Version, "version", "", "set the version on the chart to this semver version")
f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")

@ -19,6 +19,7 @@ import (
"fmt"
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
@ -81,7 +82,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error {
debug("loading plugin from %s", i.Path())
p, err := plugin.LoadDir(i.Path())
if err != nil {
return err
return errors.Wrap(err, "plugin is installed but unusable")
}
if err := runHook(p, plugin.Install); err != nil {

@ -37,6 +37,9 @@ func TestManuallyProcessArgs(t *testing.T) {
"--kubeconfig", "/home/foo",
"--kube-context=test1",
"--kube-context", "test1",
"--kube-as-user", "pikachu",
"--kube-as-group", "teatime",
"--kube-as-group", "admins",
"-n=test2",
"-n", "test2",
"--namespace=test2",
@ -51,6 +54,9 @@ func TestManuallyProcessArgs(t *testing.T) {
"--kubeconfig", "/home/foo",
"--kube-context=test1",
"--kube-context", "test1",
"--kube-as-user", "pikachu",
"--kube-as-group", "teatime",
"--kube-as-group", "admins",
"-n=test2",
"-n", "test2",
"--namespace=test2",

@ -26,7 +26,7 @@ import (
)
func TestPullCmd(t *testing.T) {
srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
if err != nil {
t.Fatal(err)
}

@ -104,7 +104,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
}
}
} else {
fmt.Fprintln(os.Stderr, "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
warning("Using --password via the CLI is insecure. Use --password-stdin.")
}
return username, password, nil

@ -38,11 +38,11 @@ import (
)
type repoAddOptions struct {
name string
url string
username string
password string
noUpdate bool
name string
url string
username string
password string
forceUpdate bool
certFile string
keyFile string
@ -51,6 +51,9 @@ type repoAddOptions struct {
repoFile string
repoCache string
// Deprecated, but cannot be removed until Helm 4
deprecatedNoUpdate bool
}
func newRepoAddCmd(out io.Writer) *cobra.Command {
@ -74,7 +77,8 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
f.StringVar(&o.username, "username", "", "chart repository username")
f.StringVar(&o.password, "password", "", "chart repository password")
f.BoolVar(&o.noUpdate, "no-update", false, "raise error if repo is already registered")
f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists")
f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.")
f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
@ -112,10 +116,6 @@ func (o *repoAddOptions) run(out io.Writer) error {
return err
}
if o.noUpdate && f.Has(o.name) {
return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name)
}
if o.username != "" && o.password == "" {
fd := int(os.Stdin.Fd())
fmt.Fprint(out, "Password: ")
@ -138,6 +138,23 @@ func (o *repoAddOptions) run(out io.Writer) error {
InsecureSkipTLSverify: o.insecureSkipTLSverify,
}
// If the repo exists do one of two things:
// 1. If the configuration for the name is the same continue without error
// 2. When the config is different require --force-update
if !o.forceUpdate && f.Has(o.name) {
existing := f.Get(o.name)
if c != *existing {
// The input coming in for the name is different from what is already
// configured. Return an error.
return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name)
}
// The add is idempotent so do nothing
fmt.Fprintf(out, "%q already exists with the same configuration, skipping\n", o.name)
return nil
}
r, err := repo.NewChartRepository(&c, getter.All(settings))
if err != nil {
return err

@ -34,26 +34,50 @@ import (
)
func TestRepoAddCmd(t *testing.T) {
srv, err := repotest.NewTempServer("testdata/testserver/*.*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer srv.Stop()
// A second test server is setup to verify URL changing
srv2, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
defer srv2.Stop()
tmpdir := ensure.TempDir(t)
repoFile := filepath.Join(tmpdir, "repositories.yaml")
tests := []cmdTestCase{{
name: "add a repository",
cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
golden: "output/repo-add.txt",
}}
tests := []cmdTestCase{
{
name: "add a repository",
cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
golden: "output/repo-add.txt",
},
{
name: "add repository second time",
cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
golden: "output/repo-add2.txt",
},
{
name: "add repository different url",
cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv2.URL(), repoFile, tmpdir),
wantError: true,
},
{
name: "add repository second time",
cmd: fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s --force-update", srv2.URL(), repoFile, tmpdir),
golden: "output/repo-add.txt",
},
}
runTestCmd(t, tests)
}
func TestRepoAdd(t *testing.T) {
ts, err := repotest.NewTempServer("testdata/testserver/*.*")
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
@ -65,10 +89,11 @@ func TestRepoAdd(t *testing.T) {
const testRepoName = "test-name"
o := &repoAddOptions{
name: testRepoName,
url: ts.URL(),
noUpdate: true,
repoFile: repoFile,
name: testRepoName,
url: ts.URL(),
forceUpdate: false,
deprecatedNoUpdate: true,
repoFile: repoFile,
}
os.Setenv(xdg.CacheHomeEnvVar, rootDir)
@ -94,7 +119,7 @@ func TestRepoAdd(t *testing.T) {
t.Errorf("Error cache charts file was not created for repository %s", testRepoName)
}
o.noUpdate = false
o.forceUpdate = true
if err := o.run(ioutil.Discard); err != nil {
t.Errorf("Repository was not updated: %s", err)
@ -118,7 +143,7 @@ func TestRepoAddConcurrentDirNotExist(t *testing.T) {
}
func repoAddConcurrent(t *testing.T, testName, repoFile string) {
ts, err := repotest.NewTempServer("testdata/testserver/*.*")
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}
@ -130,10 +155,11 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) {
go func(name string) {
defer wg.Done()
o := &repoAddOptions{
name: name,
url: ts.URL(),
noUpdate: true,
repoFile: repoFile,
name: name,
url: ts.URL(),
deprecatedNoUpdate: true,
forceUpdate: false,
repoFile: repoFile,
}
if err := o.run(ioutil.Discard); err != nil {
t.Error(err)

@ -30,7 +30,7 @@ import (
)
func TestRepoRemove(t *testing.T) {
ts, err := repotest.NewTempServer("testdata/testserver/*.*")
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}

@ -75,7 +75,7 @@ func TestUpdateCharts(t *testing.T) {
defer resetEnv()()
defer ensure.HelmHome(t)()
ts, err := repotest.NewTempServer("testdata/testserver/*.*")
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
t.Fatal(err)
}

@ -48,11 +48,22 @@ Environment variables:
| $HELM_CACHE_HOME | set an alternative location for storing cached files. |
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
| $HELM_DATA_HOME | set an alternative location for storing Helm data. |
| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode |
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, postgres |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. |
| $HELM_MAX_HISTORY | set the maximum number of helm release history. |
| $HELM_NAMESPACE | set the namespace used for the helm operations. |
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
| $HELM_PLUGINS | set the path to the plugins directory |
| $HELM_REGISTRY_CONFIG | set the path to the registry config file. |
| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory |
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
| $HELM_KUBEASGROUPS | set the Username to impersonate for the operation. |
| $HELM_KUBEASUSER | set the Groups to use for impoersonation using a comma-separated list. |
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
Helm stores cache, configuration, and data based on the following configuration order:
@ -82,6 +93,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags := cmd.PersistentFlags()
settings.AddFlags(flags)
addKlogFlags(flags)
// Setup shell completion for the namespace flag
err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@ -193,5 +205,8 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Find and add plugins
loadPlugins(cmd, out)
// Check permissions on critical files
checkPerms()
return cmd, nil
}

@ -0,0 +1,58 @@
// +build !windows
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"os"
"os/user"
"path/filepath"
)
func checkPerms() {
// This function MUST NOT FAIL, as it is just a check for a common permissions problem.
// If for some reason the function hits a stopping condition, it may panic. But only if
// we can be sure that it is panicing because Helm cannot proceed.
kc := settings.KubeConfig
if kc == "" {
kc = os.Getenv("KUBECONFIG")
}
if kc == "" {
u, err := user.Current()
if err != nil {
// No idea where to find KubeConfig, so return silently. Many helm commands
// can proceed happily without a KUBECONFIG, so this is not a fatal error.
return
}
kc = filepath.Join(u.HomeDir, ".kube", "config")
}
fi, err := os.Stat(kc)
if err != nil {
// DO NOT error if no KubeConfig is found. Not all commands require one.
return
}
perm := fi.Mode().Perm()
if perm&0040 > 0 {
warning("Kubernetes configuration file is group-readable. This is insecure. Location: %s", kc)
}
if perm&0004 > 0 {
warning("Kubernetes configuration file is world-readable. This is insecure. Location: %s", kc)
}
}

@ -0,0 +1,76 @@
// +build !windows
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bufio"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestCheckPerms(t *testing.T) {
// NOTE(bacongobbler): have to open a new file handler here as the default os.Sterr cannot be read from
stderr, err := os.Open("/dev/stderr")
if err != nil {
t.Fatalf("could not open /dev/stderr for reading: %s", err)
}
defer stderr.Close()
reader := bufio.NewReader(stderr)
tdir, err := ioutil.TempDir("", "helmtest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tdir)
tfile := filepath.Join(tdir, "testconfig")
fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440)
if err != nil {
t.Errorf("Failed to create temp file: %s", err)
}
tconfig := settings.KubeConfig
settings.KubeConfig = tfile
defer func() { settings.KubeConfig = tconfig }()
checkPerms()
text, err := reader.ReadString('\n')
if err != nil {
t.Fatalf("could not read from stderr: %s", err)
}
expectPrefix := "WARNING: Kubernetes configuration file is group-readable. This is insecure. Location:"
if !strings.HasPrefix(text, expectPrefix) {
t.Errorf("Expected to get a warning for group perms. Got %q", text)
}
if err := fh.Chmod(0404); err != nil {
t.Errorf("Could not change mode on file: %s", err)
}
checkPerms()
text, err = reader.ReadString('\n')
if err != nil {
t.Fatalf("could not read from stderr: %s", err)
}
expectPrefix = "WARNING: Kubernetes configuration file is world-readable. This is insecure. Location:"
if !strings.HasPrefix(text, expectPrefix) {
t.Errorf("Expected to get a warning for world perms. Got %q", text)
}
}

@ -0,0 +1,22 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
func checkPerms() {
// Not yet implemented on Windows. If you know how to do a comprehensive perms
// check on Windows, contributions welcomed!
}

@ -22,7 +22,6 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -184,7 +183,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) {
f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n))
ind, err := repo.LoadIndexFile(f)
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n)
warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n)
continue
}

@ -26,7 +26,7 @@ import (
)
func TestShowPreReleaseChart(t *testing.T) {
srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
if err != nil {
t.Fatal(err)
}
@ -49,6 +49,13 @@ func TestShowPreReleaseChart(t *testing.T) {
fail: true,
expectedErr: "failed to download \"test/pre-release-chart\"",
},
{
name: "show pre-release chart",
args: "test/pre-release-chart",
fail: true,
flags: "--version 1.0.0",
expectedErr: "failed to download \"test/pre-release-chart\" at version \"1.0.0\"",
},
{
name: "show pre-release chart with 'devel' flag",
args: "test/pre-release-chart",

@ -4,6 +4,8 @@ HELM_CONFIG_HOME
HELM_DATA_HOME
HELM_DEBUG
HELM_KUBEAPISERVER
HELM_KUBEASGROUPS
HELM_KUBEASUSER
HELM_KUBECONTEXT
HELM_KUBETOKEN
HELM_MAX_HISTORY

@ -1,5 +1,5 @@
table
json
yaml
:0
Completion ended with directive: ShellCompDirectiveDefault
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -0,0 +1 @@
"test-name" already exists with the same configuration, skipping

@ -1,6 +1,6 @@
module helm.sh/helm/v3
go 1.13
go 1.14
require (
github.com/BurntSushi/toml v0.3.1
@ -17,13 +17,13 @@ require (
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce
github.com/docker/go-units v0.4.0
github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b
github.com/evanphx/json-patch v4.9.0+incompatible
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.7.1
github.com/golangci/golangci-lint v1.30.0 // indirect
github.com/gofrs/flock v0.8.0
github.com/gosuri/uitable v0.0.4
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5
github.com/lib/pq v1.7.0
github.com/jessevdk/go-flags v1.4.0 // indirect
github.com/jmoiron/sqlx v1.2.0
github.com/lib/pq v1.8.0
github.com/mattn/go-shellwords v1.0.10
github.com/mitchellh/copystructure v1.0.0
github.com/opencontainers/go-digest v1.0.0
@ -36,13 +36,13 @@ require (
github.com/stretchr/testify v1.6.1
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
k8s.io/api v0.18.8
k8s.io/apiextensions-apiserver v0.18.8
k8s.io/apimachinery v0.18.8
k8s.io/cli-runtime v0.18.8
k8s.io/client-go v0.18.8
k8s.io/api v0.19.2
k8s.io/apiextensions-apiserver v0.19.2
k8s.io/apimachinery v0.19.2
k8s.io/cli-runtime v0.19.2
k8s.io/client-go v0.19.2
k8s.io/klog v1.0.0
k8s.io/kubectl v0.18.8
k8s.io/kubectl v0.19.2
sigs.k8s.io/yaml v1.2.0
)

158
go.sum

@ -6,11 +6,11 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM=
cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -21,13 +21,20 @@ github.com/Azure/go-autorest v13.3.2+incompatible h1:VxzPyuhtnlBOzc4IWCZHqpyH2d+
github.com/Azure/go-autorest v13.3.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.6 h1:5YWtOnckcudzIw8lPPBcWOnmIFWMtHci1ZWAZulMSx0=
github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0=
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM=
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
@ -130,6 +137,9 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
@ -210,6 +220,7 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arX
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@ -230,6 +241,8 @@ github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
@ -241,7 +254,6 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
@ -250,9 +262,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-critic/go-critic v0.5.0 h1:Ic2p5UCl5fX/2WX2w8nroPpPhxRNsNTMlJzsu/uqwnM=
github.com/go-critic/go-critic v0.5.0/go.mod h1:4jeRh3ZAVnRYhuWdOEvwzVqLUpxMSoAT0xZ74JsTPlo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -262,7 +272,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
@ -352,6 +363,8 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6
github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg=
github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -367,6 +380,7 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -375,11 +389,13 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -433,6 +449,7 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
@ -443,7 +460,8 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -502,6 +520,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@ -527,7 +546,10 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@ -544,6 +566,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
@ -559,6 +582,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
@ -605,6 +630,8 @@ github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
@ -625,6 +652,8 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/5
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -731,6 +760,8 @@ github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@ -748,6 +779,8 @@ github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@ -757,6 +790,8 @@ github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQl
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8 h1:DvnesvLtRPQOvaUbfXfh0tpMHg29by0H7F2U+QIkSu8=
@ -897,9 +932,7 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
@ -910,7 +943,9 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
@ -944,6 +979,7 @@ golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
@ -956,7 +992,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -966,13 +1002,13 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1000,15 +1036,17 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1030,7 +1068,6 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1051,20 +1088,24 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
@ -1084,45 +1125,31 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305 h1:yaM5S0KcY0lIoZo7Fl+oi91b/DdlU2zuWpfHrpWbCS0=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
@ -1133,7 +1160,7 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1152,8 +1179,9 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -1172,8 +1200,11 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -1181,7 +1212,6 @@ gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
@ -1213,6 +1243,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -1225,62 +1256,83 @@ k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4=
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4=
k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY=
k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms=
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
k8s.io/apiextensions-apiserver v0.18.4 h1:Y3HGERmS8t9u12YNUFoOISqefaoGRuTc43AYCLzWmWE=
k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio=
k8s.io/apiextensions-apiserver v0.18.8 h1:pkqYPKTHa0/3lYwH7201RpF9eFm0lmZDFBNzhN+k/sA=
k8s.io/apiextensions-apiserver v0.18.8/go.mod h1:7f4ySEkkvifIr4+BRrRWriKKIJjPyg9mb/p63dJKnlM=
k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJMz9tA=
k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg=
k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA=
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0=
k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig=
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8=
k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM=
k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA=
k8s.io/cli-runtime v0.18.4 h1:IUx7quIOb4gbQ4M+B1ksF/PTBovQuL5tXWzplX3t+FM=
k8s.io/cli-runtime v0.18.4/go.mod h1:9/hS/Cuf7NVzWR5F/5tyS6xsnclxoPLVtwhnkJG1Y4g=
k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k=
k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M=
k8s.io/cli-runtime v0.19.2 h1:d4uOtKhy3ImdaKqZJ8yQgLrdtUwsJLfP4Dw7L/kVPOo=
k8s.io/cli-runtime v0.19.2/go.mod h1:CMynmJM4Yf02TlkbhKxoSzi4Zf518PukJ5xep/NaNeY=
k8s.io/client-go v0.18.4 h1:un55V1Q/B3JO3A76eS0kUSywgGK/WR3BQ8fHQjNa6Zc=
k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM=
k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU=
k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc=
k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
k8s.io/component-base v0.18.4 h1:Kr53Fp1iCGNsl9Uv4VcRvLy7YyIqi9oaJOQ7SXtKI98=
k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk=
k8s.io/component-base v0.18.8 h1:BW5CORobxb6q5mb+YvdwQlyXXS6NVH5fDXWbU7tf2L8=
k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU=
k8s.io/component-base v0.19.2 h1:jW5Y9RcZTb79liEhW3XDVTW7MuvEGP0tQZnfSX6/+gs=
k8s.io/component-base v0.19.2/go.mod h1:g5LrsiTiabMLZ40AR6Hl45f088DevyGY+cCE2agEIVo=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
k8s.io/kubectl v0.18.4 h1:l9DUYPTEMs1+qNtoqPpTyaJOosvj7l7tQqphCO1K52s=
k8s.io/kubectl v0.18.4/go.mod h1:EzB+nfeUWk6fm6giXQ8P4Fayw3dsN+M7Wjy23mTRtB0=
k8s.io/kubectl v0.18.8 h1:qTkHCz21YmK0+S0oE6TtjtxmjeDP42gJcZJyRKsIenA=
k8s.io/kubectl v0.18.8/go.mod h1:PlEgIAjOMua4hDFTEkVf+W5M0asHUKfE4y7VDZkpLHM=
k8s.io/kubectl v0.19.2 h1:/Dxz9u7S0GnchLA6Avqi5k1qhZH4Fusgecj8dHsSnbk=
k8s.io/kubectl v0.19.2/go.mod h1:4ib3oj5ma6gF95QukTvC7ZBMxp60+UEAhDPjLuBIrV4=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/metrics v0.18.4/go.mod h1:luze4fyI9JG4eLDZy0kFdYEebqNfi0QrG4xNEbPkHOs=
k8s.io/metrics v0.18.8/go.mod h1:j7JzZdiyhLP2BsJm/Fzjs+j5Lb1Y7TySjhPWqBPwRXA=
k8s.io/metrics v0.19.2/go.mod h1:IlLaAGXN0q7yrtB+SV0q3JIraf6VtlDr+iuTcX21fCU=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f h1:gi7cb8HTDZ6q8VqsUpkdoFi3vxwHMneQ6+Q5Ap5hjPE=
mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg=
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA=
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=

@ -58,14 +58,15 @@ var (
errMissingRelease = errors.New("no release provided")
// errInvalidRevision indicates that an invalid release revision number was provided.
errInvalidRevision = errors.New("invalid release revision")
// errInvalidName indicates that an invalid release name was provided
errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53")
// errPending indicates that another instance of Helm is already applying an operation on a release.
errPending = errors.New("another operation (install/upgrade/rollback) is in progress")
)
// ValidName is a regular expression for resource names.
//
// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See
// pkg/chartutil.ValidateName for the replacement.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
@ -294,7 +295,7 @@ func (c *Configuration) Now() time.Time {
}
func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) {
if err := validateReleaseName(name); err != nil {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
}

@ -20,6 +20,7 @@ import (
"flag"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"testing"
@ -56,6 +57,8 @@ func actionConfigFixture(t *testing.T) *Configuration {
t.Fatal(err)
}
t.Cleanup(func() { os.RemoveAll(tdir) })
cache, err := registry.NewCache(
registry.CacheOptDebug(true),
registry.CacheOptRoot(filepath.Join(tdir, registry.CacheRootDir)),
@ -316,40 +319,3 @@ func TestGetVersionSet(t *testing.T) {
t.Error("Non-existent version is reported found.")
}
}
// TestValidName is a regression test for ValidName
//
// Kubernetes has strict naming conventions for resource names. This test represents
// those conventions.
//
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
//
// NOTE: At the time of this writing, the docs above say that names cannot begin with
// digits. However, `kubectl`'s regular expression explicit allows this, and
// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits.
func TestValidName(t *testing.T) {
names := map[string]bool{
"": false,
"foo": true,
"foo.bar1234baz.seventyone": true,
"FOO": false,
"123baz": true,
"foo.BAR.baz": false,
"one-two": true,
"-two": false,
"one_two": false,
"a..b": false,
"%^&#$%*@^*@&#^": false,
"example:com": false,
"example%%com": false,
}
for input, expectPass := range names {
if ValidName.MatchString(input) != expectPass {
st := "fail"
if expectPass {
st = "succeed"
}
t.Errorf("Expected %q to %s", input, st)
}
}
}

@ -21,6 +21,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/gosuri/uitable"
@ -61,6 +62,7 @@ func (d *Dependency) List(chartpath string, out io.Writer) error {
return nil
}
// dependecyStatus returns a string describing the status of a dependency viz a viz the parent chart.
func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, parent *chart.Chart) string {
filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
@ -75,35 +77,40 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p
case err != nil:
return "bad pattern"
case len(archives) > 1:
return "too many matches"
case len(archives) == 1:
archive := archives[0]
if _, err := os.Stat(archive); err == nil {
c, err := loader.Load(archive)
if err != nil {
return "corrupt"
// See if the second part is a SemVer
found := []string{}
for _, arc := range archives {
// we need to trip the prefix dirs and the extension off.
filename = strings.TrimSuffix(filepath.Base(arc), ".tgz")
maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name))
if _, err := semver.StrictNewVersion(maybeVersion); err == nil {
// If the version parsed without an error, it is possibly a valid
// version.
found = append(found, arc)
}
if c.Name() != dep.Name {
return "misnamed"
}
if l := len(found); l == 1 {
// If we get here, we do the same thing as in len(archives) == 1.
if r := statArchiveForStatus(found[0], dep); r != "" {
return r
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
// Fall through and look for directories
} else if l > 1 {
return "too many matches"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
// The sanest thing to do here is to fall through and see if we have any directory
// matches.
if !constraint.Check(v) {
return "wrong version"
}
}
return "ok"
case len(archives) == 1:
archive := archives[0]
if r := statArchiveForStatus(archive, dep); r != "" {
return r
}
}
// End unnecessary code.
@ -137,6 +144,40 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p
return "unpacked"
}
// stat an archive and return a message if the stat is successful
//
// This is a refactor of the code originally in dependencyStatus. It is here to
// support legacy behavior, and should be removed in Helm 4.
func statArchiveForStatus(archive string, dep *chart.Dependency) string {
if _, err := os.Stat(archive); err == nil {
c, err := loader.Load(archive)
if err != nil {
return "corrupt"
}
if c.Name() != dep.Name {
return "misnamed"
}
if c.Metadata.Version != dep.Version {
constraint, err := semver.NewConstraint(dep.Version)
if err != nil {
return "invalid version"
}
v, err := semver.NewVersion(c.Metadata.Version)
if err != nil {
return "invalid version"
}
if !constraint.Check(v) {
return "wrong version"
}
}
return "ok"
}
return ""
}
// printDependencies prints all of the dependencies in the yaml file.
func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) {
table := uitable.New()

@ -18,9 +18,16 @@ package action
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v3/internal/test"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
)
func TestList(t *testing.T) {
@ -56,3 +63,99 @@ func TestList(t *testing.T) {
test.AssertGoldenBytes(t, buf.Bytes(), tcase.golden)
}
}
// TestDependencyStatus_Dashes is a regression test to make sure that dashes in
// chart names do not cause resolution problems.
func TestDependencyStatus_Dashes(t *testing.T) {
// Make a temp dir
dir, err := ioutil.TempDir("", "helmtest-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
chartpath := filepath.Join(dir, "charts")
if err := os.MkdirAll(chartpath, 0700); err != nil {
t.Fatal(err)
}
// Add some fake charts
first := buildChart(withName("first-chart"))
_, err = chartutil.Save(first, chartpath)
if err != nil {
t.Fatal(err)
}
second := buildChart(withName("first-chart-second-chart"))
_, err = chartutil.Save(second, chartpath)
if err != nil {
t.Fatal(err)
}
dep := &chart.Dependency{
Name: "first-chart",
Version: "0.1.0",
}
// Now try to get the deps
stat := NewDependency().dependencyStatus(dir, dep, first)
if stat != "ok" {
t.Errorf("Unexpected status: %q", stat)
}
}
func TestStatArchiveForStatus(t *testing.T) {
// Make a temp dir
dir, err := ioutil.TempDir("", "helmtest-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
chartpath := filepath.Join(dir, "charts")
if err := os.MkdirAll(chartpath, 0700); err != nil {
t.Fatal(err)
}
// unsaved chart
lilith := buildChart(withName("lilith"))
// dep referring to chart
dep := &chart.Dependency{
Name: "lilith",
Version: "1.2.3",
}
is := assert.New(t)
lilithpath := filepath.Join(chartpath, "lilith-1.2.3.tgz")
is.Empty(statArchiveForStatus(lilithpath, dep))
// save the chart (version 0.1.0, because that is the default)
where, err := chartutil.Save(lilith, chartpath)
is.NoError(err)
// Should get "wrong version" because we asked for 1.2.3 and got 0.1.0
is.Equal("wrong version", statArchiveForStatus(where, dep))
// Break version on dep
dep = &chart.Dependency{
Name: "lilith",
Version: "1.2.3.4.5",
}
is.Equal("invalid version", statArchiveForStatus(where, dep))
// Break the name
dep = &chart.Dependency{
Name: "lilith2",
Version: "1.2.3",
}
is.Equal("misnamed", statArchiveForStatus(where, dep))
// Now create the right version
dep = &chart.Dependency{
Name: "lilith",
Version: "0.1.0",
}
is.Equal("ok", statArchiveForStatus(where, dep))
}

@ -19,6 +19,7 @@ package action
import (
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
)
@ -45,7 +46,7 @@ func (h *History) Run(name string) ([]*release.Release, error) {
return nil, err
}
if err := validateReleaseName(name); err != nil {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("release name is invalid: %s", name)
}

@ -654,8 +654,8 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
dl.Verify = downloader.VerifyAlways
}
if c.RepoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(c.RepoURL, c.Username, c.Password, name, version,
c.CertFile, c.KeyFile, c.CaFile, getter.All(settings))
chartURL, err := repo.FindChartInAuthAndTLSRepoURL(c.RepoURL, c.Username, c.Password, name, version,
c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, getter.All(settings))
if err != nil {
return "", err
}
@ -677,5 +677,9 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
return filename, err
}
return filename, errors.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
atVersion := ""
if version != "" {
atVersion = fmt.Sprintf(" at version %q", version)
}
return filename, errors.Errorf("failed to download %q%s (hint: running `helm repo update` may help)", name, atVersion)
}

@ -17,6 +17,7 @@ limitations under the License.
package action
import (
"bufio"
"fmt"
"io/ioutil"
"os"
@ -39,6 +40,7 @@ type Package struct {
Sign bool
Key string
Keyring string
PassphraseFile string
Version string
AppVersion string
Destination string
@ -120,7 +122,15 @@ func (p *Package) Clearsign(filename string) error {
return err
}
if err := signer.DecryptKey(promptUser); err != nil {
passphraseFetcher := promptUser
if p.PassphraseFile != "" {
passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin)
if err != nil {
return err
}
}
if err := signer.DecryptKey(passphraseFetcher); err != nil {
return err
}
@ -141,3 +151,34 @@ func promptUser(name string) ([]byte, error) {
fmt.Println()
return pw, err
}
func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) {
file, err := openPassphraseFile(passphraseFile, stdin)
if err != nil {
return nil, err
}
defer file.Close()
reader := bufio.NewReader(file)
passphrase, _, err := reader.ReadLine()
if err != nil {
return nil, err
}
return func(name string) ([]byte, error) {
return passphrase, nil
}, nil
}
func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) {
if passphraseFile == "-" {
stat, err := stdin.Stat()
if err != nil {
return nil, err
}
if (stat.Mode() & os.ModeNamedPipe) == 0 {
return nil, errors.New("specified reading passphrase from stdin, without input on stdin")
}
return stdin, nil
}
return os.Open(passphraseFile)
}

@ -17,8 +17,12 @@ limitations under the License.
package action
import (
"io/ioutil"
"os"
"path"
"testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart"
)
@ -42,3 +46,57 @@ func TestSetVersion(t *testing.T) {
t.Error("Expected bogus version to return an error.")
}
}
func TestPassphraseFileFetcher(t *testing.T) {
secret := "secret"
directory := ensure.TempFile(t, "passphrase-file", []byte(secret))
defer os.RemoveAll(directory)
fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil)
if err != nil {
t.Fatal("Unable to create passphraseFileFetcher", err)
}
passphrase, err := fetcher("key")
if err != nil {
t.Fatal("Unable to fetch passphrase")
}
if string(passphrase) != secret {
t.Errorf("Expected %s got %s", secret, string(passphrase))
}
}
func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) {
secret := "secret"
directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n."))
defer os.RemoveAll(directory)
fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil)
if err != nil {
t.Fatal("Unable to create passphraseFileFetcher", err)
}
passphrase, err := fetcher("key")
if err != nil {
t.Fatal("Unable to fetch passphrase")
}
if string(passphrase) != secret {
t.Errorf("Expected %s got %s", secret, string(passphrase))
}
}
func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) {
directory := ensure.TempDir(t)
defer os.RemoveAll(directory)
stdin, err := ioutil.TempFile(directory, "non-existing")
if err != nil {
t.Fatal("Unable to create test file", err)
}
if _, err := passphraseFileFetcher("-", stdin); err == nil {
t.Error("Expected passphraseFileFetcher returning an error")
}
}

@ -89,7 +89,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
}
if p.RepoURL != "" {
chartURL, err := repo.FindChartInAuthRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, getter.All(p.Settings))
chartURL, err := repo.FindChartInAuthAndTLSRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, getter.All(p.Settings))
if err != nil {
return out.String(), err
}

@ -25,6 +25,7 @@ import (
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
)
@ -51,7 +52,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
return nil, err
}
if err := validateReleaseName(name); err != nil {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("releaseTest: Release name is invalid: %s", name)
}

@ -24,6 +24,7 @@ import (
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
)
@ -90,7 +91,7 @@ func (r *Rollback) Run(name string) error {
// prepareRollback finds the previous release and prepares a new release object with
// the previous release's configuration
func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) {
if err := validateReleaseName(name); err != nil {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name)
}

@ -22,6 +22,7 @@ import (
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil"
helmtime "helm.sh/helm/v3/pkg/time"
@ -62,7 +63,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
return &release.UninstallReleaseResponse{Release: r}, nil
}
if err := validateReleaseName(name); err != nil {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("uninstall: Release name is invalid: %s", name)
}

@ -115,7 +115,7 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface
// the user doesn't have to specify both
u.Wait = u.Wait || u.Atomic
if err := validateReleaseName(name); err != nil {
if err := chartutil.ValidateReleaseName(name); err != nil {
return nil, errors.Errorf("release name is invalid: %s", name)
}
u.cfg.Log("preparing upgrade for %s", name)
@ -142,19 +142,6 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface
return res, nil
}
func validateReleaseName(releaseName string) error {
if releaseName == "" {
return errMissingRelease
}
// Check length first, since that is a less expensive operation.
if len(releaseName) > releaseNameMaxLen || !ValidName.MatchString(releaseName) {
return errInvalidName
}
return nil
}
// prepareUpgrade builds an upgraded release for an upgrade operation.
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) {
if chart == nil {

@ -46,7 +46,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN
}
helper := resource.NewHelper(info.Client, info.Mapping)
existing, err := helper.Get(info.Namespace, info.Name, info.Export)
existing, err := helper.Get(info.Namespace, info.Name)
if err != nil {
if apierrors.IsNotFound(err) {
return nil

@ -17,6 +17,7 @@ package chart
import (
"path/filepath"
"regexp"
"strings"
)
@ -26,6 +27,9 @@ const APIVersionV1 = "v1"
// APIVersionV2 is the API version number for version 2.
const APIVersionV2 = "v2"
// aliasNameFormat defines the characters that are legal in an alias name.
var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$")
// Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies).
type Chart struct {

@ -15,9 +15,16 @@ limitations under the License.
package chart
import "fmt"
// ValidationError represents a data validation error.
type ValidationError string
func (v ValidationError) Error() string {
return "validation: " + string(v)
}
// ValidationErrorf takes a message and formatting options and creates a ValidationError
func ValidationErrorf(msg string, args ...interface{}) ValidationError {
return ValidationError(fmt.Sprintf(msg, args...))
}

@ -81,6 +81,15 @@ func (md *Metadata) Validate() error {
if !isValidChartType(md.Type) {
return ValidationError("chart.metadata.type must be application or library")
}
// Aliases need to be validated here to make sure that the alias name does
// not contain any illegal characters.
for _, dependency := range md.Dependencies {
if err := validateDependency(dependency); err != nil {
return err
}
}
// TODO validate valid semver here?
return nil
}
@ -92,3 +101,13 @@ func isValidChartType(in string) bool {
}
return false
}
// validateDependency checks for common problems with the dependency datastructure in
// the chart. This check must be done at load time before the dependency's charts are
// loaded.
func validateDependency(dep *Dependency) error {
if len(dep.Alias) > 0 && !aliasNameFormat.MatchString(dep.Alias) {
return ValidationErrorf("dependency %q has disallowed characters in the alias", dep.Name)
}
return nil
}

@ -48,12 +48,60 @@ func TestValidate(t *testing.T) {
&Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "application"},
nil,
},
{
&Metadata{
Name: "test",
APIVersion: "v2",
Version: "1.0",
Type: "application",
Dependencies: []*Dependency{
{Name: "dependency", Alias: "legal-alias"},
},
},
nil,
},
{
&Metadata{
Name: "test",
APIVersion: "v2",
Version: "1.0",
Type: "application",
Dependencies: []*Dependency{
{Name: "bad", Alias: "illegal alias"},
},
},
ValidationError("dependency \"bad\" has disallowed characters in the alias"),
},
}
for _, tt := range tests {
result := tt.md.Validate()
if result != tt.err {
t.Errorf("expected %s, got %s", tt.err, result)
t.Errorf("expected '%s', got '%s'", tt.err, result)
}
}
}
func TestValidateDependency(t *testing.T) {
dep := &Dependency{
Name: "example",
}
for value, shouldFail := range map[string]bool{
"abcdefghijklmenopQRSTUVWXYZ-0123456780_": false,
"-okay": false,
"_okay": false,
"- bad": true,
" bad": true,
"bad\nvalue": true,
"bad ": true,
"bad$": true,
} {
dep.Alias = value
res := validateDependency(dep)
if res != nil && !shouldFail {
t.Errorf("Failed on case %q", dep.Alias)
} else if res == nil && shouldFail {
t.Errorf("Expected failure for %q", dep.Alias)
}
}
}

@ -18,9 +18,11 @@ package chartutil
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/pkg/errors"
@ -30,6 +32,12 @@ import (
"helm.sh/helm/v3/pkg/chart/loader"
)
// chartName is a regular expression for testing the supplied name of a chart.
// This regular expression is probably stricter than it needs to be. We can relax it
// somewhat. Newline characters, as well as $, quotes, +, parens, and % are known to be
// problematic.
var chartName = regexp.MustCompile("^[a-zA-Z0-9._-]+$")
const (
// ChartfileName is the default Chart file name.
ChartfileName = "Chart.yaml"
@ -63,6 +71,10 @@ const (
TestConnectionName = TemplatesTestsDir + sep + "test-connection.yaml"
)
// maxChartNameLength is lower than the limits we know of with certain file systems,
// and with certain Kubernetes fields.
const maxChartNameLength = 250
const sep = string(filepath.Separator)
const defaultChartfile = `apiVersion: v2
@ -468,6 +480,12 @@ spec:
restartPolicy: Never
`
// Stderr is an io.Writer to which error messages can be written
//
// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward
// compatibility.
var Stderr io.Writer = os.Stderr
// CreateFrom creates a new chart, but scaffolds it from the src chart.
func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
schart, err := loader.Load(src)
@ -522,6 +540,12 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(name, dir string) (string, error) {
// Sanity-check the name of a chart so user doesn't create one that causes problems.
if err := validateChartName(name); err != nil {
return "", err
}
path, err := filepath.Abs(dir)
if err != nil {
return path, err
@ -601,8 +625,8 @@ func Create(name, dir string) (string, error) {
for _, file := range files {
if _, err := os.Stat(file.path); err == nil {
// File exists and is okay. Skip it.
continue
// There is no handle to a preferred output stream here.
fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path)
}
if err := writeFile(file.path, file.content); err != nil {
return cdir, err
@ -627,3 +651,13 @@ func writeFile(name string, content []byte) error {
}
return ioutil.WriteFile(name, content, 0644)
}
func validateChartName(name string) error {
if name == "" || len(name) > maxChartNameLength {
return fmt.Errorf("chart name must be between 1 and %d characters", maxChartNameLength)
}
if !chartName.MatchString(name) {
return fmt.Errorf("chart name must match the regular expression %q", chartName.String())
}
return nil
}

@ -117,3 +117,69 @@ func TestCreateFrom(t *testing.T) {
}
}
}
// TestCreate_Overwrite is a regression test for making sure that files are overwritten.
func TestCreate_Overwrite(t *testing.T) {
tdir, err := ioutil.TempDir("", "helm-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tdir)
var errlog bytes.Buffer
if _, err := Create("foo", tdir); err != nil {
t.Fatal(err)
}
dir := filepath.Join(tdir, "foo")
tplname := filepath.Join(dir, "templates/hpa.yaml")
writeFile(tplname, []byte("FOO"))
// Now re-run the create
Stderr = &errlog
if _, err := Create("foo", tdir); err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadFile(tplname)
if err != nil {
t.Fatal(err)
}
if string(data) == "FOO" {
t.Fatal("File that should have been modified was not.")
}
if errlog.Len() == 0 {
t.Errorf("Expected warnings about overwriting files.")
}
}
func TestValidateChartName(t *testing.T) {
for name, shouldPass := range map[string]bool{
"": false,
"abcdefghijklmnopqrstuvwxyz-_.": true,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": true,
"$hello": false,
"Hellô": false,
"he%%o": false,
"he\nllo": false,
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"abcdefghijklmnopqrstuvwxyz-_." +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false,
} {
if err := validateChartName(name); (err != nil) == shouldPass {
t.Errorf("test for %q failed", name)
}
}
}

@ -0,0 +1,99 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"regexp"
"github.com/pkg/errors"
)
// validName is a regular expression for resource names.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//
// This follows the above regular expression (but requires a full string match, not partial).
//
// The Kubernetes documentation is here, though it is not entirely correct:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
var (
// errMissingName indicates that a release (name) was not provided.
errMissingName = errors.New("no name provided")
// errInvalidName indicates that an invalid release name was provided
errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53")
// errInvalidKubernetesName indicates that the name does not meet the Kubernetes
// restrictions on metadata names.
errInvalidKubernetesName = errors.New("invalid metadata name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 253")
)
const (
// maxNameLen is the maximum length Helm allows for a release name
maxReleaseNameLen = 53
// maxMetadataNameLen is the maximum length Kubernetes allows for any name.
maxMetadataNameLen = 253
)
// ValidateReleaseName performs checks for an entry for a Helm release name
//
// For Helm to allow a name, it must be below a certain character count (53) and also match
// a reguar expression.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//
// This follows the above regular expression (but requires a full string match, not partial).
//
// The Kubernetes documentation is here, though it is not entirely correct:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
func ValidateReleaseName(name string) error {
// This case is preserved for backwards compatibility
if name == "" {
return errMissingName
}
if len(name) > maxReleaseNameLen || !validName.MatchString(name) {
return errInvalidName
}
return nil
}
// ValidateMetadataName validates the name field of a Kubernetes metadata object.
//
// Empty strings, strings longer than 253 chars, or strings that don't match the regexp
// will fail.
//
// According to the Kubernetes help text, the regular expression it uses is:
//
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
//
// This follows the above regular expression (but requires a full string match, not partial).
//
// The Kubernetes documentation is here, though it is not entirely correct:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
func ValidateMetadataName(name string) error {
if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) {
return errInvalidKubernetesName
}
return nil
}

@ -0,0 +1,91 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import "testing"
// TestValidateName is a regression test for ValidateName
//
// Kubernetes has strict naming conventions for resource names. This test represents
// those conventions.
//
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
//
// NOTE: At the time of this writing, the docs above say that names cannot begin with
// digits. However, `kubectl`'s regular expression explicit allows this, and
// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits.
func TestValidateReleaseName(t *testing.T) {
names := map[string]bool{
"": false,
"foo": true,
"foo.bar1234baz.seventyone": true,
"FOO": false,
"123baz": true,
"foo.BAR.baz": false,
"one-two": true,
"-two": false,
"one_two": false,
"a..b": false,
"%^&#$%*@^*@&#^": false,
"example:com": false,
"example%%com": false,
"a1111111111111111111111111111111111111111111111111111111111z": false,
}
for input, expectPass := range names {
if err := ValidateReleaseName(input); (err == nil) != expectPass {
st := "fail"
if expectPass {
st = "succeed"
}
t.Errorf("Expected %q to %s", input, st)
}
}
}
func TestValidateMetadataName(t *testing.T) {
names := map[string]bool{
"": false,
"foo": true,
"foo.bar1234baz.seventyone": true,
"FOO": false,
"123baz": true,
"foo.BAR.baz": false,
"one-two": true,
"-two": false,
"one_two": false,
"a..b": false,
"%^&#$%*@^*@&#^": false,
"example:com": false,
"example%%com": false,
"a1111111111111111111111111111111111111111111111111111111111z": true,
"a1111111111111111111111111111111111111111111111111111111111z" +
"a1111111111111111111111111111111111111111111111111111111111z" +
"a1111111111111111111111111111111111111111111111111111111111z" +
"a1111111111111111111111111111111111111111111111111111111111z" +
"a1111111111111111111111111111111111111111111111111111111111z" +
"a1111111111111111111111111111111111111111111111111111111111z": false,
}
for input, expectPass := range names {
if err := ValidateMetadataName(input); (err == nil) != expectPass {
st := "fail"
if expectPass {
st = "succeed"
}
t.Errorf("Expected %q to %s", input, st)
}
}
}

@ -26,6 +26,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -47,6 +48,10 @@ type EnvSettings struct {
KubeContext string
// Bearer KubeToken used for authentication
KubeToken string
// Username to impersonate for the operation
KubeAsUser string
// Groups to impersonate for the operation, multiple groups parsed from a comma delimited list
KubeAsGroups []string
// Kubernetes API Server Endpoint for authentication
KubeAPIServer string
// Debug indicates whether or not Helm is running in Debug mode.
@ -69,6 +74,8 @@ func New() *EnvSettings {
MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory),
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"),
KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")),
@ -79,11 +86,13 @@ func New() *EnvSettings {
// bind to kubernetes config flags
env.config = &genericclioptions.ConfigFlags{
Namespace: &env.namespace,
Context: &env.KubeContext,
BearerToken: &env.KubeToken,
APIServer: &env.KubeAPIServer,
KubeConfig: &env.KubeConfig,
Namespace: &env.namespace,
Context: &env.KubeContext,
BearerToken: &env.KubeToken,
APIServer: &env.KubeAPIServer,
KubeConfig: &env.KubeConfig,
Impersonate: &env.KubeAsUser,
ImpersonateGroup: &env.KubeAsGroups,
}
return env
}
@ -94,6 +103,8 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file")
fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use")
fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication")
fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "Username to impersonate for the operation")
fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "Group to impersonate for the operation, this flag can be repeated to specify multiple groups.")
fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
@ -120,6 +131,14 @@ func envIntOr(name string, def int) int {
return ret
}
func envCSV(name string) (ls []string) {
trimmed := strings.Trim(os.Getenv(name), ", ")
if trimmed != "" {
ls = strings.Split(trimmed, ",")
}
return
}
func (s *EnvSettings) EnvVars() map[string]string {
envvars := map[string]string{
"HELM_BIN": os.Args[0],
@ -137,6 +156,8 @@ func (s *EnvSettings) EnvVars() map[string]string {
// broken, these are populated from helm flags and not kubeconfig.
"HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEASUSER": s.KubeAsUser,
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
"HELM_KUBEAPISERVER": s.KubeAPIServer,
}
if s.KubeConfig != "" {

@ -18,6 +18,7 @@ package cli
import (
"os"
"reflect"
"strings"
"testing"
@ -36,6 +37,8 @@ func TestEnvSettings(t *testing.T) {
ns, kcontext string
debug bool
maxhistory int
kAsUser string
kAsGroups []string
}{
{
name: "defaults",
@ -44,25 +47,31 @@ func TestEnvSettings(t *testing.T) {
},
{
name: "with flags set",
args: "--debug --namespace=myns",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters",
ns: "myns",
debug: true,
maxhistory: defaultMaxHistory,
kAsUser: "poro",
kAsGroups: []string{"admins", "teatime", "snackeaters"},
},
{
name: "with envvars set",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_MAX_HISTORY": "5"},
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5"},
ns: "yourns",
maxhistory: 5,
debug: true,
kAsUser: "pikachu",
kAsGroups: []string{"operators", "snackeaters", "partyanimals"},
},
{
name: "with flags and envvars set",
args: "--debug --namespace=myns",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns"},
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5"},
ns: "myns",
debug: true,
maxhistory: defaultMaxHistory,
maxhistory: 5,
kAsUser: "poro",
kAsGroups: []string{"admins", "teatime", "snackeaters"},
},
}
@ -92,6 +101,12 @@ func TestEnvSettings(t *testing.T) {
if settings.MaxHistory != tt.maxhistory {
t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory)
}
if tt.kAsUser != settings.KubeAsUser {
t.Errorf("expected kAsUser %q, got %q", tt.kAsUser, settings.KubeAsUser)
}
if !reflect.DeepEqual(tt.kAsGroups, settings.KubeAsGroups) {
t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kAsGroups), len(settings.KubeAsGroups))
}
})
}
}

@ -71,7 +71,7 @@ func TestResolveChartRef(t *testing.T) {
if tt.fail {
continue
}
t.Errorf("%s: failed with error %s", tt.name, err)
t.Errorf("%s: failed with error %q", tt.name, err)
continue
}
if got := u.String(); got != tt.expect {
@ -172,7 +172,7 @@ func TestIsTar(t *testing.T) {
func TestDownloadTo(t *testing.T) {
// Set up a fake repo with basic auth enabled
srv, err := repotest.NewTempServer("testdata/*.tgz*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
srv.Stop()
if err != nil {
t.Fatal(err)
@ -229,7 +229,7 @@ func TestDownloadTo(t *testing.T) {
func TestDownloadTo_TLS(t *testing.T) {
// Set up mock server w/ tls enabled
srv, err := repotest.NewTempServer("testdata/*.tgz*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
srv.Stop()
if err != nil {
t.Fatal(err)
@ -285,7 +285,7 @@ func TestDownloadTo_VerifyLater(t *testing.T) {
dest := ensure.TempDir(t)
// Set up a fake repo
srv, err := repotest.NewTempServer("testdata/*.tgz*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
if err != nil {
t.Fatal(err)
}

@ -667,7 +667,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
if err == nil {
return
}
err = errors.Errorf("chart %s not found in %s", name, repoURL)
err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err)
return
}

@ -183,7 +183,7 @@ func TestGetRepoNames(t *testing.T) {
func TestUpdateBeforeBuild(t *testing.T) {
// Set up a fake repo
srv, err := repotest.NewTempServer("testdata/*.tgz*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
if err != nil {
t.Fatal(err)
}
@ -257,7 +257,7 @@ func TestUpdateBeforeBuild(t *testing.T) {
// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used.
func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) {
// Set up a fake repo
srv, err := repotest.NewTempServer("testdata/*.tgz*")
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
if err != nil {
t.Fatal(err)
}

@ -40,7 +40,7 @@ type Engine struct {
Strict bool
// In LintMode, some 'required' template values may be missing, so don't fail
LintMode bool
// the rest config to connect to te kubernetes api
// the rest config to connect to the kubernetes api
config *rest.Config
}

@ -258,7 +258,7 @@ func getSelectorFromObject(obj runtime.Object) (map[string]string, bool, error)
}
func getResource(info *resource.Info) (runtime.Object, error) {
obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, false)
obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name)
if err != nil {
return nil, err
}
@ -340,7 +340,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
}
helper := resource.NewHelper(info.Client, info.Mapping)
if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil {
if _, err := helper.Get(info.Namespace, info.Name); err != nil {
if !apierrors.IsNotFound(err) {
return errors.Wrap(err, "could not get information about the resource")
}
@ -536,7 +536,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
// Fetch the current object for the three way merge
helper := resource.NewHelper(target.Client, target.Mapping)
currentObj, err := helper.Get(target.Namespace, target.Name, target.Export)
currentObj, err := helper.Get(target.Namespace, target.Name)
if err != nil && !apierrors.IsNotFound(err) {
return nil, types.StrategicMergePatchType, errors.Wrapf(err, "unable to get data for current object %s/%s", target.Namespace, target.Name)
}

@ -91,9 +91,12 @@ func newResponse(code int, obj runtime.Object) (*http.Response, error) {
return &http.Response{StatusCode: code, Header: header, Body: body}, nil
}
func newTestClient() *Client {
func newTestClient(t *testing.T) *Client {
testFactory := cmdtesting.NewTestFactory()
t.Cleanup(testFactory.Cleanup)
return &Client{
Factory: cmdtesting.NewTestFactory().WithNamespace("default"),
Factory: testFactory.WithNamespace("default"),
Log: nopLogger,
}
}
@ -107,7 +110,7 @@ func TestUpdate(t *testing.T) {
var actions []string
c := newTestClient()
c := newTestClient(t)
c.Factory.(*cmdtesting.TestFactory).UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -232,7 +235,7 @@ func TestBuild(t *testing.T) {
},
}
c := newTestClient()
c := newTestClient(t)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test for an invalid manifest
@ -279,7 +282,7 @@ func TestPerform(t *testing.T) {
return nil
}
c := newTestClient()
c := newTestClient(t)
infos, err := c.Build(tt.reader, false)
if err != nil && err.Error() != tt.errMessage {
t.Errorf("Error while building manifests: %v", err)

@ -40,14 +40,6 @@ var (
releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`)
)
// validName is a regular expression for names.
//
// This is different than action.ValidName. It conforms to the regular expression
// `kubectl` says it uses, plus it disallows empty names.
//
// For details, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
// Templates lints the templates in the Linter.
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) {
fpath := "templates/"
@ -199,10 +191,10 @@ func validateMetadataName(obj *K8sYamlStruct) error {
}
// This will return an error if the characters do not abide by the standard OR if the
// name is left empty.
if validName.MatchString(obj.Metadata.Name) {
return nil
if err := chartutil.ValidateMetadataName(obj.Metadata.Name); err != nil {
return errors.Wrapf(err, "object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name)
}
return fmt.Errorf("object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name)
return nil
}
func validateNoCRDHooks(manifest []byte) error {

@ -59,6 +59,18 @@ var Extractors = map[string]Extractor{
".tgz": &TarGzExtractor{},
}
// Convert a media type to an extractor extension.
//
// This should be refactored in Helm 4, combined with the extension-based mechanism.
func mediaTypeToExtension(mt string) (string, bool) {
switch strings.ToLower(mt) {
case "application/gzip", "application/x-gzip", "application/x-tgz", "application/x-gtar":
return ".tgz", true
default:
return "", false
}
}
// NewExtractor creates a new extractor matching the source file name
func NewExtractor(source string) (Extractor, error) {
for suffix, extractor := range Extractors {

@ -20,9 +20,13 @@ import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
@ -63,9 +67,24 @@ func TestStripName(t *testing.T) {
}
}
func mockArchiveServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.HasSuffix(r.URL.Path, ".tar.gz") {
w.Header().Add("Content-Type", "text/html")
fmt.Fprintln(w, "broken")
return
}
w.Header().Add("Content-Type", "application/gzip")
fmt.Fprintln(w, "test")
}))
}
func TestHTTPInstaller(t *testing.T) {
defer ensure.HelmHome(t)()
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
@ -111,7 +130,9 @@ func TestHTTPInstaller(t *testing.T) {
func TestHTTPInstallerNonExistentVersion(t *testing.T) {
defer ensure.HelmHome(t)()
source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz"
srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
@ -141,7 +162,9 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) {
}
func TestHTTPInstallerUpdate(t *testing.T) {
source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
defer ensure.HelmHome(t)()
if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
@ -307,3 +330,26 @@ func TestCleanJoin(t *testing.T) {
}
}
func TestMediaTypeToExtension(t *testing.T) {
for mt, shouldPass := range map[string]bool{
"": false,
"application/gzip": true,
"application/x-gzip": true,
"application/x-tgz": true,
"application/x-gtar": true,
"application/json": false,
} {
ext, ok := mediaTypeToExtension(mt)
if ok != shouldPass {
t.Errorf("Media type %q failed test", mt)
}
if shouldPass && ext == "" {
t.Errorf("Expected an extension but got empty string")
}
if !shouldPass && len(ext) != 0 {
t.Error("Expected extension to be empty for unrecognized type")
}
}
}

@ -18,6 +18,7 @@ package installer
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
@ -89,10 +90,29 @@ func isLocalReference(source string) bool {
}
// isRemoteHTTPArchive checks if the source is a http/https url and is an archive
//
// It works by checking whether the source looks like a URL and, if it does, running a
// HEAD operation to see if the remote resource is a file that we understand.
func isRemoteHTTPArchive(source string) bool {
if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") {
res, err := http.Head(source)
if err != nil {
// If we get an error at the network layer, we can't install it. So
// we return false.
return false
}
// Next, we look for the content type or content disposition headers to see
// if they have matching extractors.
contentType := res.Header.Get("content-type")
foundSuffix, ok := mediaTypeToExtension(contentType)
if !ok {
// Media type not recognized
return false
}
for suffix := range Extractors {
if strings.HasSuffix(source, suffix) {
if strings.HasSuffix(foundSuffix, suffix) {
return true
}
}

@ -0,0 +1,40 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package installer
import "testing"
func TestIsRemoteHTTPArchive(t *testing.T) {
srv := mockArchiveServer()
defer srv.Close()
source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
if isRemoteHTTPArchive("/not/a/URL") {
t.Errorf("Expected non-URL to return false")
}
if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") {
t.Errorf("Bad URL should not have succeeded.")
}
if !isRemoteHTTPArchive(source) {
t.Errorf("Expected %q to be a valid archive URL", source)
}
if isRemoteHTTPArchive(source + "-not-an-extension") {
t.Error("Expected media type match to fail")
}
}

@ -37,7 +37,7 @@ func TestLocalInstaller(t *testing.T) {
t.Fatal(err)
}
source := "../testdata/plugdir/echo"
source := "../testdata/plugdir/good/echo"
i, err := NewForSource(source, "")
if err != nil {
t.Fatalf("unexpected error: %s", err)

@ -56,7 +56,7 @@ func TestVCSInstaller(t *testing.T) {
}
source := "https://github.com/adamreese/helm-env"
testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo")
testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo")
repo := &testRepo{
local: testRepoPath,
tags: []string{"0.1.0", "0.1.1"},

@ -20,9 +20,11 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/cli"
@ -94,6 +96,12 @@ type Metadata struct {
// Downloaders field is used if the plugin supply downloader mechanism
// for special protocols.
Downloaders []Downloaders `json:"downloaders"`
// UseTunnelDeprecated indicates that this command needs a tunnel.
// Setting this will cause a number of side effects, such as the
// automatic setting of HELM_HOST.
// DEPRECATED and unused, but retained for backwards compatibility with Helm 2 plugins. Remove in Helm 4
UseTunnelDeprecated bool `json:"useTunnel,omitempty"`
}
// Plugin represents a plugin.
@ -157,18 +165,51 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) {
return main, baseArgs, nil
}
// validPluginName is a regular expression that validates plugin names.
//
// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -.
var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$")
// validatePluginData validates a plugin's YAML data.
func validatePluginData(plug *Plugin, filepath string) error {
if !validPluginName.MatchString(plug.Metadata.Name) {
return fmt.Errorf("invalid plugin name at %q", filepath)
}
// We could also validate SemVer, executable, and other fields should we so choose.
return nil
}
func detectDuplicates(plugs []*Plugin) error {
names := map[string]string{}
for _, plug := range plugs {
if oldpath, ok := names[plug.Metadata.Name]; ok {
return fmt.Errorf(
"two plugins claim the name %q at %q and %q",
plug.Metadata.Name,
oldpath,
plug.Dir,
)
}
names[plug.Metadata.Name] = plug.Dir
}
return nil
}
// LoadDir loads a plugin from the given directory.
func LoadDir(dirname string) (*Plugin, error) {
data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName))
pluginfile := filepath.Join(dirname, PluginFileName)
data, err := ioutil.ReadFile(pluginfile)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile)
}
plug := &Plugin{Dir: dirname}
if err := yaml.Unmarshal(data, &plug.Metadata); err != nil {
return nil, err
if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil {
return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile)
}
return plug, nil
return plug, validatePluginData(plug, pluginfile)
}
// LoadAll loads all plugins found beneath the base directory.
@ -180,7 +221,7 @@ func LoadAll(basedir string) ([]*Plugin, error) {
scanpath := filepath.Join(basedir, "*", PluginFileName)
matches, err := filepath.Glob(scanpath)
if err != nil {
return plugins, err
return plugins, errors.Wrapf(err, "failed to find plugins in %q", scanpath)
}
if matches == nil {
@ -195,7 +236,7 @@ func LoadAll(basedir string) ([]*Plugin, error) {
}
plugins = append(plugins, p)
}
return plugins, nil
return plugins, detectDuplicates(plugins)
}
// FindPlugins returns a list of YAML files that describe plugins.

@ -16,6 +16,7 @@ limitations under the License.
package plugin // import "helm.sh/helm/v3/pkg/plugin"
import (
"fmt"
"os"
"path/filepath"
"reflect"
@ -177,7 +178,7 @@ func TestNoMatchPrepareCommand(t *testing.T) {
}
func TestLoadDir(t *testing.T) {
dirname := "testdata/plugdir/hello"
dirname := "testdata/plugdir/good/hello"
plug, err := LoadDir(dirname)
if err != nil {
t.Fatalf("error loading Hello plugin: %s", err)
@ -204,8 +205,15 @@ func TestLoadDir(t *testing.T) {
}
}
func TestLoadDirDuplicateEntries(t *testing.T) {
dirname := "testdata/plugdir/bad/duplicate-entries"
if _, err := LoadDir(dirname); err == nil {
t.Errorf("successfully loaded plugin with duplicate entries when it should've failed")
}
}
func TestDownloader(t *testing.T) {
dirname := "testdata/plugdir/downloader"
dirname := "testdata/plugdir/good/downloader"
plug, err := LoadDir(dirname)
if err != nil {
t.Fatalf("error loading Hello plugin: %s", err)
@ -243,7 +251,7 @@ func TestLoadAll(t *testing.T) {
t.Fatalf("expected empty dir to have 0 plugins")
}
basedir := "testdata/plugdir"
basedir := "testdata/plugdir/good"
plugs, err := LoadAll(basedir)
if err != nil {
t.Fatalf("Could not load %q: %s", basedir, err)
@ -287,7 +295,7 @@ func TestFindPlugins(t *testing.T) {
},
{
name: "normal",
plugdirs: "./testdata/plugdir",
plugdirs: "./testdata/plugdir/good",
expected: 3,
},
}
@ -320,3 +328,51 @@ func TestSetupEnv(t *testing.T) {
}
}
}
func TestValidatePluginData(t *testing.T) {
for i, item := range []struct {
pass bool
plug *Plugin
}{
{true, mockPlugin("abcdefghijklmnopqrstuvwxyz0123456789_-ABC")},
{true, mockPlugin("foo-bar-FOO-BAR_1234")},
{false, mockPlugin("foo -bar")},
{false, mockPlugin("$foo -bar")}, // Test leading chars
{false, mockPlugin("foo -bar ")}, // Test trailing chars
{false, mockPlugin("foo\nbar")}, // Test newline
} {
err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i))
if item.pass && err != nil {
t.Errorf("failed to validate case %d: %s", i, err)
} else if !item.pass && err == nil {
t.Errorf("expected case %d to fail", i)
}
}
}
func TestDetectDuplicates(t *testing.T) {
plugs := []*Plugin{
mockPlugin("foo"),
mockPlugin("bar"),
}
if err := detectDuplicates(plugs); err != nil {
t.Error("no duplicates in the first set")
}
plugs = append(plugs, mockPlugin("foo"))
if err := detectDuplicates(plugs); err == nil {
t.Error("duplicates in the second set")
}
}
func mockPlugin(name string) *Plugin {
return &Plugin{
Metadata: &Metadata{
Name: name,
Version: "v0.1.2",
Usage: "Mock plugin",
Description: "Mock plugin for testing",
Command: "echo mock plugin",
},
Dir: "no-such-dir",
}
}

@ -0,0 +1,11 @@
name: "duplicate-entries"
version: "0.1.0"
usage: "usage"
description: |-
description
command: "echo hello"
ignoreFlags: true
hooks:
install: "echo installing..."
hooks:
install: "echo installing something different"

@ -5,6 +5,5 @@ description: |-
description
command: "$HELM_PLUGIN_SELF/hello.sh"
ignoreFlags: true
install: "echo installing..."
hooks:
install: "echo installing..."

@ -205,6 +205,14 @@ func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caF
// without adding repo to repositories, like FindChartInRepoURL,
// but it also receives credentials for the chart repository.
func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
return FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, getters)
}
// FindChartInAuthAndTLSRepoURL finds chart in chart repository pointed by repoURL
// without adding repo to repositories, like FindChartInRepoURL,
// but it also receives credentials and TLS verify flag for the chart repository.
// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL.
func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) {
// Download and write the index file to a temporary location
buf := make([]byte, 20)
@ -212,13 +220,14 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion
name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-")
c := Entry{
URL: repoURL,
Username: username,
Password: password,
CertFile: certFile,
KeyFile: keyFile,
CAFile: caFile,
Name: name,
URL: repoURL,
Username: username,
Password: password,
CertFile: certFile,
KeyFile: keyFile,
CAFile: caFile,
Name: name,
InsecureSkipTLSverify: insecureSkipTLSverify,
}
r, err := NewChartRepository(&c, getters)
if err != nil {

@ -276,6 +276,44 @@ func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) {
return httptest.NewServer(handler), nil
}
// startLocalTLSServerForTests Start the local helm server with TLS
func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) {
if handler == nil {
fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml")
if err != nil {
return nil, err
}
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(fileBytes)
})
}
return httptest.NewTLSServer(handler), nil
}
func TestFindChartInAuthAndTLSRepoURL(t *testing.T) {
srv, err := startLocalTLSServerForTests(nil)
if err != nil {
t.Fatal(err)
}
defer srv.Close()
chartURL, err := FindChartInAuthAndTLSRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, getter.All(&cli.EnvSettings{}))
if err != nil {
t.Fatalf("%v", err)
}
if chartURL != "https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz" {
t.Errorf("%s is not the valid URL", chartURL)
}
// If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority".
_, err = FindChartInAuthAndTLSRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, getter.All(&cli.EnvSettings{}))
if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
t.Errorf("Expected TLS error for function FindChartInAuthAndTLSRepoURL not found, but got a different error (%v)", err)
}
}
func TestFindChartInRepoURL(t *testing.T) {
srv, err := startLocalServerForTests(nil)
if err != nil {

@ -77,6 +77,8 @@ func (c ChartVersions) Less(a, b int) bool {
// IndexFile represents the index file in a chart repository
type IndexFile struct {
// This is used ONLY for validation against chartmuseum's index files and is discarded after validation.
ServerInfo map[string]interface{} `json:"serverInfo,omitempty"`
APIVersion string `json:"apiVersion"`
Generated time.Time `json:"generated"`
Entries map[string]ChartVersions `json:"entries"`
@ -228,6 +230,23 @@ type ChartVersion struct {
Created time.Time `json:"created,omitempty"`
Removed bool `json:"removed,omitempty"`
Digest string `json:"digest,omitempty"`
// ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced
// this with Digest. However, with a strict YAML parser enabled, a field must be
// present on the struct for backwards compatibility.
ChecksumDeprecated string `json:"checksum,omitempty"`
// EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict
// YAML parser enabled, this field must be present.
EngineDeprecated string `json:"engine,omitempty"`
// TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict
// YAML parser enabled, this field must be present.
TillerVersionDeprecated string `json:"tillerVersion,omitempty"`
// URLDeprecated is deprectaed in Helm 3, superseded by URLs. It is ignored. However,
// with a strict YAML parser enabled, this must be present on the struct.
URLDeprecated string `json:"url,omitempty"`
}
// IndexDirectory reads a (flat) directory and generates an index.
@ -281,7 +300,7 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
func loadIndex(data []byte) (*IndexFile, error) {
i := &IndexFile{}
if err := yaml.Unmarshal(data, i); err != nil {
if err := yaml.UnmarshalStrict(data, i); err != nil {
return i, err
}
i.SortEntries()

@ -35,9 +35,31 @@ import (
)
const (
testfile = "testdata/local-index.yaml"
unorderedTestfile = "testdata/local-index-unordered.yaml"
testRepo = "test-repo"
testfile = "testdata/local-index.yaml"
chartmuseumtestfile = "testdata/chartmuseum-index.yaml"
unorderedTestfile = "testdata/local-index-unordered.yaml"
testRepo = "test-repo"
indexWithDuplicates = `
apiVersion: v1
entries:
nginx:
- urls:
- https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz
name: nginx
description: string
version: 0.2.0
home: https://github.com/something/else
digest: "sha256:1234567890abcdef"
nginx:
- urls:
- https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz
- http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz
name: alpine
description: string
version: 1.0.0
home: https://github.com/something
digest: "sha256:1234567890abcdef"
`
)
func TestIndexFile(t *testing.T) {
@ -84,15 +106,43 @@ func TestIndexFile(t *testing.T) {
}
func TestLoadIndex(t *testing.T) {
b, err := ioutil.ReadFile(testfile)
if err != nil {
t.Fatal(err)
tests := []struct {
Name string
Filename string
}{
{
Name: "regular index file",
Filename: testfile,
},
{
Name: "chartmuseum index file",
Filename: chartmuseumtestfile,
},
}
i, err := loadIndex(b)
if err != nil {
t.Fatal(err)
for _, tc := range tests {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
b, err := ioutil.ReadFile(tc.Filename)
if err != nil {
t.Fatal(err)
}
i, err := loadIndex(b)
if err != nil {
t.Fatal(err)
}
verifyLocalIndex(t, i)
})
}
}
// TestLoadIndex_Duplicates is a regression to make sure that we don't non-deterministically allow duplicate packages.
func TestLoadIndex_Duplicates(t *testing.T) {
if _, err := loadIndex([]byte(indexWithDuplicates)); err == nil {
t.Errorf("Expected an error when duplicate entries are present")
}
verifyLocalIndex(t, i)
}
func TestLoadIndexFile(t *testing.T) {

@ -21,6 +21,7 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"testing"
"helm.sh/helm/v3/internal/tlsutil"
@ -29,6 +30,19 @@ import (
"helm.sh/helm/v3/pkg/repo"
)
// NewTempServerWithCleanup creates a server inside of a temp dir.
//
// If the passed in string is not "", it will be treated as a shell glob, and files
// will be copied from that path to the server's docroot.
//
// The caller is responsible for stopping the server.
// The temp dir will be removed by testing package automatically when test finished.
func NewTempServerWithCleanup(t *testing.T, glob string) (*Server, error) {
srv, err := NewTempServer(glob)
t.Cleanup(func() { os.RemoveAll(srv.docroot) })
return srv, err
}
// NewTempServer creates a server inside of a temp dir.
//
// If the passed in string is not "", it will be treated as a shell glob, and files
@ -36,6 +50,8 @@ import (
//
// The caller is responsible for destroying the temp directory as well as stopping
// the server.
//
// Deprecated: use NewTempServerWithCleanup
func NewTempServer(glob string) (*Server, error) {
tdir, err := ioutil.TempDir("", "helm-repotest-")
if err != nil {

@ -99,7 +99,7 @@ func TestServer(t *testing.T) {
func TestNewTempServer(t *testing.T) {
defer ensure.HelmHome(t)()
srv, err := NewTempServer("testdata/examplechart-0.1.0.tgz")
srv, err := NewTempServerWithCleanup(t, "testdata/examplechart-0.1.0.tgz")
if err != nil {
t.Fatal(err)
}

@ -0,0 +1,50 @@
serverInfo:
contextPath: /v1/helm
apiVersion: v1
entries:
nginx:
- urls:
- https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz
name: nginx
description: string
version: 0.2.0
home: https://github.com/something/else
digest: "sha256:1234567890abcdef"
keywords:
- popular
- web server
- proxy
- urls:
- https://kubernetes-charts.storage.googleapis.com/nginx-0.1.0.tgz
name: nginx
description: string
version: 0.1.0
home: https://github.com/something
digest: "sha256:1234567890abcdef"
keywords:
- popular
- web server
- proxy
alpine:
- urls:
- https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz
- http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz
name: alpine
description: string
version: 1.0.0
home: https://github.com/something
keywords:
- linux
- alpine
- small
- sumtin
digest: "sha256:1234567890abcdef"
chartWithNoURL:
- name: chartWithNoURL
description: string
version: 1.0.0
home: https://github.com/something
keywords:
- small
- sumtin
digest: "sha256:1234567890abcdef"

@ -19,8 +19,16 @@
: ${BINARY_NAME:="helm"}
: ${USE_SUDO:="true"}
: ${DEBUG:="false"}
: ${VERIFY_CHECKSUM:="true"}
: ${VERIFY_SIGNATURES:="false"}
: ${HELM_INSTALL_DIR:="/usr/local/bin"}
HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)"
HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)"
# initArch discovers the architecture for this system.
initArch() {
ARCH=$(uname -m)
@ -58,7 +66,7 @@ runAsRoot() {
}
# verifySupported checks that the os/arch combination is supported for
# binary builds.
# binary builds, as well whether or not necessary tools are present.
verifySupported() {
local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nwindows-amd64"
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
@ -67,10 +75,29 @@ verifySupported() {
exit 1
fi
if ! type "curl" > /dev/null && ! type "wget" > /dev/null; then
if [ "${HAS_CURL}" != "true" ] && [ "${HAS_WGET}" != "true" ]; then
echo "Either curl or wget is required"
exit 1
fi
if [ "${VERIFY_CHECKSUM}" == "true" ] && [ "${HAS_OPENSSL}" != "true" ]; then
echo "In order to verify checksum, openssl must first be installed."
echo "Please install openssl or set VERIFY_CHECKSUM=false in your environment."
exit 1
fi
if [ "${VERIFY_SIGNATURES}" == "true" ]; then
if [ "${HAS_GPG}" != "true" ]; then
echo "In order to verify signatures, gpg must first be installed."
echo "Please install gpg or set VERIFY_SIGNATURES=false in your environment."
exit 1
fi
if [ "${OS}" != "linux" ]; then
echo "Signature verification is currently only supported on Linux."
echo "Please set VERIFY_SIGNATURES=false or verify the signatures manually."
exit 1
fi
fi
}
# checkDesiredVersion checks if the desired version is available.
@ -78,9 +105,9 @@ checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then
# Get tag from release URL
local latest_release_url="https://github.com/helm/helm/releases"
if type "curl" > /dev/null; then
if [ "${HAS_CURL}" == "true" ]; then
TAG=$(curl -Ls $latest_release_url | grep 'href="/helm/helm/releases/tag/v3.[0-9]*.[0-9]*\"' | grep -v no-underline | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}')
elif type "wget" > /dev/null; then
elif [ "${HAS_WGET}" == "true" ]; then
TAG=$(wget $latest_release_url -O - 2>&1 | grep 'href="/helm/helm/releases/tag/v3.[0-9]*.[0-9]*\"' | grep -v no-underline | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}')
fi
else
@ -115,35 +142,94 @@ downloadFile() {
HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST"
HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256"
echo "Downloading $DOWNLOAD_URL"
if type "curl" > /dev/null; then
if [ "${HAS_CURL}" == "true" ]; then
curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE"
elif type "wget" > /dev/null; then
wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL"
fi
if type "curl" > /dev/null; then
curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE"
elif type "wget" > /dev/null; then
elif [ "${HAS_WGET}" == "true" ]; then
wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL"
wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL"
fi
}
# installFile verifies the SHA256 for the file, then unpacks and
# installs it.
# verifyFile verifies the SHA256 checksum of the binary package
# and the GPG signatures for both the package and checksum file
# (depending on settings in environment).
verifyFile() {
if [ "${VERIFY_CHECKSUM}" == "true" ]; then
verifyChecksum
fi
if [ "${VERIFY_SIGNATURES}" == "true" ]; then
verifySignatures
fi
}
# installFile installs the Helm binary.
installFile() {
HELM_TMP="$HELM_TMP_ROOT/$BINARY_NAME"
mkdir -p "$HELM_TMP"
tar xf "$HELM_TMP_FILE" -C "$HELM_TMP"
HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm"
echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}"
runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME"
echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME"
}
# verifyChecksum verifies the SHA256 checksum of the binary package.
verifyChecksum() {
printf "Verifying checksum... "
local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}')
local expected_sum=$(cat ${HELM_SUM_FILE})
if [ "$sum" != "$expected_sum" ]; then
echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting."
exit 1
fi
echo "Done."
}
mkdir -p "$HELM_TMP"
tar xf "$HELM_TMP_FILE" -C "$HELM_TMP"
HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm"
echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}"
runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME"
echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME"
# verifySignatures obtains the latest KEYS file from GitHub master branch
# as well as the signature .asc files from the specific GitHub release,
# then verifies that the release artifacts were signed by a maintainer's key.
verifySignatures() {
printf "Verifying signatures... "
local keys_filename="KEYS"
local github_keys_url="https://raw.githubusercontent.com/helm/helm/master/${keys_filename}"
if [ "${HAS_CURL}" == "true" ]; then
curl -SsL "${github_keys_url}" -o "${HELM_TMP_ROOT}/${keys_filename}"
elif [ "${HAS_WGET}" == "true" ]; then
wget -q -O "${HELM_TMP_ROOT}/${keys_filename}" "${github_keys_url}"
fi
local gpg_keyring="${HELM_TMP_ROOT}/keyring.gpg"
local gpg_homedir="${HELM_TMP_ROOT}/gnupg"
mkdir -p -m 0700 "${gpg_homedir}"
local gpg_stderr_device="/dev/null"
if [ "${DEBUG}" == "true" ]; then
gpg_stderr_device="/dev/stderr"
fi
gpg --batch --quiet --homedir="${gpg_homedir}" --import "${HELM_TMP_ROOT}/${keys_filename}" 2> "${gpg_stderr_device}"
gpg --batch --no-default-keyring --keyring "${gpg_homedir}/pubring.kbx" --export > "${gpg_keyring}"
local github_release_url="https://github.com/helm/helm/releases/download/${TAG}"
if [ "${HAS_CURL}" == "true" ]; then
curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
elif [ "${HAS_WGET}" == "true" ]; then
wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
fi
local error_text="If you think this might be a potential security issue,"
error_text="${error_text}\nplease see here: https://github.com/helm/community/blob/master/SECURITY.md"
local num_goodlines_sha=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
if [[ ${num_goodlines_sha} -lt 2 ]]; then
echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256!"
echo -e "${error_text}"
exit 1
fi
local num_goodlines_tar=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
if [[ ${num_goodlines_tar} -lt 2 ]]; then
echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz!"
echo -e "${error_text}"
exit 1
fi
echo "Done."
}
# fail_trap is executed if an error occurs.
@ -195,6 +281,11 @@ cleanup() {
trap "fail_trap" EXIT
set -e
# Set debug if desired
if [ "${DEBUG}" == "true" ]; then
set -x
fi
# Parsing input arguments (if any)
export INPUT_ARGUMENTS="${@}"
set -u
@ -229,6 +320,7 @@ verifySupported
checkDesiredVersion
if ! checkHelmInstalledVersion; then
downloadFile
verifyFile
installFile
fi
testVersion

@ -90,7 +90,7 @@ Download Helm ${RELEASE}. The common platform binaries are here:
- [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256))
- [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256sum) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256))
The [Quickstart Guide](https://docs.helm.sh/using_helm/#quickstart-guide) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://docs.helm.sh/using_helm/#installing-helm). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3) on any system with \`bash\`.
The [Quickstart Guide](https://helm.sh/docs/intro/quickstart/) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://helm.sh/docs/intro/install/). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3) on any system with \`bash\`.
## What's Next

Loading…
Cancel
Save