Merge branch 'main' into main

Signed-off-by: Marcos Lorenzo <mlorenzo@stratio.com>
pull/10893/head
Marcos Lorenzo 3 years ago committed by GitHub
commit 054cfca4e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,7 +5,7 @@ jobs:
build: build:
working_directory: ~/helm.sh/helm working_directory: ~/helm.sh/helm
docker: docker:
- image: circleci/golang:1.17 - image: cimg/go:1.18
auth: auth:
username: $DOCKER_USER username: $DOCKER_USER
@ -13,7 +13,7 @@ jobs:
environment: environment:
GOCACHE: "/tmp/go/cache" GOCACHE: "/tmp/go/cache"
GOLANGCI_LINT_VERSION: "1.43.0" GOLANGCI_LINT_VERSION: "1.46.2"
steps: steps:
- checkout - checkout

@ -34,7 +34,7 @@ else
fi fi
echo "Installing Azure CLI" echo "Installing Azure CLI"
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ stretch main" | sudo tee /etc/apt/sources.list.d/azure-cli.list echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add
sudo apt install apt-transport-https sudo apt install apt-transport-https
sudo apt update sudo apt update

@ -12,7 +12,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: '1.17' go-version: '1.18'
- name: Install golangci-lint - name: Install golangci-lint
run: | run: |
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
@ -21,8 +21,8 @@ jobs:
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64* rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
env: env:
GOLANGCI_LINT_VERSION: '1.43.0' GOLANGCI_LINT_VERSION: '1.46.2'
GOLANGCI_LINT_SHA256: 'f3515cebec926257da703ba0a2b169e4a322c11dc31a8b4656b50a43e48877f4' GOLANGCI_LINT_SHA256: '242cd4f2d6ac0556e315192e8555784d13da5d1874e51304711570769c4f2b9b'
- name: Test style - name: Test style
run: make test-style run: make test-style
- name: Run unit tests - name: Run unit tests

@ -18,12 +18,13 @@ ACCEPTANCE_DIR:=../acceptance-testing
ACCEPTANCE_RUN_TESTS=. ACCEPTANCE_RUN_TESTS=.
# go option # go option
PKG := ./... PKG := ./...
TAGS := TAGS :=
TESTS := . TESTS := .
TESTFLAGS := TESTFLAGS :=
LDFLAGS := -w -s LDFLAGS := -w -s
GOFLAGS := GOFLAGS :=
CGO_ENABLED ?= 0
# Rebuild the binary if any of these files change # Rebuild the binary if any of these files change
SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum
@ -77,7 +78,7 @@ all: build
build: $(BINDIR)/$(BINNAME) build: $(BINDIR)/$(BINNAME)
$(BINDIR)/$(BINNAME): $(SRC) $(BINDIR)/$(BINNAME): $(SRC)
GO111MODULE=on CGO_ENABLED=0 go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm GO111MODULE=on CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# install # install
@ -149,15 +150,15 @@ gen-test-golden: test-unit
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# dependencies # dependencies
# If go get is run from inside the project directory it will add the dependencies # If go install is run from inside the project directory it will add the
# to the go.mod file. To avoid that we change to a directory without a go.mod file # dependencies to the go.mod file. To avoid that we change to a directory
# when downloading the following dependencies # without a go.mod file when downloading the following dependencies
$(GOX): $(GOX):
(cd /; GO111MODULE=on go get -u github.com/mitchellh/gox) (cd /; GO111MODULE=on go install github.com/mitchellh/gox@latest)
$(GOIMPORTS): $(GOIMPORTS):
(cd /; GO111MODULE=on go get -u golang.org/x/tools/cmd/goimports) (cd /; GO111MODULE=on go install golang.org/x/tools/cmd/goimports@latest)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# release # release

@ -5,6 +5,7 @@ maintainers:
- jdolitsky - jdolitsky
- marckhouzam - marckhouzam
- mattfarina - mattfarina
- sabre1041
- scottrigby - scottrigby
- SlickNik - SlickNik
- technosophos - technosophos

@ -25,6 +25,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
) )
@ -84,7 +86,7 @@ func (o *docsOptions) run(out io.Writer) error {
hdrFunc := func(filename string) string { hdrFunc := func(filename string) string {
base := filepath.Base(filename) base := filepath.Base(filename)
name := strings.TrimSuffix(base, path.Ext(base)) name := strings.TrimSuffix(base, path.Ext(base))
title := strings.Title(strings.Replace(name, "_", " ", -1)) title := cases.Title(language.Und, cases.NoLower).String(strings.Replace(name, "_", " ", -1))
return fmt.Sprintf("---\ntitle: \"%s\"\n---\n\n", title) return fmt.Sprintf("---\ntitle: \"%s\"\n---\n\n", title)
} }

@ -47,6 +47,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)")
} }
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {

@ -51,7 +51,8 @@ To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line, to force or use the '--set' flag and pass configuration from the command line, to force
a string value use '--set-string'. You can use '--set-file' to set individual a string value use '--set-string'. You can use '--set-file' to set individual
values from a file when the value itself is too long for the command line values from a file when the value itself is too long for the command line
or is dynamically generated. or is dynamically generated. You can also use '--set-json' to set json values
(scalars/objects/arrays) from the command line.
$ helm install -f myvalues.yaml myredis ./redis $ helm install -f myvalues.yaml myredis ./redis
@ -67,6 +68,11 @@ or
$ helm install --set-file my_script=dothings.sh myredis ./redis $ helm install --set-file my_script=dothings.sh myredis ./redis
or
$ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
contained a key called 'Test', the value set in override.yaml would take precedence: contained a key called 'Test', the value set in override.yaml would take precedence:
@ -79,6 +85,13 @@ set for a key called 'foo', the 'newbar' value would take precedence:
$ helm install --set foo=bar --set foo=newbar myredis ./redis $ helm install --set foo=bar --set foo=newbar myredis ./redis
Similarly, in the following example 'foo' is set to '["four"]':
$ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis
And in the following example, 'foo' is set to '{"key1":"value1","key2":"bar"}':
$ helm install --set-json='foo={"key1":"value1","key2":"value2"}' --set-json='foo.key2="bar"' myredis ./redis
To check the generated manifests of a release without installing the chart, To check the generated manifests of a release without installing the chart,
the '--debug' and '--dry-run' flags can be combined. the '--debug' and '--dry-run' flags can be combined.
@ -86,13 +99,14 @@ the '--debug' and '--dry-run' flags can be combined.
If --verify is set, the chart MUST have a provenance file, and the provenance If --verify is set, the chart MUST have a provenance file, and the provenance
file MUST pass all verification steps. file MUST pass all verification steps.
There are five different ways you can express the chart you want to install: There are six different ways you can express the chart you want to install:
1. By chart reference: helm install mymaria example/mariadb 1. By chart reference: helm install mymaria example/mariadb
2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz 2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz
3. By path to an unpacked chart directory: helm install mynginx ./nginx 3. By path to an unpacked chart directory: helm install mynginx ./nginx
4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz 4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz
5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx 5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx
6. By OCI registries: helm install mynginx --version 1.2.3 oci://example.com/charts/nginx
CHART REFERENCES CHART REFERENCES

@ -29,6 +29,7 @@ import (
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/lint/support"
) )
var longLintHelp = ` var longLintHelp = `
@ -76,12 +77,23 @@ func newLintCmd(out io.Writer) *cobra.Command {
var message strings.Builder var message strings.Builder
failed := 0 failed := 0
errorsOrWarnings := 0
for _, path := range paths { for _, path := range paths {
fmt.Fprintf(&message, "==> Linting %s\n", path)
result := client.Run([]string{path}, vals) result := client.Run([]string{path}, vals)
// If there is no errors/warnings and quiet flag is set
// go to the next chart
hasWarningsOrErrors := action.HasWarningsOrErrors(result)
if hasWarningsOrErrors {
errorsOrWarnings++
}
if client.Quiet && !hasWarningsOrErrors {
continue
}
fmt.Fprintf(&message, "==> Linting %s\n", path)
// All the Errors that are generated by a chart // All the Errors that are generated by a chart
// that failed a lint will be included in the // that failed a lint will be included in the
// results.Messages so we only need to print // results.Messages so we only need to print
@ -93,7 +105,9 @@ func newLintCmd(out io.Writer) *cobra.Command {
} }
for _, msg := range result.Messages { for _, msg := range result.Messages {
fmt.Fprintf(&message, "%s\n", msg) if !client.Quiet || msg.Severity > support.InfoSev {
fmt.Fprintf(&message, "%s\n", msg)
}
} }
if len(result.Errors) != 0 { if len(result.Errors) != 0 {
@ -112,7 +126,9 @@ func newLintCmd(out io.Writer) *cobra.Command {
if failed > 0 { if failed > 0 {
return errors.New(summary) return errors.New(summary)
} }
fmt.Fprintln(out, summary) if !client.Quiet || errorsOrWarnings > 0 {
fmt.Fprintln(out, summary)
}
return nil return nil
}, },
} }
@ -120,6 +136,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts")
f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors")
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
return cmd return cmd

@ -37,6 +37,27 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func TestLintCmdWithQuietFlag(t *testing.T) {
testChart1 := "testdata/testcharts/alpine"
testChart2 := "testdata/testcharts/chart-bad-requirements"
tests := []cmdTestCase{{
name: "lint good chart using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s", testChart1),
golden: "output/lint-quiet.txt",
}, {
name: "lint two charts, one with error using --quiet flag",
cmd: fmt.Sprintf("lint --quiet %s %s", testChart1, testChart2),
golden: "output/lint-quiet-with-error.txt",
wantError: true,
}, {
name: "lint chart with warning using --quiet flag",
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
golden: "output/lint-quiet-with-warning.txt",
}}
runTestCmd(t, tests)
}
func TestLintFileCompletion(t *testing.T) { func TestLintFileCompletion(t *testing.T) {
checkFileCompletion(t, "lint", true) checkFileCompletion(t, "lint", true)
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given

@ -83,8 +83,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
if client.Short { if client.Short {
names := make([]string, 0, len(results))
names := make([]string, 0)
for _, res := range results { for _, res := range results {
names = append(names, res.Name) names = append(names, res.Name)
} }
@ -103,17 +102,16 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
fmt.Fprintln(out, res.Name) fmt.Fprintln(out, res.Name)
} }
return nil return nil
default:
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat))
} }
} }
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat)) return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat, client.NoHeaders))
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format") f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
f.BoolVarP(&client.NoHeaders, "no-headers", "", false, "don't print headers when using the default output format")
f.StringVar(&client.TimeFormat, "time-format", "", `format time using golang time formatter. Example: --time-format "2006-01-02 15:04:05Z0700"`) f.StringVar(&client.TimeFormat, "time-format", "", `format time using golang time formatter. Example: --time-format "2006-01-02 15:04:05Z0700"`)
f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date") f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order") f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order")
@ -145,10 +143,11 @@ type releaseElement struct {
} }
type releaseListWriter struct { type releaseListWriter struct {
releases []releaseElement releases []releaseElement
noHeaders bool
} }
func newReleaseListWriter(releases []*release.Release, timeFormat string) *releaseListWriter { func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders bool) *releaseListWriter {
// Initialize the array so no results returns an empty array instead of null // Initialize the array so no results returns an empty array instead of null
elements := make([]releaseElement, 0, len(releases)) elements := make([]releaseElement, 0, len(releases))
for _, r := range releases { for _, r := range releases {
@ -173,12 +172,14 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string) *relea
elements = append(elements, element) elements = append(elements, element)
} }
return &releaseListWriter{elements} return &releaseListWriter{elements, noHeaders}
} }
func (r *releaseListWriter) WriteTable(out io.Writer) error { func (r *releaseListWriter) WriteTable(out io.Writer) error {
table := uitable.New() table := uitable.New()
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION") if !r.noHeaders {
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION")
}
for _, r := range r.releases { for _, r := range r.releases {
table.AddRow(r.Name, r.Namespace, r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion) table.AddRow(r.Name, r.Namespace, r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion)
} }

@ -148,6 +148,11 @@ func TestListCmd(t *testing.T) {
cmd: "list", cmd: "list",
golden: "output/list.txt", golden: "output/list.txt",
rels: releaseFixture, rels: releaseFixture,
}, {
name: "list without headers",
cmd: "list --no-headers",
golden: "output/list-no-headers.txt",
rels: releaseFixture,
}, { }, {
name: "list all releases", name: "list all releases",
cmd: "list --all", cmd: "list --all",

@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) { func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{} known := []string{}
unknown := []string{} unknown := []string{}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config"} kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config", "--insecure-skip-tls-verify", "--tls-server-name"}
knownArg := func(a string) bool { knownArg := func(a string) bool {
for _, pre := range kvargs { for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") { if strings.HasPrefix(a, pre+"=") {

@ -80,7 +80,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it") f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification") f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
f.StringVar(&client.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded") f.StringVar(&client.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded")
f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this") f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and untardir are specified, untardir is appended to this")
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

@ -25,7 +25,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/docker/docker/pkg/term" //nolint "github.com/moby/term"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"

@ -38,8 +38,8 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
f, err := repo.LoadFile(settings.RepositoryConfig) f, _ := repo.LoadFile(settings.RepositoryConfig)
if isNotExist(err) || (len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML)) { if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) {
return errors.New("no repositories to show") return errors.New("no repositories to show")
} }

@ -45,28 +45,31 @@ Common actions for Helm:
Environment variables: Environment variables:
| Name | Description | | Name | Description |
|------------------------------------|-----------------------------------------------------------------------------------| |------------------------------------|---------------------------------------------------------------------------------------------------|
| $HELM_CACHE_HOME | set an alternative location for storing cached files. | | $HELM_CACHE_HOME | set an alternative location for storing cached files. |
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. | | $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
| $HELM_DATA_HOME | set an alternative location for storing Helm data. | | $HELM_DATA_HOME | set an alternative location for storing Helm data. |
| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode | | $HELM_DEBUG | indicate whether or not Helm is running in Debug mode |
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, sql. | | $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, sql. |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. | | $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_MAX_HISTORY | set the maximum number of helm release history. |
| $HELM_NAMESPACE | set the namespace used for the helm operations. | | $HELM_NAMESPACE | set the namespace used for the helm operations. |
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | | $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
| $HELM_PLUGINS | set the path to the plugins directory | | $HELM_PLUGINS | set the path to the plugins directory |
| $HELM_REGISTRY_CONFIG | set the path to the registry config file. | | $HELM_REGISTRY_CONFIG | set the path to the registry config file. |
| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory | | $HELM_REPOSITORY_CACHE | set the path to the repository cache directory |
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. | | $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication | | $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. | | $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. | | $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. | | $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. | | $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. | | $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) |
| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate |
| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)|
Helm stores cache, configuration, and data based on the following configuration order: Helm stores cache, configuration, and data based on the following configuration order:
@ -151,7 +154,8 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
registryClient, err := registry.NewClient( registryClient, err := registry.NewClient(
registry.ClientOptDebug(settings.Debug), registry.ClientOptDebug(settings.Debug),
registry.ClientOptWriter(out), registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig), registry.ClientOptCredentialsFile(settings.RegistryConfig),
) )
if err != nil { if err != nil {

@ -47,14 +47,14 @@ func TestShowPreReleaseChart(t *testing.T) {
name: "show pre-release chart", name: "show pre-release chart",
args: "test/pre-release-chart", args: "test/pre-release-chart",
fail: true, fail: true,
expectedErr: "failed to download \"test/pre-release-chart\"", expectedErr: "chart \"pre-release-chart\" matching not found in test index. (try 'helm repo update'): no chart version found for pre-release-chart-",
}, },
{ {
name: "show pre-release chart", name: "show pre-release chart",
args: "test/pre-release-chart", args: "test/pre-release-chart",
fail: true, fail: true,
flags: "--version 1.0.0", flags: "--version 1.0.0",
expectedErr: "failed to download \"test/pre-release-chart\" at version \"1.0.0\"", expectedErr: "chart \"pre-release-chart\" matching 1.0.0 not found in test index. (try 'helm repo update'): no chart version found for pre-release-chart-1.0.0",
}, },
{ {
name: "show pre-release chart with 'devel' flag", name: "show pre-release chart with 'devel' flag",

@ -1,4 +1,5 @@
HELM_BIN HELM_BIN
HELM_BURST_LIMIT
HELM_CACHE_HOME HELM_CACHE_HOME
HELM_CONFIG_HOME HELM_CONFIG_HOME
HELM_DATA_HOME HELM_DATA_HOME
@ -8,6 +9,8 @@ HELM_KUBEASGROUPS
HELM_KUBEASUSER HELM_KUBEASUSER
HELM_KUBECAFILE HELM_KUBECAFILE
HELM_KUBECONTEXT HELM_KUBECONTEXT
HELM_KUBEINSECURE_SKIP_TLS_VERIFY
HELM_KUBETLS_SERVER_NAME
HELM_KUBETOKEN HELM_KUBETOKEN
HELM_MAX_HISTORY HELM_MAX_HISTORY
HELM_NAMESPACE HELM_NAMESPACE

@ -0,0 +1,8 @@
==> Linting testdata/testcharts/chart-bad-requirements
[ERROR] Chart.yaml: unable to parse YAML
error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[WARNING] templates/: directory not found
[ERROR] : unable to load chart
cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
Error: 2 chart(s) linted, 1 chart(s) failed

@ -0,0 +1,4 @@
==> Linting testdata/testcharts/chart-with-only-crds
[WARNING] templates/: directory not found
1 chart(s) linted, 0 chart(s) failed

@ -0,0 +1,4 @@
hummingbird default 1 2016-01-16 00:00:03 +0000 UTC deployed chickadee-1.0.0 0.0.1
iguana default 2 2016-01-16 00:00:04 +0000 UTC deployed chickadee-1.0.0 0.0.1
rocket default 1 2016-01-16 00:00:02 +0000 UTC failed chickadee-1.0.0 0.0.1
starlord default 2 2016-01-16 00:00:01 +0000 UTC deployed chickadee-1.0.0 0.0.1

@ -1 +1 @@
version.BuildInfo{Version:"v3.8", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.10", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
version.BuildInfo{Version:"v3.8", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.10", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
Version: v3.8 Version: v3.10

@ -1 +1 @@
version.BuildInfo{Version:"v3.8", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.10", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -51,7 +51,8 @@ To override values in a chart, use either the '--values' flag and pass in a file
or use the '--set' flag and pass configuration from the command line, to force string or use the '--set' flag and pass configuration from the command line, to force string
values, use '--set-string'. You can use '--set-file' to set individual values, use '--set-string'. You can use '--set-file' to set individual
values from a file when the value itself is too long for the command line values from a file when the value itself is too long for the command line
or is dynamically generated. or is dynamically generated. You can also use '--set-json' to set json values
(scalars/objects/arrays) from the command line.
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml last (right-most) file specified. For example, if both myvalues.yaml and override.yaml

115
go.mod

@ -1,47 +1,50 @@
module helm.sh/helm/v3 module helm.sh/helm/v3
go 1.17 go 1.18
require ( require (
github.com/BurntSushi/toml v1.0.0 github.com/BurntSushi/toml v1.2.0
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/semver/v3 v3.1.1 github.com/Masterminds/semver/v3 v3.1.1
github.com/Masterminds/sprig/v3 v3.2.2 github.com/Masterminds/sprig/v3 v3.2.2
github.com/Masterminds/squirrel v1.5.2 github.com/Masterminds/squirrel v1.5.3
github.com/Masterminds/vcs v1.13.3 github.com/Masterminds/vcs v1.13.3
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/containerd/containerd v1.6.1 github.com/containerd/containerd v1.6.6
github.com/cyphar/filepath-securejoin v0.2.3 github.com/cyphar/filepath-securejoin v0.2.3
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269
github.com/docker/docker v20.10.13+incompatible github.com/evanphx/json-patch v5.6.0+incompatible
github.com/evanphx/json-patch v4.12.0+incompatible
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.1 github.com/gofrs/flock v0.8.1
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/jmoiron/sqlx v1.3.4 github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.4 github.com/lib/pq v1.10.7
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/copystructure v1.2.0
github.com/opencontainers/image-spec v1.0.2 github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v1.1.1 github.com/rubenv/sql-migrate v1.2.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.4.0 github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.8.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 go.etcd.io/etcd/api/v3 v3.5.4
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
k8s.io/api v0.23.5 golang.org/x/text v0.3.7
k8s.io/apiextensions-apiserver v0.23.5 google.golang.org/grpc v1.47.0
k8s.io/apimachinery v0.23.5 k8s.io/api v0.25.2
k8s.io/apiserver v0.23.5 k8s.io/apiextensions-apiserver v0.25.2
k8s.io/cli-runtime v0.23.5 k8s.io/apimachinery v0.25.2
k8s.io/client-go v0.23.5 k8s.io/apiserver v0.25.2
k8s.io/klog/v2 v2.30.0 k8s.io/cli-runtime v0.25.2
k8s.io/kubectl v0.23.5 k8s.io/client-go v0.25.2
oras.land/oras-go v1.1.1 k8s.io/klog/v2 v2.70.1
k8s.io/kubectl v0.25.2
oras.land/oras-go v1.2.0
sigs.k8s.io/yaml v1.3.0 sigs.k8s.io/yaml v1.3.0
) )
@ -49,12 +52,12 @@ require (
cloud.google.com/go v0.99.0 // indirect cloud.google.com/go v0.99.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.20 // indirect github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@ -65,36 +68,38 @@ require (
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.11+incompatible // indirect github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/logr v1.2.2 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect github.com/go-openapi/swag v0.19.14 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.0.0 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/gomodule/redigo v1.8.2 // indirect github.com/gomodule/redigo v1.8.2 // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.6 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.2.0 // indirect github.com/google/uuid v1.2.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
@ -116,17 +121,17 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/russross/blackfriday v1.5.2 // indirect github.com/russross/blackfriday v1.5.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
@ -134,29 +139,27 @@ require (
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect github.com/xlab/treeprint v1.1.0 // indirect
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/grpc v1.43.0 // indirect google.golang.org/protobuf v1.28.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.23.5 // indirect k8s.io/component-base v0.25.2 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.10.1 // indirect sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
) )

921
go.sum

File diff suppressed because it is too large Load Diff

@ -71,7 +71,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "error evaluating symlink %s", path) return errors.Wrapf(err, "error evaluating symlink %s", path)
} }
log.Printf("found symbolic link in path: %s resolves to %s", path, resolved) log.Printf("found symbolic link in path: %s resolves to %s. Contents of linked file included and used", path, resolved)
if info, err = os.Lstat(resolved); err != nil { if info, err = os.Lstat(resolved); err != nil {
return err return err
} }

@ -40,21 +40,12 @@ type HelperT interface {
Helper() Helper()
} }
// AssertGoldenBytes asserts that the give actual content matches the contents of the given filename
func AssertGoldenBytes(t TestingT, actual []byte, filename string) {
t.Helper()
if err := compare(actual, path(filename)); err != nil {
t.Fatalf("%v", err)
}
}
// AssertGoldenString asserts that the given string matches the contents of the given file. // AssertGoldenString asserts that the given string matches the contents of the given file.
func AssertGoldenString(t TestingT, actual, filename string) { func AssertGoldenString(t TestingT, actual, filename string) {
t.Helper() t.Helper()
if err := compare([]byte(actual), path(filename)); err != nil { if err := compare([]byte(actual), path(filename)); err != nil {
t.Fatalf("%v", err) t.Fatalf("%v\n", err)
} }
} }
@ -66,7 +57,7 @@ func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string
if err != nil { if err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
AssertGoldenBytes(t, actual, expectedFilename) AssertGoldenString(t, string(actual), expectedFilename)
} }
func path(filename string) string { func path(filename string) string {
@ -88,7 +79,7 @@ func compare(actual []byte, filename string) error {
} }
expected = normalize(expected) expected = normalize(expected)
if !bytes.Equal(expected, actual) { if !bytes.Equal(expected, actual) {
return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'\n", filename, expected, actual) return errors.Errorf("does not match golden file %s\n\nWANT:\n'%s'\n\nGOT:\n'%s'", filename, expected, actual)
} }
return nil return nil
} }

@ -29,7 +29,7 @@ var (
// //
// Increment major number for new feature additions and behavioral changes. // Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
version = "v3.8" version = "v3.10"
// metadata is extra build time data // metadata is extra build time data
metadata = "" metadata = ""

@ -59,7 +59,7 @@ func TestList(t *testing.T) {
if err := NewDependency().List(tcase.chart, &buf); err != nil { if err := NewDependency().List(tcase.chart, &buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
test.AssertGoldenBytes(t, buf.Bytes(), tcase.golden) test.AssertGoldenString(t, buf.String(), tcase.golden)
} }
} }

@ -712,6 +712,10 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
RegistryClient: c.registryClient, RegistryClient: c.registryClient,
} }
if registry.IsOCI(name) {
dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient))
}
if c.Verify { if c.Verify {
dl.Verify = downloader.VerifyAlways dl.Verify = downloader.VerifyAlways
} }
@ -751,20 +755,13 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
} }
filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache) filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache)
if err == nil { if err != nil {
lname, err := filepath.Abs(filename) return "", err
if err != nil {
return filename, err
}
return lname, nil
} else if settings.Debug {
return filename, err
} }
atVersion := "" lname, err := filepath.Abs(filename)
if version != "" { if err != nil {
atVersion = fmt.Sprintf(" at version %q", version) return filename, err
} }
return lname, nil
return filename, errors.Errorf("failed to download %q%s", name, atVersion)
} }

@ -36,6 +36,7 @@ type Lint struct {
Strict bool Strict bool
Namespace string Namespace string
WithSubcharts bool WithSubcharts bool
Quiet bool
} }
// LintResult is the result of Lint // LintResult is the result of Lint
@ -75,6 +76,16 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
return result return result
} }
// HasWaringsOrErrors checks is LintResult has any warnings or errors
func HasWarningsOrErrors(result *LintResult) bool {
for _, msg := range result.Messages {
if msg.Severity > support.InfoSev {
return true
}
}
return false
}
func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) { func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) {
var chartPath string var chartPath string
linter := support.Linter{} linter := support.Linter{}

@ -125,6 +125,7 @@ type List struct {
// Filter is a filter that is applied to the results // Filter is a filter that is applied to the results
Filter string Filter string
Short bool Short bool
NoHeaders bool
TimeFormat string TimeFormat string
Uninstalled bool Uninstalled bool
Superseded bool Superseded bool

@ -113,6 +113,10 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
} }
deletedResources, kept, errs := u.deleteRelease(rel) deletedResources, kept, errs := u.deleteRelease(rel)
if errs != nil {
u.cfg.Log("uninstall: Failed to delete release: %s", errs)
return nil, errors.Errorf("failed to delete release: %s", name)
}
if kept != "" { if kept != "" {
kept = "These resources were kept due to the resource policy:\n" + kept kept = "These resources were kept due to the resource policy:\n" + kept

@ -391,6 +391,9 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
} }
if u.Wait { if u.Wait {
u.cfg.Log(
"waiting for release %s resources (created: %d updated: %d deleted: %d)",
upgradedRelease.Name, len(results.Created), len(results.Updated), len(results.Deleted))
if u.WaitForJobs { if u.WaitForJobs {
if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil {
u.cfg.recordRelease(originalRelease) u.cfg.recordRelease(originalRelease)

@ -53,6 +53,9 @@ type Dependency struct {
// the chart. This check must be done at load time before the dependency's charts are // the chart. This check must be done at load time before the dependency's charts are
// loaded. // loaded.
func (d *Dependency) Validate() error { func (d *Dependency) Validate() error {
if d == nil {
return ValidationError("dependency cannot be an empty list")
}
d.Name = sanitizeString(d.Name) d.Name = sanitizeString(d.Name)
d.Version = sanitizeString(d.Version) d.Version = sanitizeString(d.Version)
d.Repository = sanitizeString(d.Repository) d.Repository = sanitizeString(d.Repository)

@ -34,6 +34,9 @@ type Maintainer struct {
// Validate checks valid data and sanitizes string characters. // Validate checks valid data and sanitizes string characters.
func (m *Maintainer) Validate() error { func (m *Maintainer) Validate() error {
if m == nil {
return ValidationError("maintainer cannot be an empty list")
}
m.Name = sanitizeString(m.Name) m.Name = sanitizeString(m.Name)
m.Email = sanitizeString(m.Email) m.Email = sanitizeString(m.Email)
m.URL = sanitizeString(m.URL) m.URL = sanitizeString(m.URL)

@ -72,6 +72,30 @@ func TestValidate(t *testing.T) {
}, },
ValidationError("dependency \"bad\" has disallowed characters in the alias"), ValidationError("dependency \"bad\" has disallowed characters in the alias"),
}, },
{
&Metadata{
Name: "test",
APIVersion: "v2",
Version: "1.0",
Type: "application",
Dependencies: []*Dependency{
nil,
},
},
ValidationError("dependency cannot be an empty list"),
},
{
&Metadata{
Name: "test",
APIVersion: "v2",
Version: "1.0",
Type: "application",
Maintainers: []*Maintainer{
nil,
},
},
ValidationError("maintainer cannot be an empty list"),
},
{ {
&Metadata{APIVersion: "v2", Name: "test", Version: "1.2.3.4"}, &Metadata{APIVersion: "v2", Name: "test", Version: "1.2.3.4"},
ValidationError("chart.metadata.version \"1.2.3.4\" is invalid"), ValidationError("chart.metadata.version \"1.2.3.4\" is invalid"),

@ -62,8 +62,8 @@ func TestDefaultCapabilities(t *testing.T) {
func TestDefaultCapabilitiesHelmVersion(t *testing.T) { func TestDefaultCapabilitiesHelmVersion(t *testing.T) {
hv := DefaultCapabilities.HelmVersion hv := DefaultCapabilities.HelmVersion
if hv.Version != "v3.8" { if hv.Version != "v3.10" {
t.Errorf("Expected default HelmVersion to be v3.8, got %q", hv.Version) t.Errorf("Expected default HelmVersion to be v3.10, got %q", hv.Version)
} }
} }

@ -312,7 +312,7 @@ spec:
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:
- name: http - name: http
containerPort: 80 containerPort: {{ .Values.service.port }}
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
httpGet: httpGet:

@ -68,7 +68,7 @@ func (v Values) Table(name string) (Values, error) {
// //
// It protects against nil map panics. // It protects against nil map panics.
func (v Values) AsMap() map[string]interface{} { func (v Values) AsMap() map[string]interface{} {
if v == nil || len(v) == 0 { if len(v) == 0 {
return map[string]interface{}{} return map[string]interface{}{}
} }
return v return v

@ -30,6 +30,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
) )
@ -37,6 +38,9 @@ import (
// defaultMaxHistory sets the maximum number of releases to 0: unlimited // defaultMaxHistory sets the maximum number of releases to 0: unlimited
const defaultMaxHistory = 10 const defaultMaxHistory = 10
// defaultBurstLimit sets the default client-side throttling limit
const defaultBurstLimit = 100
// EnvSettings describes all of the environment settings. // EnvSettings describes all of the environment settings.
type EnvSettings struct { type EnvSettings struct {
namespace string namespace string
@ -56,6 +60,12 @@ type EnvSettings struct {
KubeAPIServer string KubeAPIServer string
// Custom certificate authority file. // Custom certificate authority file.
KubeCaFile string KubeCaFile string
// KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity.
// This makes the HTTPS connections insecure
KubeInsecureSkipTLSVerify bool
// KubeTLSServerName overrides the name to use for server certificate validation.
// If it is not provided, the hostname used to contact the server is used
KubeTLSServerName string
// Debug indicates whether or not Helm is running in Debug mode. // Debug indicates whether or not Helm is running in Debug mode.
Debug bool Debug bool
// RegistryConfig is the path to the registry config file. // RegistryConfig is the path to the registry config file.
@ -68,22 +78,27 @@ type EnvSettings struct {
PluginsDirectory string PluginsDirectory string
// MaxHistory is the max release history maintained. // MaxHistory is the max release history maintained.
MaxHistory int MaxHistory int
// BurstLimit is the default client-side throttling limit.
BurstLimit int
} }
func New() *EnvSettings { func New() *EnvSettings {
env := &EnvSettings{ env := &EnvSettings{
namespace: os.Getenv("HELM_NAMESPACE"), namespace: os.Getenv("HELM_NAMESPACE"),
MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory),
KubeContext: os.Getenv("HELM_KUBECONTEXT"), KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"), KubeToken: os.Getenv("HELM_KUBETOKEN"),
KubeAsUser: os.Getenv("HELM_KUBEASUSER"), KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"), KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")), KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit),
} }
env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG"))
@ -96,7 +111,13 @@ func New() *EnvSettings {
CAFile: &env.KubeCaFile, CAFile: &env.KubeCaFile,
KubeConfig: &env.KubeConfig, KubeConfig: &env.KubeConfig,
Impersonate: &env.KubeAsUser, Impersonate: &env.KubeAsUser,
Insecure: &env.KubeInsecureSkipTLSVerify,
TLSServerName: &env.KubeTLSServerName,
ImpersonateGroup: &env.KubeAsGroups, ImpersonateGroup: &env.KubeAsGroups,
WrapConfigFn: func(config *rest.Config) *rest.Config {
config.Burst = env.BurstLimit
return config
},
} }
return env return env
} }
@ -111,10 +132,13 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
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.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.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection")
fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used")
fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs")
fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the file containing cached repository indexes") fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the file containing cached repository indexes")
fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit")
} }
func envOr(name, def string) string { func envOr(name, def string) string {
@ -124,6 +148,18 @@ func envOr(name, def string) string {
return def return def
} }
func envBoolOr(name string, def bool) bool {
if name == "" {
return def
}
envVal := envOr(name, strconv.FormatBool(def))
ret, err := strconv.ParseBool(envVal)
if err != nil {
return def
}
return ret
}
func envIntOr(name string, def int) int { func envIntOr(name string, def int) int {
if name == "" { if name == "" {
return def return def
@ -157,14 +193,17 @@ func (s *EnvSettings) EnvVars() map[string]string {
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig, "HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(), "HELM_NAMESPACE": s.Namespace(),
"HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory),
"HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit),
// broken, these are populated from helm flags and not kubeconfig. // broken, these are populated from helm flags and not kubeconfig.
"HELM_KUBECONTEXT": s.KubeContext, "HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken, "HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEASUSER": s.KubeAsUser, "HELM_KUBEASUSER": s.KubeAsUser,
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
"HELM_KUBEAPISERVER": s.KubeAPIServer, "HELM_KUBEAPISERVER": s.KubeAPIServer,
"HELM_KUBECAFILE": s.KubeCaFile, "HELM_KUBECAFILE": s.KubeCaFile,
"HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify),
"HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName,
} }
if s.KubeConfig != "" { if s.KubeConfig != "" {
envvars["KUBECONFIG"] = s.KubeConfig envvars["KUBECONFIG"] = s.KubeConfig

@ -48,48 +48,61 @@ func TestEnvSettings(t *testing.T) {
envvars map[string]string envvars map[string]string
// expected values // expected values
ns, kcontext string ns, kcontext string
debug bool debug bool
maxhistory int maxhistory int
kubeAsUser string kubeAsUser string
kubeAsGroups []string kubeAsGroups []string
kubeCaFile string kubeCaFile string
kubeInsecure bool
kubeTLSServer string
burstLimit int
}{ }{
{ {
name: "defaults", name: "defaults",
ns: "default", ns: "default",
maxhistory: defaultMaxHistory, maxhistory: defaultMaxHistory,
burstLimit: defaultBurstLimit,
}, },
{ {
name: "with flags set", name: "with flags set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt", args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
ns: "myns", ns: "myns",
debug: true, debug: true,
maxhistory: defaultMaxHistory, maxhistory: defaultMaxHistory,
kubeAsUser: "poro", burstLimit: 100,
kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeAsUser: "poro",
kubeCaFile: "/tmp/ca.crt", kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
}, },
{ {
name: "with envvars set", name: "with envvars set",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt"}, envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"},
ns: "yourns", ns: "yourns",
maxhistory: 5, maxhistory: 5,
debug: true, burstLimit: 150,
kubeAsUser: "pikachu", debug: true,
kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, kubeAsUser: "pikachu",
kubeCaFile: "/tmp/ca.crt", kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
}, },
{ {
name: "with flags and envvars set", name: "with flags and envvars set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt", args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt"}, envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"},
ns: "myns", ns: "myns",
debug: true, debug: true,
maxhistory: 5, maxhistory: 5,
kubeAsUser: "poro", burstLimit: 175,
kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeAsUser: "poro",
kubeCaFile: "/my/ca.crt", kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeCaFile: "/my/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
}, },
} }
@ -128,6 +141,92 @@ func TestEnvSettings(t *testing.T) {
if tt.kubeCaFile != settings.KubeCaFile { if tt.kubeCaFile != settings.KubeCaFile {
t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile) t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile)
} }
if tt.burstLimit != settings.BurstLimit {
t.Errorf("expected BurstLimit %d, got %d", tt.burstLimit, settings.BurstLimit)
}
if tt.kubeInsecure != settings.KubeInsecureSkipTLSVerify {
t.Errorf("expected kubeInsecure %t, got %t", tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify)
}
if tt.kubeTLSServer != settings.KubeTLSServerName {
t.Errorf("expected kubeTLSServer %q, got %q", tt.kubeTLSServer, settings.KubeTLSServerName)
}
})
}
}
func TestEnvOrBool(t *testing.T) {
const envName = "TEST_ENV_OR_BOOL"
tests := []struct {
name string
env string
val string
def bool
expected bool
}{
{
name: "unset with default false",
def: false,
expected: false,
},
{
name: "unset with default true",
def: true,
expected: true,
},
{
name: "blank env with default false",
env: envName,
def: false,
expected: false,
},
{
name: "blank env with default true",
env: envName,
def: true,
expected: true,
},
{
name: "env true with default false",
env: envName,
val: "true",
def: false,
expected: true,
},
{
name: "env false with default true",
env: envName,
val: "false",
def: true,
expected: false,
},
{
name: "env fails parsing with default true",
env: envName,
val: "NOT_A_BOOL",
def: true,
expected: true,
},
{
name: "env fails parsing with default false",
env: envName,
val: "NOT_A_BOOL",
def: false,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.env != "" {
t.Cleanup(func() {
os.Unsetenv(tt.env)
})
os.Setenv(tt.env, tt.val)
}
actual := envBoolOr(tt.env, tt.def)
if actual != tt.expected {
t.Errorf("expected result %t, got %t", tt.expected, actual)
}
}) })
} }
} }

@ -34,6 +34,7 @@ type Options struct {
StringValues []string StringValues []string
Values []string Values []string
FileValues []string FileValues []string
JSONValues []string
} }
// MergeValues merges values from files specified via -f/--values and directly // MergeValues merges values from files specified via -f/--values and directly
@ -57,6 +58,13 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
base = mergeMaps(base, currentMap) base = mergeMaps(base, currentMap)
} }
// User specified a value via --set-json
for _, value := range opts.JSONValues {
if err := strvals.ParseJSON(value, base); err != nil {
return nil, errors.Errorf("failed parsing --set-json data %s", value)
}
}
// User specified a value via --set // User specified a value via --set
for _, value := range opts.Values { for _, value := range opts.Values {
if err := strvals.ParseInto(value, base); err != nil { if err := strvals.ParseInto(value, base); err != nil {
@ -112,7 +120,10 @@ func readFile(filePath string, p getter.Providers) ([]byte, error) {
if strings.TrimSpace(filePath) == "-" { if strings.TrimSpace(filePath) == "-" {
return ioutil.ReadAll(os.Stdin) return ioutil.ReadAll(os.Stdin)
} }
u, _ := url.Parse(filePath) u, err := url.Parse(filePath)
if err != nil {
return nil, err
}
// FIXME: maybe someone handle other protocols like ftp. // FIXME: maybe someone handle other protocols like ftp.
g, err := p.ByScheme(u.Scheme) g, err := p.ByScheme(u.Scheme)

@ -19,6 +19,8 @@ package values
import ( import (
"reflect" "reflect"
"testing" "testing"
"helm.sh/helm/v3/pkg/getter"
) )
func TestMergeValues(t *testing.T) { func TestMergeValues(t *testing.T) {
@ -75,3 +77,12 @@ func TestMergeValues(t *testing.T) {
t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap)
} }
} }
func TestReadFile(t *testing.T) {
var p getter.Providers
filePath := "%a.txt"
_, err := readFile(filePath, p)
if err == nil {
t.Errorf("Expected error when has special strings")
}
}

@ -307,6 +307,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
} }
q := repoURL.Query() q := repoURL.Query()
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one // We need a trailing slash for ResolveReference to work, but make sure there isn't already one
repoURL.RawPath = strings.TrimSuffix(repoURL.RawPath, "/") + "/"
repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/" repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/"
u = repoURL.ResolveReference(u) u = repoURL.ResolveReference(u)
u.RawQuery = q.Encode() u.RawQuery = q.Encode()

@ -48,6 +48,7 @@ func TestResolveChartRef(t *testing.T) {
{name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, {name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"},
{name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"},
{name: "encoded URL", ref: "encoded-url/foobar", expect: "http://example.com/with%2Fslash/charts/foobar-4.2.1.tgz"},
{name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true}, {name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true},
{name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true}, {name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true},
{name: "invalid", ref: "invalid-1.2.3", fail: true}, {name: "invalid", ref: "invalid-1.2.3", fail: true},

@ -24,3 +24,5 @@ repositories:
- name: testing-https-insecureskip-tls-verify - name: testing-https-insecureskip-tls-verify
url: "https://example-https-insecureskiptlsverify.com" url: "https://example-https-insecureskiptlsverify.com"
insecure_skip_tls_verify: true insecure_skip_tls_verify: true
- name: encoded-url
url: "http://example.com/with%2Fslash"

@ -0,0 +1,15 @@
apiVersion: v1
entries:
foobar:
- name: foobar
description: Foo Chart With Encoded URL
home: https://helm.sh/helm
keywords: []
maintainers: []
sources:
- https://github.com/helm/charts
urls:
- charts/foobar-4.2.1.tgz
version: 4.2.1
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
apiVersion: v2

@ -117,9 +117,9 @@ func TestFuncs(t *testing.T) {
// version of mergo (even accidentally) that causes a breaking change. See // version of mergo (even accidentally) that causes a breaking change. See
// sprig changelog and notes for more details. // sprig changelog and notes for more details.
// Note, Go modules assume semver is never broken. So, there is no way to tell // Note, Go modules assume semver is never broken. So, there is no way to tell
// the tooling to not update to a minor or patch version. `go get -u` could be // the tooling to not update to a minor or patch version. `go install` could
// used to accidentally update mergo. This test and message should catch the // be used to accidentally update mergo. This test and message should catch
// problem and explain why it's happening. // the problem and explain why it's happening.
func TestMerge(t *testing.T) { func TestMerge(t *testing.T) {
dict := map[string]interface{}{ dict := map[string]interface{}{
"src2": map[string]interface{}{ "src2": map[string]interface{}{

@ -128,7 +128,6 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't create TLS config for client") return nil, errors.Wrap(err, "can't create TLS config for client")
} }
tlsConf.BuildNameToCertificate()
sni, err := urlutil.ExtractHostname(g.opts.url) sni, err := urlutil.ExtractHostname(g.opts.url)
if err != nil { if err != nil {

@ -291,7 +291,6 @@ func TestDownloadTLS(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(errors.Wrap(err, "can't create TLS config for client")) t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
} }
tlsConf.BuildNameToCertificate()
tlsConf.ServerName = "helm.sh" tlsConf.ServerName = "helm.sh"
tlsSrv.TLS = tlsConf tlsSrv.TLS = tlsConf
tlsSrv.StartTLS() tlsSrv.StartTLS()

@ -63,7 +63,9 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
// NewOCIGetter constructs a valid http/https client as a Getter // NewOCIGetter constructs a valid http/https client as a Getter
func NewOCIGetter(ops ...Option) (Getter, error) { func NewOCIGetter(ops ...Option) (Getter, error) {
registryClient, err := registry.NewClient() registryClient, err := registry.NewClient(
registry.ClientOptEnableCache(true),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -192,7 +192,18 @@ func (c *Client) newBuilder() *resource.Builder {
// Build validates for Kubernetes objects and returns unstructured infos. // Build validates for Kubernetes objects and returns unstructured infos.
func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) { func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) {
schema, err := c.Factory.Validator(validate) validationDirective := metav1.FieldValidationIgnore
if validate {
validationDirective = metav1.FieldValidationStrict
}
dynamicClient, err := c.Factory.DynamicClient()
if err != nil {
return nil, err
}
verifier := resource.NewQueryParamVerifier(dynamicClient, c.Factory.OpenAPIGetter(), resource.QueryParamFieldValidation)
schema, err := c.Factory.Validator(validationDirective, verifier)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -315,16 +326,20 @@ func (c *Client) Delete(resources ResourceList) (*Result, []error) {
defer mtx.Unlock() defer mtx.Unlock()
return nil return nil
} }
if err := c.skipIfNotFound(deleteResource(info)); err != nil { err := deleteResource(info)
mtx.Lock() if err == nil || apierrors.IsNotFound(err) {
defer mtx.Unlock() if err != nil {
// Collect the error and continue on c.Log("Ignoring delete failure for %q %s: %v", info.Name, info.Mapping.GroupVersionKind, err)
errs = append(errs, err) }
} else {
mtx.Lock() mtx.Lock()
defer mtx.Unlock() defer mtx.Unlock()
res.Deleted = append(res.Deleted, info) res.Deleted = append(res.Deleted, info)
return nil
} }
mtx.Lock()
defer mtx.Unlock()
// Collect the error and continue on
errs = append(errs, err)
return nil return nil
}) })
if err != nil { if err != nil {
@ -341,14 +356,6 @@ func (c *Client) Delete(resources ResourceList) (*Result, []error) {
return res, nil return res, nil
} }
func (c *Client) skipIfNotFound(err error) error {
if apierrors.IsNotFound(err) {
c.Log("%v", err)
return nil
}
return err
}
func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error { func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error {
return func(info *resource.Info) error { return func(info *resource.Info) error {
return c.watchUntilReady(t, info) return c.watchUntilReady(t, info)

@ -18,6 +18,8 @@ package kube // import "helm.sh/helm/v3/pkg/kube"
import ( import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/validation" "k8s.io/kubectl/pkg/validation"
@ -28,11 +30,19 @@ import (
type Factory interface { type Factory interface {
// ToRawKubeConfigLoader return kubeconfig loader as-is // ToRawKubeConfigLoader return kubeconfig loader as-is
ToRawKubeConfigLoader() clientcmd.ClientConfig ToRawKubeConfigLoader() clientcmd.ClientConfig
// DynamicClient returns a dynamic client ready for use
DynamicClient() (dynamic.Interface, error)
// KubernetesClientSet gives you back an external clientset // KubernetesClientSet gives you back an external clientset
KubernetesClientSet() (*kubernetes.Clientset, error) KubernetesClientSet() (*kubernetes.Clientset, error)
// NewBuilder returns an object that assists in loading objects from both disk and the server // NewBuilder returns an object that assists in loading objects from both disk and the server
// and which implements the common patterns for CLI interactions with generic resources. // and which implements the common patterns for CLI interactions with generic resources.
NewBuilder() *resource.Builder NewBuilder() *resource.Builder
// Returns a schema that can validate objects stored on disk. // Returns a schema that can validate objects stored on disk.
Validator(validate bool) (validation.Schema, error) Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error)
// OpenAPIGetter returns a getter for the openapi schema document
OpenAPIGetter() discovery.OpenAPISchemaInterface
} }

@ -353,9 +353,16 @@ func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool {
func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool {
// If the update strategy is not a rolling update, there will be nothing to wait for // If the update strategy is not a rolling update, there will be nothing to wait for
if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType { if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType {
c.log("StatefulSet skipped ready check: %s/%s. updateStrategy is %v", sts.Namespace, sts.Name, sts.Spec.UpdateStrategy.Type)
return true return true
} }
// Make sure the status is up-to-date with the StatefulSet changes
if sts.Status.ObservedGeneration < sts.Generation {
c.log("StatefulSet is not ready: %s/%s. update has not yet been observed", sts.Namespace, sts.Name)
return false
}
// Dereference all the pointers because StatefulSets like them // Dereference all the pointers because StatefulSets like them
var partition int var partition int
// 1 is the default for replicas if not set // 1 is the default for replicas if not set
@ -386,6 +393,13 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool {
c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
return false return false
} }
if sts.Status.CurrentRevision != sts.Status.UpdateRevision {
c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision)
return false
}
c.log("StatefulSet is ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas)
return true return true
} }

@ -175,6 +175,20 @@ func Test_ReadyChecker_statefulSetReady(t *testing.T) {
}, },
want: true, want: true,
}, },
{
name: "statefulset is not ready when status of latest generation has not yet been observed",
args: args{
sts: newStatefulSetWithNewGeneration("foo", 1, 0, 1, 1),
},
want: false,
},
{
name: "statefulset is not ready when current revision for current replicas does not match update revision for updated replicas",
args: args{
sts: newStatefulSetWithUpdateRevision("foo", 1, 0, 1, 1, "foo-bbbbbbb"),
},
want: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -377,8 +391,9 @@ func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberSchedul
func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet { func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet {
return &appsv1.StatefulSet{ return &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: defaultNamespace, Namespace: defaultNamespace,
Generation: int64(1),
}, },
Spec: appsv1.StatefulSetSpec{ Spec: appsv1.StatefulSetSpec{
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
@ -404,12 +419,27 @@ func newStatefulSet(name string, replicas, partition, readyReplicas, updatedRepl
}, },
}, },
Status: appsv1.StatefulSetStatus{ Status: appsv1.StatefulSetStatus{
UpdatedReplicas: int32(updatedReplicas), ObservedGeneration: int64(1),
ReadyReplicas: int32(readyReplicas), CurrentRevision: name + "-aaaaaaa",
UpdateRevision: name + "-aaaaaaa",
UpdatedReplicas: int32(updatedReplicas),
ReadyReplicas: int32(readyReplicas),
}, },
} }
} }
func newStatefulSetWithNewGeneration(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet {
ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas)
ss.Generation++
return ss
}
func newStatefulSetWithUpdateRevision(name string, replicas, partition, readyReplicas, updatedReplicas int, updateRevision string) *appsv1.StatefulSet {
ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas)
ss.Status.UpdateRevision = updateRevision
return ss
}
func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment { func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment {
return &appsv1.Deployment{ return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

@ -22,6 +22,9 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"google.golang.org/grpc/codes"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta1 "k8s.io/api/apps/v1beta1"
appsv1beta2 "k8s.io/api/apps/v1beta2" appsv1beta2 "k8s.io/api/apps/v1beta2"
@ -32,7 +35,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
) )
@ -42,6 +44,22 @@ type waiter struct {
log func(string, ...interface{}) log func(string, ...interface{})
} }
// isServiceUnavailable helps figure out if the error is caused by etcd not being available
// see https://pkg.go.dev/go.etcd.io/etcd/api/v3/v3rpc/rpctypes for `codes.Unavailable`
// we use this to check if the etcdserver is not available we should retry in case
// this is a temporary situation
func isServiceUnavailable(err error) bool {
if err != nil {
err = rpctypes.Error(err)
if ev, ok := err.(rpctypes.EtcdError); ok {
if ev.Code() == codes.Unavailable {
return true
}
}
}
return false
}
// waitForResources polls to get the current status of all pods, PVCs, Services and // waitForResources polls to get the current status of all pods, PVCs, Services and
// Jobs(optional) until all are ready or a timeout is reached // Jobs(optional) until all are ready or a timeout is reached
func (w *waiter) waitForResources(created ResourceList) error { func (w *waiter) waitForResources(created ResourceList) error {
@ -54,6 +72,9 @@ func (w *waiter) waitForResources(created ResourceList) error {
for _, v := range created { for _, v := range created {
ready, err := w.c.IsReady(ctx, v) ready, err := w.c.IsReady(ctx, v)
if !ready || err != nil { if !ready || err != nil {
if isServiceUnavailable(err) {
return false, nil
}
return false, err return false, err
} }
} }
@ -72,6 +93,9 @@ func (w *waiter) waitForDeletedResources(deleted ResourceList) error {
for _, v := range deleted { for _, v := range deleted {
err := v.Get() err := v.Get()
if err == nil || !apierrors.IsNotFound(err) { if err == nil || !apierrors.IsNotFound(err) {
if isServiceUnavailable(err) {
return false, nil
}
return false, err return false, err
} }
} }

@ -0,0 +1,42 @@
/*
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 kube // import "helm.sh/helm/v3/pkg/kube"
import (
"errors"
"testing"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
)
func Test_isServiceUnavailable(t *testing.T) {
tests := []struct {
err error
expect bool
}{
{err: nil, expect: false},
{err: errors.New("random error from somewhere"), expect: false},
{err: rpctypes.ErrGRPCLeaderChanged, expect: true},
{err: errors.New("etcdserver: leader changed"), expect: true},
}
for _, tt := range tests {
if isServiceUnavailable(tt.err) != tt.expect {
t.Errorf("failed test for %q (expect equal: %t)", tt.err, tt.expect)
}
}
}

@ -216,7 +216,7 @@ func (s *Signatory) ClearSign(chartpath string) (string, error) {
b, err := messageBlock(chartpath) b, err := messageBlock(chartpath)
if err != nil { if err != nil {
return "", nil return "", err
} }
// Sign the buffer // Sign the buffer
@ -224,9 +224,24 @@ func (s *Signatory) ClearSign(chartpath string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
_, err = io.Copy(w, b) _, err = io.Copy(w, b)
w.Close()
return out.String(), err if err != nil {
// NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which
// actually does the PGP signing, and therefore is the part which uses the private key.
// In other words, if we call Close here, there's a risk that there's an attempt to use the
// private key to sign garbage data (since we know that io.Copy failed, `w` won't contain
// anything useful).
return "", errors.Wrap(err, "failed to write to clearsign encoder")
}
err = w.Close()
if err != nil {
return "", errors.Wrap(err, "failed to either sign or armor message block")
}
return out.String(), nil
} }
// Verify checks a signature and verifies that it is legit for a chart. // Verify checks a signature and verifies that it is legit for a chart.

@ -16,6 +16,9 @@ limitations under the License.
package provenance package provenance
import ( import (
"crypto"
"fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -230,6 +233,36 @@ func TestClearSign(t *testing.T) {
} }
} }
// failSigner always fails to sign and returns an error
type failSigner struct{}
func (s failSigner) Public() crypto.PublicKey {
return nil
}
func (s failSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) {
return nil, fmt.Errorf("always fails")
}
func TestClearSignError(t *testing.T) {
signer, err := NewFromFiles(testKeyfile, testPubfile)
if err != nil {
t.Fatal(err)
}
// ensure that signing always fails
signer.Entity.PrivateKey.PrivateKey = failSigner{}
sig, err := signer.ClearSign(testChartfile)
if err == nil {
t.Fatal("didn't get an error from ClearSign but expected one")
}
if sig != "" {
t.Fatalf("expected an empty signature after failed ClearSign but got %q", sig)
}
}
func TestDecodeSignature(t *testing.T) { func TestDecodeSignature(t *testing.T) {
// Unlike other tests, this does a round-trip test, ensuring that a signature // Unlike other tests, this does a round-trip test, ensuring that a signature
// generated by the library can also be verified by the library. // generated by the library can also be verified by the library.

@ -85,7 +85,9 @@ func (pusher *OCIPusher) push(chartRef, href string) error {
// NewOCIPusher constructs a valid OCI client as a Pusher // NewOCIPusher constructs a valid OCI client as a Pusher
func NewOCIPusher(ops ...Option) (Pusher, error) { func NewOCIPusher(ops ...Option) (Pusher, error) {
registryClient, err := registry.NewClient() registryClient, err := registry.NewClient(
registry.ClientOptEnableCache(true),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -53,7 +53,8 @@ a plus (+) when pulling from a registry.`
type ( type (
// Client works with OCI-compliant registries // Client works with OCI-compliant registries
Client struct { Client struct {
debug bool debug bool
enableCache bool
// path to repository config file e.g. ~/.docker/config.json // path to repository config file e.g. ~/.docker/config.json
credentialsFile string credentialsFile string
out io.Writer out io.Writer
@ -95,12 +96,18 @@ func NewClient(options ...ClientOption) (*Client, error) {
} }
client.resolver = resolver client.resolver = resolver
} }
// allocate a cache if option is set
var cache registryauth.Cache
if client.enableCache {
cache = registryauth.DefaultCache
}
if client.registryAuthorizer == nil { if client.registryAuthorizer == nil {
client.registryAuthorizer = &registryauth.Client{ client.registryAuthorizer = &registryauth.Client{
Header: http.Header{ Header: http.Header{
"User-Agent": {version.GetUserAgent()}, "User-Agent": {version.GetUserAgent()},
}, },
Cache: registryauth.DefaultCache, Cache: cache,
Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) { Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) {
dockerClient, ok := client.authorizer.(*dockerauth.Client) dockerClient, ok := client.authorizer.(*dockerauth.Client)
if !ok { if !ok {
@ -138,6 +145,13 @@ func ClientOptDebug(debug bool) ClientOption {
} }
} }
// ClientOptEnableCache returns a function that sets the enableCache setting on a client options set
func ClientOptEnableCache(enableCache bool) ClientOption {
return func(client *Client) {
client.enableCache = enableCache
}
}
// ClientOptWriter returns a function that sets the writer setting on client options set // ClientOptWriter returns a function that sets the writer setting on client options set
func ClientOptWriter(out io.Writer) ClientOption { func ClientOptWriter(out io.Writer) ClientOption {
return func(client *Client) { return func(client *Client) {

@ -70,6 +70,7 @@ func (suite *RegistryClientTestSuite) SetupSuite() {
var err error var err error
suite.RegistryClient, err = NewClient( suite.RegistryClient, err = NewClient(
ClientOptDebug(true), ClientOptDebug(true),
ClientOptEnableCache(true),
ClientOptWriter(suite.Out), ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile), ClientOptCredentialsFile(credentialsFile),
) )

@ -253,6 +253,10 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName,
if err != nil { if err != nil {
return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL) return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL)
} }
defer func() {
os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)))
os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)))
}()
// Read the index file for the repository to get chart information and return chart URL // Read the index file for the repository to get chart information and return chart URL
repoIndex, err := LoadIndexFile(idx) repoIndex, err := LoadIndexFile(idx)

@ -24,6 +24,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -309,8 +310,15 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) {
// If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority". // If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority".
_, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{})) _, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{}))
// Go communicates with the platform and different platforms return different messages. Go itself tests darwin
if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { // differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older
// versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test
// for both messages.
if runtime.GOOS == "darwin" {
if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err)
}
} else if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err)
} }
} }

@ -208,14 +208,15 @@ func TestMerge(t *testing.T) {
if len(ind1.Entries) != 2 { if len(ind1.Entries) != 2 {
t.Errorf("Expected 2 entries, got %d", len(ind1.Entries)) t.Errorf("Expected 2 entries, got %d", len(ind1.Entries))
vs := ind1.Entries["dreadnought"] }
if len(vs) != 2 {
t.Errorf("Expected 2 versions, got %d", len(vs)) vs := ind1.Entries["dreadnought"]
} if len(vs) != 2 {
v := vs[0] t.Errorf("Expected 2 versions, got %d", len(vs))
if v.Version != "0.2.0" { }
t.Errorf("Expected %q version to be 0.2.0, got %s", v.Name, v.Version)
} if v := vs[1]; v.Version != "0.2.0" {
t.Errorf("Expected %q version to be 0.2.0, got %s", v.Name, v.Version)
} }
} }

@ -153,6 +153,7 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) {
// init test client // init test client
registryClient, err := ociRegistry.NewClient( registryClient, err := ociRegistry.NewClient(
ociRegistry.ClientOptDebug(true), ociRegistry.ClientOptDebug(true),
ociRegistry.ClientOptEnableCache(true),
ociRegistry.ClientOptWriter(os.Stdout), ociRegistry.ClientOptWriter(os.Stdout),
ociRegistry.ClientOptCredentialsFile(credentialsFile), ociRegistry.ClientOptCredentialsFile(credentialsFile),
) )
@ -370,7 +371,6 @@ func (s *Server) StartTLS() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
tlsConf.BuildNameToCertificate()
tlsConf.ServerName = "helm.sh" tlsConf.ServerName = "helm.sh"
s.srv.TLS = tlsConf s.srv.TLS = tlsConf
s.srv.StartTLS() s.srv.StartTLS()

@ -63,7 +63,7 @@ func decodeRelease(data string) (*rspb.Release, error) {
// For backwards compatibility with releases that were stored before // For backwards compatibility with releases that were stored before
// compression was introduced we skip decompression if the // compression was introduced we skip decompression if the
// gzip magic header is not found // gzip magic header is not found
if bytes.Equal(b[0:3], magicGzip) { if len(b) > 3 && bytes.Equal(b[0:3], magicGzip) {
r, err := gzip.NewReader(bytes.NewReader(b)) r, err := gzip.NewReader(bytes.NewReader(b))
if err != nil { if err != nil {
return nil, err return nil, err

@ -177,7 +177,7 @@ func (s *Storage) removeLeastRecent(name string, max int) error {
relutil.SortByRevision(h) relutil.SortByRevision(h)
lastDeployed, err := s.Deployed(name) lastDeployed, err := s.Deployed(name)
if err != nil { if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) {
return err return err
} }

@ -278,8 +278,40 @@ func TestStorageHistory(t *testing.T) {
} }
} }
func TestStorageRemoveLeastRecentWithError(t *testing.T) { var errMaxHistoryMockDriverSomethingHappened = errors.New("something happened")
storage := Init(driver.NewMemory())
type MaxHistoryMockDriver struct {
Driver driver.Driver
}
func NewMaxHistoryMockDriver(d driver.Driver) *MaxHistoryMockDriver {
return &MaxHistoryMockDriver{Driver: d}
}
func (d *MaxHistoryMockDriver) Create(key string, rls *rspb.Release) error {
return d.Driver.Create(key, rls)
}
func (d *MaxHistoryMockDriver) Update(key string, rls *rspb.Release) error {
return d.Driver.Update(key, rls)
}
func (d *MaxHistoryMockDriver) Delete(key string) (*rspb.Release, error) {
return nil, errMaxHistoryMockDriverSomethingHappened
}
func (d *MaxHistoryMockDriver) Get(key string) (*rspb.Release, error) {
return d.Driver.Get(key)
}
func (d *MaxHistoryMockDriver) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
return d.Driver.List(filter)
}
func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]*rspb.Release, error) {
return d.Driver.Query(labels)
}
func (d *MaxHistoryMockDriver) Name() string {
return d.Driver.Name()
}
func TestMaxHistoryErrorHandling(t *testing.T) {
//func TestStorageRemoveLeastRecentWithError(t *testing.T) {
storage := Init(NewMaxHistoryMockDriver(driver.NewMemory()))
storage.Log = t.Logf storage.Log = t.Logf
storage.MaxHistory = 1 storage.MaxHistory = 1
@ -297,7 +329,7 @@ func TestStorageRemoveLeastRecentWithError(t *testing.T) {
setup() setup()
rls2 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() rls2 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease()
wantErr := driver.ErrNoDeployedReleases wantErr := errMaxHistoryMockDriverSomethingHappened
gotErr := storage.Create(rls2) gotErr := storage.Create(rls2)
if !errors.Is(gotErr, wantErr) { if !errors.Is(gotErr, wantErr) {
t.Fatalf("Storing release 'angry-bird' (v2) should return the error %#v, but returned %#v", wantErr, gotErr) t.Fatalf("Storing release 'angry-bird' (v2) should return the error %#v, but returned %#v", wantErr, gotErr)
@ -444,6 +476,65 @@ func TestStorageLast(t *testing.T) {
} }
} }
// TestUpgradeInitiallyFailedRelease tests a case when there are no deployed release yet, but history limit has been
// reached: the has-no-deployed-releases error should not occur in such case.
func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) {
storage := Init(driver.NewMemory())
storage.MaxHistory = 4
const name = "angry-bird"
// setup storage with test releases
setup := func() {
// release records
rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusFailed}.ToRelease()
rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusFailed}.ToRelease()
rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease()
rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease()
// create the release records in the storage
assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)")
assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)")
assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)")
assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)")
hist, err := storage.History(name)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
wantHistoryLen := 4
if len(hist) != wantHistoryLen {
t.Fatalf("expected history of release %q to contain %d releases, got %d", name, wantHistoryLen, len(hist))
}
}
setup()
rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease()
err := storage.Create(rls5)
if err != nil {
t.Fatalf("Failed to create a new release version: %s", err)
}
hist, err := storage.History(name)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
for i, rel := range hist {
wantVersion := i + 2
if rel.Version != wantVersion {
t.Fatalf("Expected history release %d version to equal %d, got %d", i+1, wantVersion, rel.Version)
}
wantStatus := rspb.StatusFailed
if rel.Info.Status != wantStatus {
t.Fatalf("Expected history release %d status to equal %q, got %q", i+1, wantStatus, rel.Info.Status)
}
}
}
type ReleaseTestData struct { type ReleaseTestData struct {
Name string Name string
Version int Version int

@ -17,10 +17,13 @@ package strvals
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"strconv" "strconv"
"strings" "strings"
"unicode"
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -29,6 +32,10 @@ import (
// ErrNotList indicates that a non-list was treated as a list. // ErrNotList indicates that a non-list was treated as a list.
var ErrNotList = errors.New("not a list") var ErrNotList = errors.New("not a list")
// MaxIndex is the maximum index that will be allowed by setIndex.
// The default value 65536 = 1024 * 64
var MaxIndex = 65536
// ToYAML takes a string of arguments and converts to a YAML document. // ToYAML takes a string of arguments and converts to a YAML document.
func ToYAML(s string) (string, error) { func ToYAML(s string) (string, error) {
m, err := Parse(s) m, err := Parse(s)
@ -94,6 +101,18 @@ func ParseIntoString(s string, dest map[string]interface{}) error {
return t.parse() return t.parse()
} }
// ParseJSON parses a string with format key1=val1, key2=val2, ...
// where values are json strings (null, or scalars, or arrays, or objects).
// An empty val is treated as null.
//
// If a key exists in dest, the new value overwrites the dest version.
//
func ParseJSON(s string, dest map[string]interface{}) error {
scanner := bytes.NewBufferString(s)
t := newJSONParser(scanner, dest)
return t.parse()
}
// ParseIntoFile parses a filevals line and merges the result into dest. // ParseIntoFile parses a filevals line and merges the result into dest.
// //
// This method always returns a string as the value. // This method always returns a string as the value.
@ -113,9 +132,10 @@ type RunesValueReader func([]rune) (interface{}, error)
// where sc is the source of the original data being parsed // where sc is the source of the original data being parsed
// where data is the final parsed data from the parses with correct types // where data is the final parsed data from the parses with correct types
type parser struct { type parser struct {
sc *bytes.Buffer sc *bytes.Buffer
data map[string]interface{} data map[string]interface{}
reader RunesValueReader reader RunesValueReader
isjsonval bool
} }
func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser {
@ -125,6 +145,10 @@ func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *
return &parser{sc: sc, data: data, reader: stringConverter} return &parser{sc: sc, data: data, reader: stringConverter}
} }
func newJSONParser(sc *bytes.Buffer, data map[string]interface{}) *parser {
return &parser{sc: sc, data: data, reader: nil, isjsonval: true}
}
func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser { func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser {
return &parser{sc: sc, data: data, reader: reader} return &parser{sc: sc, data: data, reader: reader}
} }
@ -184,6 +208,33 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
set(data, kk, list) set(data, kk, list)
return err return err
case last == '=': case last == '=':
if t.isjsonval {
empval, err := t.emptyVal()
if err != nil {
return err
}
if empval {
set(data, string(k), nil)
return nil
}
// parse jsonvals by using Gos JSON standard library
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
var jsonval interface{}
dec := json.NewDecoder(strings.NewReader(t.sc.String()))
if err = dec.Decode(&jsonval); err != nil {
return err
}
set(data, string(k), jsonval)
if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil {
return err
}
// skip possible blanks and comma
_, err = t.emptyVal()
return err
}
//End of key. Consume =, Get value. //End of key. Consume =, Get value.
// FIXME: Get value list first // FIXME: Get value list first
vl, e := t.valList() vl, e := t.valList()
@ -205,7 +256,6 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
default: default:
return e return e
} }
case last == ',': case last == ',':
// No value given. Set the value to empty string. Return error. // No value given. Set the value to empty string. Return error.
set(data, string(k), "") set(data, string(k), "")
@ -249,6 +299,9 @@ func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{},
if index < 0 { if index < 0 {
return list, fmt.Errorf("negative %d index not allowed", index) return list, fmt.Errorf("negative %d index not allowed", index)
} }
if index > MaxIndex {
return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex)
}
if len(list) <= index { if len(list) <= index {
newlist := make([]interface{}, index+1) newlist := make([]interface{}, index+1)
copy(newlist, list) copy(newlist, list)
@ -280,6 +333,34 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
case err != nil: case err != nil:
return list, err return list, err
case last == '=': case last == '=':
if t.isjsonval {
empval, err := t.emptyVal()
if err != nil {
return list, err
}
if empval {
return setIndex(list, i, nil)
}
// parse jsonvals by using Gos JSON standard library
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
var jsonval interface{}
dec := json.NewDecoder(strings.NewReader(t.sc.String()))
if err = dec.Decode(&jsonval); err != nil {
return list, err
}
if list, err = setIndex(list, i, jsonval); err != nil {
return list, err
}
if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil {
return list, err
}
// skip possible blanks and comma
_, err = t.emptyVal()
return list, err
}
vl, e := t.valList() vl, e := t.valList()
switch e { switch e {
case nil: case nil:
@ -343,6 +424,28 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
} }
} }
// check for an empty value
// read and consume optional spaces until comma or EOF (empty val) or any other char (not empty val)
// comma and spaces are consumed, while any other char is not cosumed
func (t *parser) emptyVal() (bool, error) {
for {
r, _, e := t.sc.ReadRune()
if e == io.EOF {
return true, nil
}
if e != nil {
return false, e
}
if r == ',' {
return true, nil
}
if !unicode.IsSpace(r) {
t.sc.UnreadRune()
return false, nil
}
}
}
func (t *parser) val() ([]rune, error) { func (t *parser) val() ([]rune, error) {
stop := runeSet([]rune{','}) stop := runeSet([]rune{','})
v, _, err := runesUntil(t.sc, stop) v, _, err := runesUntil(t.sc, stop)

@ -62,6 +62,14 @@ func TestSetIndex(t *testing.T) {
val: 4, val: 4,
err: true, err: true,
}, },
{
name: "large",
initial: []interface{}{0, 1, 2, 3, 4, 5},
expect: []interface{}{0, 1, 2, 3, 4, 5},
add: MaxIndex + 1,
val: 4,
err: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -567,6 +575,107 @@ func TestParseIntoString(t *testing.T) {
} }
} }
func TestParseJSON(t *testing.T) {
tests := []struct {
input string
got map[string]interface{}
expect map[string]interface{}
err bool
}{
{ // set json scalars values, and replace one existing key
input: "outer.inner1=\"1\",outer.inner3=3,outer.inner4=true,outer.inner5=\"true\"",
got: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": "overwrite",
"inner2": "value2",
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": "1",
"inner2": "value2",
"inner3": 3,
"inner4": true,
"inner5": "true",
},
},
err: false,
},
{ // set json objects and arrays, and replace one existing key
input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5=[{\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]",
got: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": map[string]interface{}{
"x": "overwrite",
},
"inner2": "value2",
"inner3": []interface{}{
"overwrite",
},
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": map[string]interface{}{"a": "1", "b": 2, "c": []interface{}{1, 2, 3}},
"inner2": "value2",
"inner3": []interface{}{"new value 1", "new value 2"},
"inner4": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, 2, 3}},
"inner5": []interface{}{map[string]interface{}{"A": "1", "B": 2, "C": []interface{}{1, 2, 3}}},
},
},
err: false,
},
{ // null assigment, and no value assigned (equivalent to null)
input: "outer.inner1=,outer.inner3={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner3.cc[1]=null",
got: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": map[string]interface{}{
"x": "overwrite",
},
"inner2": "value2",
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": nil,
"inner2": "value2",
"inner3": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, nil, 3}},
},
},
err: false,
},
{ // syntax error
input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5={\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]",
got: nil,
expect: nil,
err: true,
},
}
for _, tt := range tests {
if err := ParseJSON(tt.input, tt.got); err != nil {
if tt.err {
continue
}
t.Fatalf("%s: %s", tt.input, err)
}
if tt.err {
t.Fatalf("%s: Expected error. Got nil", tt.input)
}
y1, err := yaml.Marshal(tt.expect)
if err != nil {
t.Fatalf("Error serializing expected value: %s", err)
}
y2, err := yaml.Marshal(tt.got)
if err != nil {
t.Fatalf("Error serializing parsed value: %s", err)
}
if string(y1) != string(y2) {
t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2)
}
}
}
func TestParseFile(t *testing.T) { func TestParseFile(t *testing.T) {
input := "name1=path1" input := "name1=path1"
expect := map[string]interface{}{ expect := map[string]interface{}{

@ -21,7 +21,7 @@ coverdir=$(mktemp -d /tmp/coverage.XXXXXXXXXX)
profile="${coverdir}/cover.out" profile="${coverdir}/cover.out"
pushd / pushd /
hash goveralls 2>/dev/null || go get github.com/mattn/goveralls hash goveralls 2>/dev/null || go install github.com/mattn/goveralls@v0.0.11
popd popd
generate_cover_data() { generate_cover_data() {

54
testdata/crt.pem vendored

@ -2,12 +2,12 @@ Certificate:
Data: Data:
Version: 3 (0x2) Version: 3 (0x2)
Serial Number: Serial Number:
55:31:53:9b:41:72:05:dc:90:49:bd:48:13:7c:59:9e:5a:53:5e:86 48:5a:94:94:51:de:97:11:3b:62:54:dd:ac:85:63:e6:40:5c:4c:f6
Signature Algorithm: sha256WithRSAEncryption Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh Issuer: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh
Validity Validity
Not Before: Nov 1 22:51:49 2019 GMT Not Before: Aug 24 18:07:59 2022 GMT
Not After : Oct 29 22:51:49 2029 GMT Not After : Aug 21 18:07:59 2032 GMT
Subject: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh Subject: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh
Subject Public Key Info: Subject Public Key Info:
Public Key Algorithm: rsaEncryption Public Key Algorithm: rsaEncryption
@ -36,26 +36,26 @@ Certificate:
X509v3 Subject Alternative Name: X509v3 Subject Alternative Name:
DNS:helm.sh, IP Address:127.0.0.1 DNS:helm.sh, IP Address:127.0.0.1
Signature Algorithm: sha256WithRSAEncryption Signature Algorithm: sha256WithRSAEncryption
4e:17:27:3d:36:4e:6c:2b:f7:d4:28:33:7e:05:26:7a:42:a0: d9:95:3b:98:01:6c:cb:a2:92:d8:f7:a7:52:2c:00:c1:04:cd:
2c:44:57:04:a0:de:df:40:fb:af:70:27:e6:55:20:f1:f8:c0: ef:1b:d8:fa:71:71:29:7d:1d:29:42:ea:03:ce:15:c6:d5:ee:
50:63:ab:b8:f1:31:5d:1e:f4:ca:8d:65:0b:d4:5e:5b:77:2f: 2d:25:51:7e:96:8b:44:2e:d9:19:1b:95:a6:9c:92:52:2b:88:
2a:af:74:5f:18:2d:92:29:7f:2d:97:fb:ec:aa:e3:1e:db:b3: d8:76:6e:1b:87:36:8e:3a:b1:c6:aa:a4:7a:4e:a9:8b:8d:c0:
8d:01:aa:82:1a:f6:28:a8:b3:ee:15:9f:9a:f5:76:37:30:f2: 3c:77:95:81:db:9a:50:f4:fb:cc:62:21:36:36:91:3b:6c:6e:
3b:38:13:b2:d4:14:94:c6:38:fa:f9:6e:94:e8:1f:11:0b:b0: 37:a8:fa:cc:21:56:f4:31:6f:07:2b:29:0e:1a:06:6c:10:87:
69:1a:b3:f9:f1:27:b4:d2:f5:64:54:7c:8f:e7:83:31:f6:0d: fa:6c:be:e1:29:8c:b9:84:b2:ea:4d:07:e8:2b:ff:f6:24:e6:
a7:0e:0e:66:d8:33:2f:e0:a1:93:56:92:58:bf:50:da:56:8e: a6:95:72:c7:d8:02:53:c2:c0:68:d3:fc:e9:72:a5:da:6c:39:
db:42:22:f5:0c:6f:f8:4c:ef:f5:7c:2d:a6:b8:60:e4:bb:df: 5a:6b:17:71:86:40:96:ac:94:dd:21:45:9e:aa:85:8a:73:4c:
a3:6c:c2:6b:99:0b:d3:0a:ad:7c:f4:74:72:9a:52:5e:81:d9: 8c:3f:0d:2b:d0:8b:04:ef:61:bb:8e:06:6b:86:46:30:a3:64:
a2:a2:dd:68:38:fb:b7:54:7f:f6:aa:ee:53:de:3d:3a:0e:86: 6b:97:01:8b:46:56:7d:42:33:f5:e0:ea:fd:80:b4:8a:50:a8:
53:ad:af:72:db:fb:6b:18:ce:ac:e4:64:70:13:68:da:be:e1: 20:2c:f9:ad:61:05:da:ff:b9:b5:da:9c:d6:0e:47:44:0c:9a:
6b:46:dd:a0:72:96:9b:3f:ba:cf:11:6e:98:03:0a:69:83:9e: 8f:11:e0:66:f8:76:0c:0f:43:99:6b:af:44:3c:5c:cb:30:98:
37:25:c9:36:b9:68:4f:73:ca:c6:32:5c:be:46:64:bb:a8:cc: 6a:24:f7:ea:23:db:cf:23:35:dd:6c:2e:9d:0a:b0:82:77:b8:
71:25:8f:be dc:90:5f:78
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDRDCCAiygAwIBAgIUVTFTm0FyBdyQSb1IE3xZnlpTXoYwDQYJKoZIhvcNAQEL MIIDRDCCAiygAwIBAgIUSFqUlFHelxE7YlTdrIVj5kBcTPYwDQYJKoZIhvcNAQEL
BQAwTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAwDgYDVQQHDAdCb3VsZGVy BQAwTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAwDgYDVQQHDAdCb3VsZGVy
MQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNoMB4XDTE5MTEwMTIyNTE0 MQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNoMB4XDTIyMDgyNDE4MDc1
OVoXDTI5MTAyOTIyNTE0OVowTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAw OVoXDTMyMDgyMTE4MDc1OVowTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAw
DgYDVQQHDAdCb3VsZGVyMQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNo DgYDVQQHDAdCb3VsZGVyMQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNo
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyIlVDQvx2ubAcH3TJ824 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyIlVDQvx2ubAcH3TJ824
qIGLfKSJ5dGxeAEd30SIC/zWgTU90Ttej7uTs34o2+3/oBM6cKP+lGsL/vtjALDL qIGLfKSJ5dGxeAEd30SIC/zWgTU90Ttej7uTs34o2+3/oBM6cKP+lGsL/vtjALDL
@ -64,10 +64,10 @@ V3SpI5vityJ6FHo96vF+MmtXbC7GT3VU+WtU0srrVByvORWb0HwP+FVRBOra+nuL
Yw+sObH2S45O9urpe+a6XlqOke/csX1SP3ODUkaDSEn/8i3KVPI2u0nMWZnAns+O Yw+sObH2S45O9urpe+a6XlqOke/csX1SP3ODUkaDSEn/8i3KVPI2u0nMWZnAns+O
eFVs7X1+g7hZLH34GoHwffUn8tuu1DFUOP5Hsu4WIA/x2y0ov2846xG7mtSyWjpK eFVs7X1+g7hZLH34GoHwffUn8tuu1DFUOP5Hsu4WIA/x2y0ov2846xG7mtSyWjpK
fwIDAQABoxwwGjAYBgNVHREEETAPggdoZWxtLnNohwR/AAABMA0GCSqGSIb3DQEB fwIDAQABoxwwGjAYBgNVHREEETAPggdoZWxtLnNohwR/AAABMA0GCSqGSIb3DQEB
CwUAA4IBAQBOFyc9Nk5sK/fUKDN+BSZ6QqAsRFcEoN7fQPuvcCfmVSDx+MBQY6u4 CwUAA4IBAQDZlTuYAWzLopLY96dSLADBBM3vG9j6cXEpfR0pQuoDzhXG1e4tJVF+
8TFdHvTKjWUL1F5bdy8qr3RfGC2SKX8tl/vsquMe27ONAaqCGvYoqLPuFZ+a9XY3 lotELtkZG5WmnJJSK4jYdm4bhzaOOrHGqqR6TqmLjcA8d5WB25pQ9PvMYiE2NpE7
MPI7OBOy1BSUxjj6+W6U6B8RC7BpGrP58Se00vVkVHyP54Mx9g2nDg5m2DMv4KGT bG43qPrMIVb0MW8HKykOGgZsEIf6bL7hKYy5hLLqTQfoK//2JOamlXLH2AJTwsBo
VpJYv1DaVo7bQiL1DG/4TO/1fC2muGDku9+jbMJrmQvTCq189HRymlJegdmiot1o 0/zpcqXabDlaaxdxhkCWrJTdIUWeqoWKc0yMPw0r0IsE72G7jgZrhkYwo2RrlwGL
OPu3VH/2qu5T3j06DoZTra9y2/trGM6s5GRwE2javuFrRt2gcpabP7rPEW6YAwpp RlZ9QjP14Or9gLSKUKggLPmtYQXa/7m12pzWDkdEDJqPEeBm+HYMD0OZa69EPFzL
g543Jck2uWhPc8rGMly+RmS7qMxxJY++ MJhqJPfqI9vPIzXdbC6dCrCCd7jckF94
-----END CERTIFICATE----- -----END CERTIFICATE-----

@ -1,19 +1,21 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE----- -----END CERTIFICATE-----

@ -1,27 +1,27 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----

Loading…
Cancel
Save