diff --git a/KEYS b/KEYS index 929938710..95e52f71c 100644 --- a/KEYS +++ b/KEYS @@ -622,3 +622,233 @@ BlA4kJPTfla4LmKRg/T/xow/naen/aM9mQCs7k2UAoeqNZ6IfQ6G5BZ81H9JNvHC beriLZDBuRy1LJRjBmZEz+UDBgZoR9oz5DOLh8dGVpkt =HZO9 -----END PGP PUBLIC KEY BLOCK----- +pub rsa4096 2014-05-13 [SCEA] + ABA2529598F6626C420D335B62F49E747D911B60 +uid [ unknown] Matt Butcher +sig 3 62F49E747D911B60 2017-08-11 Matt Butcher +sig 461449C25E36B98E 2018-12-12 Matthew Farina +sig 1EF612347F8A9958 2018-12-12 Adam Reese +sig 2CDBBFBB37AE822A 2018-12-12 Adnan Abdulhussein +uid [ unknown] technosophos (keybase.io/technosophos) +sig 3 62F49E747D911B60 2016-10-24 Matt Butcher +sig 461449C25E36B98E 2018-12-12 Matthew Farina +sig 1EF612347F8A9958 2018-12-12 Adam Reese +sig 2CDBBFBB37AE822A 2018-12-12 Adnan Abdulhussein +uid [ unknown] keybase.io/technosophos +sig 3 62F49E747D911B60 2014-05-13 Matt Butcher +sig 461449C25E36B98E 2018-12-12 Matthew Farina +sig 1EF612347F8A9958 2018-12-12 Adam Reese +sig 2CDBBFBB37AE822A 2018-12-12 Adnan Abdulhussein +sub rsa2048 2014-05-13 [S] [expires: 2022-05-11] +sig 62F49E747D911B60 2014-05-13 Matt Butcher +sub rsa2048 2014-05-13 [E] [expires: 2022-05-11] +sig 62F49E747D911B60 2014-05-13 Matt Butcher + +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: GPGTools - https://gpgtools.org + +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 +=nEJu +-----END PGP PUBLIC KEY BLOCK----- diff --git a/Makefile b/Makefile index 0185b249a..d5e36bcd1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ -BINDIR := $(CURDIR)/bin -DIST_DIRS := find * -type d -exec -TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64 -BINNAME ?= helm +BINDIR := $(CURDIR)/bin +DIST_DIRS := find * -type d -exec +TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64 +TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-386.tar.gz linux-386.tar.gz.sha256 linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 windows-amd64.zip windows-amd64.zip.sha256 +BINNAME ?= helm GOPATH = $(shell go env GOPATH) DEP = $(GOPATH)/bin/dep @@ -138,6 +139,20 @@ dist: $(DIST_DIRS) zip -r helm-${VERSION}-{}.zip {} \; \ ) +.PHONY: fetch-dist +fetch-dist: + mkdir -p _dist + cd _dist && \ + for obj in ${TARGET_OBJS} ; do \ + curl -sSL -o helm-${VERSION}-$${obj} https://get.helm.sh/helm-${VERSION}-$${obj} ; \ + done + +.PHONY: sign +sign: + for f in _dist/*.{gz,zip,sha256} ; do \ + gpg --armor --detach-sign $${f} ; \ + done + .PHONY: checksum checksum: for f in _dist/*.{gz,zip} ; do \ diff --git a/OWNERS b/OWNERS index 715c8993d..d7dac5514 100644 --- a/OWNERS +++ b/OWNERS @@ -4,6 +4,7 @@ maintainers: - fibonacci1729 - hickeyma - jdolitsky + - marckhouzam - mattfarina - michelleN - prydonius diff --git a/README.md b/README.md index 9af358e10..73fd189d7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CircleCI](https://circleci.com/gh/helm/helm.svg?style=shield)](https://circleci.com/gh/helm/helm) [![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm) -[![GoDoc](https://godoc.org/helm.sh/helm?status.svg)](https://godoc.org/helm.sh/helm) +[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v3) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. @@ -37,19 +37,19 @@ Unpack the `helm` binary and add it to your PATH and you are good to go! If you want to use a package manager: -- [Homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`. +- [Homebrew](https://brew.sh/) users can use `brew install helm`. - [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`. - [Scoop](https://scoop.sh/) users can use `scoop install helm`. - [GoFish](https://gofi.sh/) users can use `gofish install helm`. To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide). -See the [installation guide](https://docs.helm.sh/using_helm/#installing-helm) for more options, +See the [installation guide](https://helm.sh/docs/intro/install/) for more options, including installing pre-releases. ## Docs -Get started with the [Quick Start guide](https://docs.helm.sh/using_helm/#quickstart-guide) or plunge into the [complete documentation](https://docs.helm.sh) +Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs) ## Roadmap diff --git a/cmd/helm/dependency_update_test.go b/cmd/helm/dependency_update_test.go index 53d67ef59..9afc04f8d 100644 --- a/cmd/helm/dependency_update_test.go +++ b/cmd/helm/dependency_update_test.go @@ -182,7 +182,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { func createTestingMetadata(name, baseURL string) *chart.Chart { return &chart.Chart{ Metadata: &chart.Metadata{ - APIVersion: chart.APIVersionV1, + APIVersion: chart.APIVersionV2, Name: name, Version: "1.2.3", Dependencies: []*chart.Dependency{ diff --git a/cmd/helm/env.go b/cmd/helm/env.go index 1b9cb4012..2687272ba 100644 --- a/cmd/helm/env.go +++ b/cmd/helm/env.go @@ -19,6 +19,7 @@ package main import ( "fmt" "io" + "sort" "helm.sh/helm/v3/pkg/cli" @@ -55,8 +56,18 @@ type envOptions struct { } func (o *envOptions) run(out io.Writer) error { - for k, v := range o.settings.EnvVars() { - fmt.Printf("%s=\"%s\"\n", k, v) + envVars := o.settings.EnvVars() + + // Sort the variables by alphabetical order. + // This allows for a constant output across calls to 'helm env'. + var keys []string + for k := range envVars { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + fmt.Printf("%s=\"%s\"\n", k, envVars[k]) } return nil } diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 40935db17..b75dfce74 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "fmt" "io" "time" @@ -130,11 +131,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") - f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") + f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)") f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") + f.StringVar(&client.Description, "description", "", "add a custom description") f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart") f.BoolVar(&client.Atomic, "atomic", false, "if set, installation process purges chart on fail. The --wait flag will be set automatically if --atomic is used") @@ -181,6 +183,10 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options return nil, err } + if chartRequested.Metadata.Deprecated { + fmt.Fprintln(out, "WARNING: This chart is deprecated") + } + if req := chartRequested.Metadata.Dependencies; req != nil { // If CheckDependencies returns an error, we have unfulfilled dependencies. // As of Helm 2.4.0, this is treated as a stopping condition: diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index 9ab25417b..e8b573dfc 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -183,6 +183,12 @@ func TestInstall(t *testing.T) { wantError: true, golden: "output/subchart-schema-cli-negative.txt", }, + // Install deprecated chart + { + name: "install with warning about deprecated chart", + cmd: "install aeneas testdata/testcharts/deprecated --namespace default", + golden: "output/deprecated-chart.txt", + }, } runTestActionCmd(t, tests) diff --git a/cmd/helm/list.go b/cmd/helm/list.go index 32501530d..0da0c21d3 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -77,12 +77,15 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.SetStateMask() results, err := client.Run() + if err != nil { + return err + } if client.Short { for _, res := range results { fmt.Fprintln(out, res.Name) } - return err + return nil } return outfmt.Write(out, newReleaseListWriter(results)) @@ -94,7 +97,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { 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.All, "all", "a", false, "show all releases, not just the ones marked deployed or failed") - f.BoolVar(&client.Uninstalled, "uninstalled", false, "show uninstalled releases") + f.BoolVar(&client.Uninstalled, "uninstalled", false, "show uninstalled releases (if 'helm uninstall --keep-history' was used)") f.BoolVar(&client.Superseded, "superseded", false, "show superseded releases") f.BoolVar(&client.Uninstalling, "uninstalling", false, "show releases that are currently being uninstalled") f.BoolVar(&client.Deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled") diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index 53e082e96..f2fb5c01d 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -127,7 +127,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) { func manuallyProcessArgs(args []string) ([]string, []string) { known := []string{} unknown := []string{} - kvargs := []string{"--kube-context", "--namespace", "--kubeconfig", "--registry-config", "--repository-cache", "--repository-config"} + kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--registry-config", "--repository-cache", "--repository-config"} knownArg := func(a string) bool { for _, pre := range kvargs { if strings.HasPrefix(a, pre+"=") { @@ -136,13 +136,26 @@ func manuallyProcessArgs(args []string) ([]string, []string) { } return false } + + isKnown := func(v string) string { + for _, i := range kvargs { + if i == v { + return v + } + } + return "" + } + for i := 0; i < len(args); i++ { switch a := args[i]; a { case "--debug": known = append(known, a) - case "--kube-context", "--namespace", "-n", "--kubeconfig", "--registry-config", "--repository-cache", "--repository-config": - known = append(known, a, args[i+1]) + case isKnown(a): + known = append(known, a) i++ + if i < len(args) { + known = append(known, args[i]) + } default: if knownArg(a) { known = append(known, a) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index dd4bb2d17..9f7961f95 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -36,10 +36,15 @@ This command packages a chart into a versioned chart archive file. If a path is given, this will look at that path for a chart (which must contain a Chart.yaml file) and then package that directory. -If no path is given, this will look in the present working directory for a -Chart.yaml file, and (if found) build the current directory into a chart. - Versioned chart archives are used by Helm package repositories. + +To sign a chart, use the '--sign' flag. In most cases, you should also +provide '--keyring path/to/secret/keys' and '--key keyname'. + + $ helm package --sign ./mychart --key mykey --keyring ~/.gnupg/secring.gpg + +If '--keyring' is not specified, Helm usually defaults to the public keyring +unless your environment is otherwise configured. ` func newPackageCmd(out io.Writer) *cobra.Command { diff --git a/cmd/helm/plugin_test.go b/cmd/helm/plugin_test.go index 7bc8fe70c..3fd3a4197 100644 --- a/cmd/helm/plugin_test.go +++ b/cmd/helm/plugin_test.go @@ -30,14 +30,27 @@ func TestManuallyProcessArgs(t *testing.T) { "--debug", "--foo", "bar", "--kubeconfig=/home/foo", + "--kubeconfig", "/home/foo", + "--kube-context=test1", "--kube-context", "test1", + "-n=test2", "-n", "test2", + "--namespace=test2", + "--namespace", "test2", "--home=/tmp", "command", } expectKnown := []string{ - "--debug", "--kubeconfig=/home/foo", "--kube-context", "test1", "-n", "test2", + "--debug", + "--kubeconfig=/home/foo", + "--kubeconfig", "/home/foo", + "--kube-context=test1", + "--kube-context", "test1", + "-n=test2", + "-n", "test2", + "--namespace=test2", + "--namespace", "test2", } expectUnknown := []string{ diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 1c84ad8d6..e6afce3d5 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -29,7 +29,7 @@ import ( "github.com/gofrs/flock" "github.com/pkg/errors" "github.com/spf13/cobra" - "gopkg.in/yaml.v2" + "sigs.k8s.io/yaml" "helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/pkg/getter" diff --git a/cmd/helm/root.go b/cmd/helm/root.go index f0a034d45..3a15966bb 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -64,12 +64,12 @@ __helm_override_flags_to_kubectl_flags() # --kubeconfig, -n, --namespace stay the same for kubectl # --kube-context becomes --context for kubectl __helm_debug "${FUNCNAME[0]}: flags to convert: $1" - echo "$1" | sed s/kube-context/context/ + echo "$1" | \sed s/kube-context/context/ } __helm_get_repos() { - eval $(__helm_binary_name) repo list 2>/dev/null | tail +2 | cut -f1 + eval $(__helm_binary_name) repo list 2>/dev/null | \tail +2 | \cut -f1 } __helm_get_contexts() @@ -125,7 +125,7 @@ __helm_zsh_comp_nospace() { # We only do this if there is a single choice left for completion # to reduce the times the user could be presented with the fake completion choice. - out=($(echo ${in[*]} | tr " " "\n" | \grep "^${cur}")) + out=($(echo ${in[*]} | \tr " " "\n" | \grep "^${cur}")) __helm_debug "${FUNCNAME[0]}: out is ${out[*]}" [ ${#out[*]} -eq 1 ] && out+=("${out}.") @@ -145,7 +145,7 @@ __helm_list_charts() for repo in $(__helm_get_repos); do if [[ "${cur}" =~ ^${repo}/.* ]]; then # We are doing completion from within a repo - out=$(eval $(__helm_binary_name) search repo ${cur} 2>/dev/null | cut -f1 | \grep ^${cur}) + out=$(eval $(__helm_binary_name) search repo ${cur} 2>/dev/null | \cut -f1 | \grep ^${cur}) nospace=0 elif [[ ${repo} =~ ^${cur}.* ]]; then # We are completing a repo name @@ -236,7 +236,7 @@ __helm_list_plugins() __helm_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" local out # Use eval in case helm_binary_name contains a variable (e.g., $HOME/bin/h3) - if out=$(eval $(__helm_binary_name) plugin list 2>/dev/null | tail +2 | cut -f1); then + if out=$(eval $(__helm_binary_name) plugin list 2>/dev/null | \tail +2 | \cut -f1); then COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) ) fi } diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 92e12dbe0..92a947c26 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -31,12 +31,13 @@ import ( "helm.sh/helm/v3/pkg/release" ) +// NOTE: Keep the list of statuses up-to-date with pkg/release/status.go. var statusHelp = ` This command shows the status of a named release. The status consists of: - last deployment time - k8s namespace in which the release lives -- state of the release (can be: unknown, deployed, deleted, superseded, failed or deleting) +- state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback) - list of resources that this release consists of, sorted by kind - details on last test suite run, if applicable - additional notes provided by the chart diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 5312d798f..a47631c0d 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -44,6 +44,7 @@ faked locally. Additionally, none of the server-side testing of chart validity func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var validate bool + var includeCrds bool client := action.NewInstall(cfg) valueOpts := &values.Options{} var extraAPIs []string @@ -67,7 +68,14 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var manifests bytes.Buffer + if includeCrds { + for _, f := range rel.Chart.CRDs() { + fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", f.Name, f.Data) + } + } + fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) + if !client.DisableHooks { for _, m := range rel.Hooks { fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) @@ -120,6 +128,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates") f.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") f.BoolVar(&validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install") + f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the templated output") + f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall") f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions") return cmd diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index 8aa2f6c06..735829432 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -79,6 +79,11 @@ func TestTemplateCmd(t *testing.T) { cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test '%s'", chartPath), golden: "output/template-with-api-version.txt", }, + { + name: "template with CRDs", + cmd: fmt.Sprintf("template '%s' --include-crds", chartPath), + golden: "output/template-with-crds.txt", + }, } runTestCmd(t, tests) } diff --git a/cmd/helm/testdata/output/deprecated-chart.txt b/cmd/helm/testdata/output/deprecated-chart.txt new file mode 100644 index 000000000..e5be2c3f1 --- /dev/null +++ b/cmd/helm/testdata/output/deprecated-chart.txt @@ -0,0 +1,7 @@ +WARNING: This chart is deprecated +NAME: aeneas +LAST DEPLOYED: Fri Sep 2 22:04:05 1977 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +TEST SUITE: None diff --git a/cmd/helm/testdata/output/search-single.txt b/cmd/helm/testdata/output/search-single.txt deleted file mode 100644 index 936605ae1..000000000 --- a/cmd/helm/testdata/output/search-single.txt +++ /dev/null @@ -1,2 +0,0 @@ -NAME CHART VERSION APP VERSION DESCRIPTION -testing/mariadb 0.3.0 Chart for MariaDB diff --git a/cmd/helm/testdata/output/status-with-resource.txt b/cmd/helm/testdata/output/status-with-resource.txt deleted file mode 100644 index 5f6f4e663..000000000 --- a/cmd/helm/testdata/output/status-with-resource.txt +++ /dev/null @@ -1,9 +0,0 @@ -NAME: flummoxed-chickadee -LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC -NAMESPACE: default -STATUS: deployed - -RESOURCES: -resource A -resource B - diff --git a/cmd/helm/testdata/output/status.yaml b/cmd/helm/testdata/output/status.yaml deleted file mode 100644 index a7bc12276..000000000 --- a/cmd/helm/testdata/output/status.yaml +++ /dev/null @@ -1,10 +0,0 @@ -info: - deleted: "0001-01-01T00:00:00Z" - first_deployed: "0001-01-01T00:00:00Z" - last_deployed: "2016-01-16T00:00:00Z" - resources: | - resource A - resource B - status: deployed -name: flummoxed-chickadee -namespace: default diff --git a/cmd/helm/testdata/output/template-with-crds.txt b/cmd/helm/testdata/output/template-with-crds.txt new file mode 100644 index 000000000..9fa1c7e6d --- /dev/null +++ b/cmd/helm/testdata/output/template-with-crds.txt @@ -0,0 +1,72 @@ +--- +# Source: crds/crdA.yaml +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: testCRDs +spec: + group: testCRDGroups + names: + kind: TestCRD + listKind: TestCRDList + plural: TestCRDs + shortNames: + - tc + singular: authconfig + +--- +# Source: subchart1/charts/subcharta/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: subcharta + labels: + helm.sh/chart: "subcharta-0.1.0" +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: apache + selector: + app.kubernetes.io/name: subcharta +--- +# Source: subchart1/charts/subchartb/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: subchartb + labels: + helm.sh/chart: "subchartb-0.1.0" +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: nginx + selector: + app.kubernetes.io/name: subchartb +--- +# Source: subchart1/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: subchart1 + labels: + helm.sh/chart: "subchart1-0.1.0" + app.kubernetes.io/instance: "RELEASE-NAME" + kube-version/major: "1" + kube-version/minor: "16" + kube-version/version: "v1.16.0" + kube-api-version/test: v1 +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: nginx + selector: + app.kubernetes.io/name: subchart1 diff --git a/cmd/helm/testdata/output/version-client-shorthand.txt b/cmd/helm/testdata/output/version-client-shorthand.txt index 0d9b536e6..4b493d31c 100644 --- a/cmd/helm/testdata/output/version-client-shorthand.txt +++ b/cmd/helm/testdata/output/version-client-shorthand.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.0+unreleased", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.0", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-client.txt b/cmd/helm/testdata/output/version-client.txt index 0d9b536e6..4b493d31c 100644 --- a/cmd/helm/testdata/output/version-client.txt +++ b/cmd/helm/testdata/output/version-client.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.0+unreleased", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.0", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/output/version-short.txt b/cmd/helm/testdata/output/version-short.txt index 5e7749489..d9fb9c736 100644 --- a/cmd/helm/testdata/output/version-short.txt +++ b/cmd/helm/testdata/output/version-short.txt @@ -1 +1 @@ -v3.0+unreleased +v3.0 diff --git a/cmd/helm/testdata/output/version-template.txt b/cmd/helm/testdata/output/version-template.txt index c12e29c2d..776c1919b 100644 --- a/cmd/helm/testdata/output/version-template.txt +++ b/cmd/helm/testdata/output/version-template.txt @@ -1 +1 @@ -Version: v3.0+unreleased \ No newline at end of file +Version: v3.0 \ No newline at end of file diff --git a/cmd/helm/testdata/output/version.txt b/cmd/helm/testdata/output/version.txt index 0d9b536e6..4b493d31c 100644 --- a/cmd/helm/testdata/output/version.txt +++ b/cmd/helm/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v3.0+unreleased", GitCommit:"", GitTreeState:"", GoVersion:""} +version.BuildInfo{Version:"v3.0", GitCommit:"", GitTreeState:"", GoVersion:""} diff --git a/cmd/helm/testdata/testcharts/deprecated/Chart.yaml b/cmd/helm/testdata/testcharts/deprecated/Chart.yaml new file mode 100644 index 000000000..10185beeb --- /dev/null +++ b/cmd/helm/testdata/testcharts/deprecated/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +description: Deprecated testing chart +home: https://helm.sh/helm +name: deprecated +sources: + - https://github.com/helm/helm +version: 0.1.0 +deprecated: true diff --git a/cmd/helm/testdata/testcharts/deprecated/README.md b/cmd/helm/testdata/testcharts/deprecated/README.md new file mode 100644 index 000000000..0df9a8bbc --- /dev/null +++ b/cmd/helm/testdata/testcharts/deprecated/README.md @@ -0,0 +1,3 @@ +#Deprecated + +This space intentionally left blank. diff --git a/cmd/helm/uninstall.go b/cmd/helm/uninstall.go index 27b5a8426..7096d7873 100644 --- a/cmd/helm/uninstall.go +++ b/cmd/helm/uninstall.go @@ -69,6 +69,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.StringVar(&client.Description, "description", "", "add a custom description") return cmd } diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index eadc3b63d..acfc23198 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -127,6 +127,10 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } + if ch.Metadata.Deprecated { + fmt.Fprintln(out, "WARNING: This chart is deprecated") + } + rel, err := client.Run(args[0], ch, vals) if err != nil { return errors.Wrap(err, "UPGRADE FAILED") @@ -156,6 +160,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.IntVar(&client.MaxHistory, "history-max", 10, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") + f.StringVar(&client.Description, "description", "", "add a custom description") addChartPathOptionsFlags(f, &client.ChartPathOptions) addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) diff --git a/code-of-conduct.md b/code-of-conduct.md index 0d15c00cf..91ccaf035 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,3 +1,3 @@ -# Kubernetes Community Code of Conduct +# Community Code of Conduct -Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) +Helm follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). diff --git a/go.mod b/go.mod index 57b342ed2..8e972fdfe 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e // indirect - github.com/Masterminds/semver/v3 v3.0.1 - github.com/Masterminds/sprig/v3 v3.0.0 + github.com/Masterminds/semver/v3 v3.0.3 + github.com/Masterminds/sprig/v3 v3.0.2 github.com/Masterminds/vcs v1.13.0 github.com/Microsoft/go-winio v0.4.12 // indirect github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a diff --git a/go.sum b/go.sum index 5659b06f8..fb11fdbc4 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,12 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk= github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.0.0 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4G3t3g= github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U= +github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8= +github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= github.com/Masterminds/vcs v1.13.0 h1:USF5TvZGYgIpcbNAEMLfFhHqP08tFZVlUVrmTSpqnyA= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= diff --git a/internal/tlsutil/cfg.go b/internal/tlsutil/cfg.go index f40258739..8b9d4329f 100644 --- a/internal/tlsutil/cfg.go +++ b/internal/tlsutil/cfg.go @@ -27,18 +27,14 @@ import ( // Options represents configurable options used to create client and server TLS configurations. type Options struct { CaCertFile string - // If either the KeyFile or CertFile is empty, ClientConfig() will not load them, - // preventing Helm from authenticating to Tiller. They are required to be non-empty - // when calling ServerConfig, otherwise an error is returned. + // If either the KeyFile or CertFile is empty, ClientConfig() will not load them. KeyFile string CertFile string // Client-only options InsecureSkipVerify bool - // Server-only options - ClientAuth tls.ClientAuthType } -// ClientConfig retusn a TLS configuration for use by a Helm client. +// ClientConfig returns a TLS configuration for use by a Helm client. func ClientConfig(opts Options) (cfg *tls.Config, err error) { var cert *tls.Certificate var pool *x509.CertPool @@ -60,24 +56,3 @@ func ClientConfig(opts Options) (cfg *tls.Config, err error) { cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool} return cfg, nil } - -// ServerConfig returns a TLS configuration for use by the Tiller server. -func ServerConfig(opts Options) (cfg *tls.Config, err error) { - var cert *tls.Certificate - var pool *x509.CertPool - - if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil { - if os.IsNotExist(err) { - return nil, errors.Wrapf(err, "could not load x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile) - } - return nil, errors.Wrapf(err, "could not read x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile) - } - if opts.ClientAuth >= tls.VerifyClientCertIfGiven && opts.CaCertFile != "" { - if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil { - return nil, err - } - } - - cfg = &tls.Config{MinVersion: tls.VersionTLS12, ClientAuth: opts.ClientAuth, Certificates: []tls.Certificate{*cert}, ClientCAs: pool} - return cfg, nil -} diff --git a/internal/tlsutil/tlsutil_test.go b/internal/tlsutil/tlsutil_test.go index a737226fd..24551fdec 100644 --- a/internal/tlsutil/tlsutil_test.go +++ b/internal/tlsutil/tlsutil_test.go @@ -17,7 +17,6 @@ limitations under the License. package tlsutil import ( - "crypto/tls" "path/filepath" "testing" ) @@ -54,26 +53,6 @@ func TestClientConfig(t *testing.T) { } } -func TestServerConfig(t *testing.T) { - opts := Options{ - CaCertFile: testfile(t, testCaCertFile), - CertFile: testfile(t, testCertFile), - KeyFile: testfile(t, testKeyFile), - ClientAuth: tls.RequireAndVerifyClientCert, - } - - cfg, err := ServerConfig(opts) - if err != nil { - t.Fatalf("error building tls server config: %v", err) - } - if got := cfg.MinVersion; got != tls.VersionTLS12 { - t.Errorf("expecting TLS version 1.2, got %d", got) - } - if got := cfg.ClientCAs; got == nil { - t.Errorf("expecting non-nil CA pool") - } -} - func testfile(t *testing.T, file string) (path string) { var err error if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil { diff --git a/internal/version/version.go b/internal/version/version.go index 131a2b130..22439d11b 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -33,7 +33,7 @@ var ( version = "v3.0" // metadata is extra build time data - metadata = "unreleased" + metadata = "" // gitCommit is the git sha1 gitCommit = "" // gitTreeState is the state of the git tree diff --git a/pkg/action/get_values.go b/pkg/action/get_values.go index 5bc3a7005..9c32db213 100644 --- a/pkg/action/get_values.go +++ b/pkg/action/get_values.go @@ -39,6 +39,10 @@ func NewGetValues(cfg *Configuration) *GetValues { // Run executes 'helm get values' against the given release. func (g *GetValues) Run(name string) (map[string]interface{}, error) { + if err := g.cfg.KubeClient.IsReachable(); err != nil { + return nil, err + } + rel, err := g.cfg.releaseContent(name, g.Version) if err != nil { return nil, err diff --git a/pkg/action/install.go b/pkg/action/install.go index 4019dbc54..dc5941810 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -79,6 +79,7 @@ type Install struct { ReleaseName string GenerateName bool NameTemplate string + Description string OutputDir string Atomic bool SkipCRDs bool @@ -86,6 +87,8 @@ type Install struct { // APIVersions allows a manual set of supported API Versions to be passed // (for things like templating). These are ignored if ClientOnly is false APIVersions chartutil.VersionSet + // Used by helm template to render charts with .Relase.IsUpgrade. Ignored if Dry-Run is false + IsUpgrade bool } // ChartPathOptions captures common options used for controlling chart paths @@ -197,11 +200,14 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. return nil, err } + //special case for helm template --is-upgrade + isUpgrade := i.IsUpgrade && i.DryRun options := chartutil.ReleaseOptions{ Name: i.ReleaseName, Namespace: i.Namespace, Revision: 1, - IsInstall: true, + IsInstall: !isUpgrade, + IsUpgrade: isUpgrade, } valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) if err != nil { @@ -237,7 +243,7 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // we'll end up in a state where we will delete those resources upon // deleting the release because the manifest will be pointing at that // resource - if !i.ClientOnly { + if !i.ClientOnly && !isUpgrade { if err := existingResourceConflict(resources); err != nil { return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") } @@ -292,7 +298,11 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. } } - rel.SetStatus(release.StatusDeployed, "Install complete") + if len(i.Description) > 0 { + rel.SetStatus(release.StatusDeployed, i.Description) + } else { + rel.SetStatus(release.StatusDeployed, "Install complete") + } // This is a tricky case. The release has been created, but the result // cannot be recorded. The truest thing to tell the user is that the @@ -301,7 +311,9 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. // // One possible strategy would be to do a timed retry to see if we can get // this stored in the future. - i.recordRelease(rel) + if err := i.recordRelease(rel); err != nil { + i.cfg.Log("failed to record the release: %s", err) + } return rel, nil } @@ -420,7 +432,7 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values if ch.Metadata.KubeVersion != "" { if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { - return hs, b, "", errors.Errorf("chart requires kubernetesVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) + return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) } } @@ -543,6 +555,10 @@ func (i *Install) NameAndChart(args []string) (string, string, error) { return nil } + if len(args) > 2 { + return args[0], args[1], errors.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", ")) + } + if len(args) == 2 { return args[0], args[1], flagsNotSet() } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index bb36b843d..d6f1c88cd 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -306,7 +306,7 @@ func TestInstallRelease_KubeVersion(t *testing.T) { vals = map[string]interface{}{} _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) is.Error(err) - is.Contains(err.Error(), "chart requires kubernetesVersion") + is.Contains(err.Error(), "chart requires kubeVersion") } func TestInstallRelease_Wait(t *testing.T) { @@ -505,6 +505,14 @@ func TestNameAndChart(t *testing.T) { t.Fatal("expected an error") } is.Equal("must either provide a name or specify --generate-name", err.Error()) + + instAction.NameTemplate = "" + instAction.ReleaseName = "" + _, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"}) + if err == nil { + t.Fatal("expected an error") + } + is.Equal("expected at most two arguments, unexpected arguments: bar", err.Error()) } func TestNameAndChartGenerateName(t *testing.T) { diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index cd86aee68..378a747b0 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -237,27 +237,36 @@ func makeMeSomeReleases(store *storage.Storage, t *testing.T) { } func TestFilterList(t *testing.T) { - one := releaseStub() - one.Name = "one" - one.Namespace = "default" - one.Version = 1 - two := releaseStub() - two.Name = "two" - two.Namespace = "default" - two.Version = 1 - anotherOldOne := releaseStub() - anotherOldOne.Name = "one" - anotherOldOne.Namespace = "testing" - anotherOldOne.Version = 1 - anotherOne := releaseStub() - anotherOne.Name = "one" - anotherOne.Namespace = "testing" - anotherOne.Version = 2 + t.Run("should filter old versions of the same release", func(t *testing.T) { + r1 := releaseStub() + r1.Name = "r" + r1.Version = 1 + r2 := releaseStub() + r2.Name = "r" + r2.Version = 2 + another := releaseStub() + another.Name = "another" + another.Version = 1 - list := []*release.Release{one, two, anotherOne} - expectedFilteredList := []*release.Release{one, two, anotherOne} + filteredList := filterList([]*release.Release{r1, r2, another}) + expectedFilteredList := []*release.Release{r2, another} - filteredList := filterList(list) + assert.ElementsMatch(t, expectedFilteredList, filteredList) + }) - assert.ElementsMatch(t, expectedFilteredList, filteredList) + t.Run("should not filter out any version across namespaces", func(t *testing.T) { + r1 := releaseStub() + r1.Name = "r" + r1.Namespace = "default" + r1.Version = 1 + r2 := releaseStub() + r2.Name = "r" + r2.Namespace = "testing" + r2.Version = 2 + + filteredList := filterList([]*release.Release{r1, r2}) + expectedFilteredList := []*release.Release{r1, r2} + + assert.ElementsMatch(t, expectedFilteredList, filteredList) + }) } diff --git a/pkg/action/pull.go b/pkg/action/pull.go index aaf63861e..b0a3d2598 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -63,6 +63,7 @@ func (p *Pull) Run(chartRef string) (string, error) { Getters: getter.All(p.Settings), Options: []getter.Option{ getter.WithBasicAuth(p.Username, p.Password), + getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), }, RepositoryConfig: p.Settings.RepositoryConfig, RepositoryCache: p.Settings.RepositoryCache, diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index fb72a845b..dfaa98472 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -37,6 +37,7 @@ type Uninstall struct { DryRun bool KeepHistory bool Timeout time.Duration + Description string } // NewUninstall creates a new Uninstall object with the given configuration. @@ -118,7 +119,11 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } rel.Info.Status = release.StatusUninstalled - rel.Info.Description = "Uninstallation complete" + if len(u.Description) > 0 { + rel.Info.Description = u.Description + } else { + rel.Info.Description = "Uninstallation complete" + } if !u.KeepHistory { u.cfg.Log("purge requested for %s", name) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index d0495a864..cdc40eaaa 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -58,6 +58,7 @@ type Upgrade struct { Atomic bool CleanupOnFail bool SubNotes bool + Description string } // NewUpgrade creates a new Upgrade object with the given configuration. @@ -218,7 +219,11 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea if u.DryRun { u.cfg.Log("dry run for %s", upgradedRelease.Name) - upgradedRelease.Info.Description = "Dry run complete" + if len(u.Description) > 0 { + upgradedRelease.Info.Description = u.Description + } else { + upgradedRelease.Info.Description = "Dry run complete" + } return upgradedRelease, nil } @@ -270,7 +275,11 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea u.cfg.recordRelease(originalRelease) upgradedRelease.Info.Status = release.StatusDeployed - upgradedRelease.Info.Description = "Upgrade complete" + if len(u.Description) > 0 { + upgradedRelease.Info.Description = u.Description + } else { + upgradedRelease.Info.Description = "Upgrade complete" + } return upgradedRelease, nil } diff --git a/pkg/chart/loader/archive.go b/pkg/chart/loader/archive.go index 3c50fe379..7e187a170 100644 --- a/pkg/chart/loader/archive.go +++ b/pkg/chart/loader/archive.go @@ -126,6 +126,12 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { continue } + switch hd.Typeflag { + // We don't want to process these extension header files. + case tar.TypeXGlobalHeader, tar.TypeXHeader: + continue + } + // Archive could contain \ if generated on Windows delimiter := "/" if strings.ContainsRune(hd.Name, '\\') { diff --git a/pkg/chart/loader/archive_test.go b/pkg/chart/loader/archive_test.go new file mode 100644 index 000000000..7d8c8b51e --- /dev/null +++ b/pkg/chart/loader/archive_test.go @@ -0,0 +1,110 @@ +/* +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 loader + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "testing" +) + +func TestLoadArchiveFiles(t *testing.T) { + tcs := []struct { + name string + generate func(w *tar.Writer) + check func(t *testing.T, files []*BufferedFile, err error) + }{ + { + name: "empty input should return no files", + generate: func(w *tar.Writer) {}, + check: func(t *testing.T, files []*BufferedFile, err error) { + if err.Error() != "no files in chart archive" { + t.Fatalf(`expected "no files in chart archive", got [%#v]`, err) + } + }, + }, + { + name: "should ignore files with XGlobalHeader type", + generate: func(w *tar.Writer) { + // simulate the presence of a `pax_global_header` file like you would get when + // processing a GitHub release archive. + _ = w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeXGlobalHeader, + Name: "pax_global_header", + }) + + // we need to have at least one file, otherwise we'll get the "no files in chart archive" error + _ = w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: "dir/empty", + }) + }, + check: func(t *testing.T, files []*BufferedFile, err error) { + if err != nil { + t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err) + } + + if len(files) != 1 { + t.Fatalf(`expected to get one file but got [%v]`, files) + } + }, + }, + { + name: "should ignore files with TypeXHeader type", + generate: func(w *tar.Writer) { + // simulate the presence of a `pax_header` file like you might get when + // processing a GitHub release archive. + _ = w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeXHeader, + Name: "pax_header", + }) + + // we need to have at least one file, otherwise we'll get the "no files in chart archive" error + _ = w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: "dir/empty", + }) + }, + check: func(t *testing.T, files []*BufferedFile, err error) { + if err != nil { + t.Fatalf(`got unwanted error [%#v] for tar file with pax_header content`, err) + } + + if len(files) != 1 { + t.Fatalf(`expected to get one file but got [%v]`, files) + } + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + buf := &bytes.Buffer{} + gzw := gzip.NewWriter(buf) + tw := tar.NewWriter(gzw) + + tc.generate(tw) + + _ = tw.Close() + _ = gzw.Close() + + files, err := LoadArchiveFiles(buf) + tc.check(t, files, err) + }) + } +} diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go index 3c38519bc..dd4fd2dff 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/loader/load.go @@ -114,12 +114,18 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { return c, errors.Wrap(err, "cannot load requirements.yaml") } + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data}) + } // Deprecated: requirements.lock is deprecated use Chart.lock. case f.Name == "requirements.lock": c.Lock = new(chart.Lock) if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { return c, errors.Wrap(err, "cannot load requirements.lock") } + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data}) + } case strings.HasPrefix(f.Name, "templates/"): c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data}) diff --git a/pkg/chartutil/chartfile.go b/pkg/chartutil/chartfile.go index 68176ed5d..756b87cfb 100644 --- a/pkg/chartutil/chartfile.go +++ b/pkg/chartutil/chartfile.go @@ -42,7 +42,16 @@ func LoadChartfile(filename string) (*chart.Metadata, error) { // // 'filename' should be the complete path and filename ('foo/Chart.yaml') func SaveChartfile(filename string, cf *chart.Metadata) error { + // Pull out the dependencies of a v1 Chart, since there's no way + // to tell the serialiser to skip a field for just this use case + savedDependencies := cf.Dependencies + if cf.APIVersion == chart.APIVersionV1 { + cf.Dependencies = nil + } out, err := yaml.Marshal(cf) + if cf.APIVersion == chart.APIVersionV1 { + cf.Dependencies = savedDependencies + } if err != nil { return err } diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index bc8f5bdd9..e264c4391 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -141,8 +141,17 @@ func Save(c *chart.Chart, outDir string) (string, error) { func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { base := filepath.Join(prefix, c.Name()) + // Pull out the dependencies of a v1 Chart, since there's no way + // to tell the serialiser to skip a field for just this use case + savedDependencies := c.Metadata.Dependencies + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Metadata.Dependencies = nil + } // Save Chart.yaml cdata, err := yaml.Marshal(c.Metadata) + if c.Metadata.APIVersion == chart.APIVersionV1 { + c.Metadata.Dependencies = savedDependencies + } if err != nil { return err } diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml new file mode 100644 index 000000000..fca77fd4b --- /dev/null +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: testCRDs +spec: + group: testCRDGroups + names: + kind: TestCRD + listKind: TestCRDList + plural: TestCRDs + shortNames: + - tc + singular: authconfig diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 8e251bc89..f3d4321c5 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -214,6 +214,10 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er c.Options = append(c.Options, getter.WithBasicAuth(r.Config.Username, r.Config.Password)) } + if r.Config.CertFile != "" || r.Config.KeyFile != "" || r.Config.CAFile != "" { + c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)) + } + // Next, we need to load the index, and actually look up the chart. idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) i, err := repo.LoadIndexFile(idxFile) diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 80249e240..e0692c8c8 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -80,6 +80,67 @@ func TestResolveChartRef(t *testing.T) { } } +func TestResolveChartOpts(t *testing.T) { + tests := []struct { + name, ref, version string + expect []getter.Option + }{ + { + name: "repo with CA-file", + ref: "testing-ca-file/foo", + expect: []getter.Option{ + getter.WithURL("https://example.com/foo-1.2.3.tgz"), + getter.WithTLSClientConfig("cert", "key", "ca"), + }, + }, + } + + c := ChartDownloader{ + Out: os.Stderr, + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + Getters: getter.All(&cli.EnvSettings{ + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + }), + } + + // snapshot options + snapshotOpts := c.Options + + for _, tt := range tests { + // reset chart downloader options for each test case + c.Options = snapshotOpts + + expect, err := getter.NewHTTPGetter(tt.expect...) + if err != nil { + t.Errorf("%s: failed to setup http client: %s", tt.name, err) + continue + } + + u, err := c.ResolveChartVersion(tt.ref, tt.version) + if err != nil { + t.Errorf("%s: failed with error %s", tt.name, err) + continue + } + + got, err := getter.NewHTTPGetter( + append( + c.Options, + getter.WithURL(u.String()), + )..., + ) + if err != nil { + t.Errorf("%s: failed to create http client: %s", tt.name, err) + continue + } + + if *(got.(*getter.HTTPGetter)) != *(expect.(*getter.HTTPGetter)) { + t.Errorf("%s: expected %s, got %s", tt.name, expect, got) + } + } +} + func TestVerifyChart(t *testing.T) { v, err := VerifyChart("testdata/signtest-0.1.0.tgz", "testdata/helm-test-key.pub") if err != nil { diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 4e9d691d8..e46af6944 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -149,6 +149,13 @@ func (m *Manager) Update() error { return err } + // downloadAll might overwrite dependency version, recalculate lock digest + newDigest, err := resolver.HashReq(req, lock.Dependencies) + if err != nil { + return err + } + lock.Digest = newDigest + // If the lock file hasn't changed, don't write a new one. oldLock := c.Lock if oldLock != nil && oldLock.Digest == lock.Digest { @@ -156,7 +163,7 @@ func (m *Manager) Update() error { } // Finally, we need to write the lockfile. - return writeLock(m.ChartPath, lock) + return writeLock(m.ChartPath, lock, c.Metadata.APIVersion == chart.APIVersionV1) } func (m *Manager) loadChartDir() (*chart.Chart, error) { @@ -627,12 +634,16 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err } // writeLock writes a lockfile to disk -func writeLock(chartpath string, lock *chart.Lock) error { +func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error { data, err := yaml.Marshal(lock) if err != nil { return err } - dest := filepath.Join(chartpath, "Chart.lock") + lockfileName := "Chart.lock" + if legacyLockfile { + lockfileName = "requirements.lock" + } + dest := filepath.Join(chartpath, lockfileName) return ioutil.WriteFile(dest, data, 0644) } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 6e32b3e85..ea235c13f 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -181,6 +181,74 @@ func TestGetRepoNames(t *testing.T) { } } +func TestUpdateBeforeBuild(t *testing.T) { + // Set up a fake repo + srv, err := repotest.NewTempServer("testdata/*.tgz*") + if err != nil { + t.Fatal(err) + } + defer srv.Stop() + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + dir := func(p ...string) string { + return filepath.Join(append([]string{srv.Root()}, p...)...) + } + + // Save dep + d := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "dep-chart", + Version: "0.1.0", + APIVersion: "v1", + }, + } + if err := chartutil.SaveDir(d, dir()); err != nil { + t.Fatal(err) + } + // Save a chart + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "with-dependency", + Version: "0.1.0", + APIVersion: "v2", + Dependencies: []*chart.Dependency{{ + Name: d.Metadata.Name, + Version: ">=0.1.0", + Repository: "file://../dep-chart", + }}, + }, + } + if err := chartutil.SaveDir(c, dir()); err != nil { + t.Fatal(err) + } + + // Set-up a manager + b := bytes.NewBuffer(nil) + g := getter.Providers{getter.Provider{ + Schemes: []string{"http", "https"}, + New: getter.NewHTTPGetter, + }} + m := &Manager{ + ChartPath: dir(c.Metadata.Name), + Out: b, + Getters: g, + RepositoryConfig: dir("repositories.yaml"), + RepositoryCache: dir(), + } + + // Update before Build. see issue: https://github.com/helm/helm/issues/7101 + err = m.Update() + if err != nil { + t.Fatal(err) + } + + err = m.Build() + if err != nil { + t.Fatal(err) + } +} + // This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874. // // This function is used by below tests that ensures success of build operation @@ -217,7 +285,7 @@ func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Depe Metadata: &chart.Metadata{ Name: chartName, Version: "0.1.0", - APIVersion: "v1", + APIVersion: "v2", Dependencies: []*chart.Dependency{&dep}, }, } diff --git a/pkg/downloader/testdata/repositories.yaml b/pkg/downloader/testdata/repositories.yaml index 374d95c8a..430865269 100644 --- a/pkg/downloader/testdata/repositories.yaml +++ b/pkg/downloader/testdata/repositories.yaml @@ -15,4 +15,9 @@ repositories: - name: testing-relative url: "http://example.com/helm" - name: testing-relative-trailing-slash - url: "http://example.com/helm/" \ No newline at end of file + url: "http://example.com/helm/" + - name: testing-ca-file + url: "https://example.com" + certFile: "cert" + keyFile: "key" + caFile: "ca" diff --git a/pkg/downloader/testdata/repository/testing-ca-file-index.yaml b/pkg/downloader/testdata/repository/testing-ca-file-index.yaml new file mode 100644 index 000000000..17cdde1c6 --- /dev/null +++ b/pkg/downloader/testdata/repository/testing-ca-file-index.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +entries: + foo: + - name: foo + description: Foo Chart + home: https://helm.sh/helm + keywords: [] + maintainers: [] + sources: + - https://github.com/helm/charts + urls: + - https://example.com/foo-1.2.3.tgz + version: 1.2.3 + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index dae0b6be7..5a7d54993 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -93,11 +93,19 @@ func warnWrap(warn string) string { // initFunMap creates the Engine's FuncMap and adds context-specific functions. func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { funcMap := funcMap() + includedNames := make([]string, 0) // Add the 'include' function here so we can close over t. funcMap["include"] = func(name string, data interface{}) (string, error) { var buf strings.Builder + for _, n := range includedNames { + if n == name { + return "", errors.Wrapf(fmt.Errorf("unable to excute template"), "rendering template has a nested reference name: %s", name) + } + } + includedNames = append(includedNames, name) err := t.ExecuteTemplate(&buf, name, data) + includedNames = includedNames[:len(includedNames)-1] return buf.String(), err } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 031527dd0..f3609fcbd 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -460,6 +460,15 @@ func TestAlterFuncMap_include(t *testing.T) { }, } + // Check nested reference in include FuncMap + d := &chart.Chart{ + Metadata: &chart.Metadata{Name: "nested"}, + Templates: []*chart.File{ + {Name: "templates/quote", Data: []byte(`{{include "nested/templates/quote" . | indent 2}} dead.`)}, + {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, + }, + } + v := chartutil.Values{ "Values": "", "Chart": c.Metadata, @@ -477,6 +486,12 @@ func TestAlterFuncMap_include(t *testing.T) { if got := out["conrad/templates/quote"]; got != expect { t.Errorf("Expected %q, got %q (%v)", expect, got, out) } + + _, err = Render(d, v) + expectErrName := "nested/templates/quote" + if err == nil { + t.Errorf("Expected err of nested reference name: %v", expectErrName) + } } func TestAlterFuncMap_require(t *testing.T) { diff --git a/pkg/kube/client.go b/pkg/kube/client.go index ab1f600ff..96179d19d 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -34,6 +34,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" @@ -41,6 +42,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/kubernetes/scheme" + cachetools "k8s.io/client-go/tools/cache" watchtools "k8s.io/client-go/tools/watch" cmdutil "k8s.io/kubectl/pkg/cmd/util" ) @@ -419,10 +421,13 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) - w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) + // Use a selector on the name of the resource. This should be unique for the + // given version and kind + selector, err := fields.ParseSelector(fmt.Sprintf("metadata.name=%s", info.Name)) if err != nil { return err } + lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, selector) // What we watch for depends on the Kind. // - For a Job, we watch for completion. @@ -432,7 +437,7 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout) defer cancel() - _, err = watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) { + _, err = watchtools.ListWatchUntil(ctx, lw, func(e watch.Event) (bool, error) { // Make sure the incoming object is versioned as we use unstructured // objects when we build manifests obj := convertWithMapper(e.Object, info.Mapping) diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index b770704c6..2a982d088 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -35,12 +35,12 @@ const goodChartDir = "rules/testdata/goodone" func TestBadChart(t *testing.T) { m := All(badChartDir, values, namespace, strict).Messages - if len(m) != 8 { + if len(m) != 7 { t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) } // There should be one INFO, 2 WARNINGs and one ERROR messages, check for them - var i, w, e, e2, e3, e4, e5, e6 bool + var i, w, e, e2, e3, e4, e5 bool for _, msg := range m { if msg.Severity == support.InfoSev { if strings.Contains(msg.Err.Error(), "icon is recommended") { @@ -59,24 +59,21 @@ func TestBadChart(t *testing.T) { if strings.Contains(msg.Err.Error(), "name is required") { e2 = true } - if strings.Contains(msg.Err.Error(), "directory name (badchartfile) and chart name () must be the same") { + + if strings.Contains(msg.Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { e3 = true } - if strings.Contains(msg.Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { + if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") { e4 = true } - if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") { - e5 = true - } - if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { - e6 = true + e5 = true } } } - if !e || !e2 || !e3 || !e4 || !e5 || !e6 || !w || !i { + if !e || !e2 || !e3 || !e4 || !e5 || !w || !i { t.Errorf("Didn't find all the expected errors, got %#v", m) } } diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go index 35bb81571..70cb83bc5 100644 --- a/pkg/lint/rules/chartfile.go +++ b/pkg/lint/rules/chartfile.go @@ -46,7 +46,6 @@ func Chartfile(linter *support.Linter) { } linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNameDirMatch(linter.ChartDir, chartFile)) // Chart metadata linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile)) @@ -82,13 +81,6 @@ func validateChartName(cf *chart.Metadata) error { return nil } -func validateChartNameDirMatch(chartDir string, cf *chart.Metadata) error { - if cf.Name != filepath.Base(chartDir) { - return errors.Errorf("directory name (%s) and chart name (%s) must be the same", filepath.Base(chartDir), cf.Name) - } - return nil -} - func validateChartAPIVersion(cf *chart.Metadata) error { if cf.APIVersion == "" { return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"") diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go index ff2a63583..d032dd12f 100644 --- a/pkg/lint/rules/chartfile_test.go +++ b/pkg/lint/rules/chartfile_test.go @@ -30,18 +30,15 @@ import ( ) const ( - badChartDir = "testdata/badchartfile" - goodChartDir = "testdata/goodone" + badChartDir = "testdata/badchartfile" ) var ( badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") - goodChartFilePath = filepath.Join(goodChartDir, "Chart.yaml") nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") ) var badChart, _ = chartutil.LoadChartfile(badChartFilePath) -var goodChart, _ = chartutil.LoadChartfile(goodChartFilePath) // Validation functions Test func TestValidateChartYamlNotDirectory(t *testing.T) { @@ -73,24 +70,6 @@ func TestValidateChartName(t *testing.T) { } } -func TestValidateChartNameDirMatch(t *testing.T) { - err := validateChartNameDirMatch(goodChartDir, goodChart) - if err != nil { - t.Errorf("validateChartNameDirMatch to return no error, gor a linter error") - } - // It has not name - err = validateChartNameDirMatch(badChartDir, badChart) - if err == nil { - t.Errorf("validatechartnamedirmatch to return a linter error, got no error") - } - - // Wrong path - err = validateChartNameDirMatch(badChartDir, goodChart) - if err == nil { - t.Errorf("validatechartnamedirmatch to return a linter error, got no error") - } -} - func TestValidateChartVersion(t *testing.T) { var failTest = []struct { Version string @@ -209,36 +188,32 @@ func TestChartfile(t *testing.T) { Chartfile(&linter) msgs := linter.Messages - if len(msgs) != 7 { - t.Errorf("Expected 7 errors, got %d", len(msgs)) + if len(msgs) != 6 { + t.Errorf("Expected 6 errors, got %d", len(msgs)) } if !strings.Contains(msgs[0].Err.Error(), "name is required") { t.Errorf("Unexpected message 0: %s", msgs[0].Err) } - if !strings.Contains(msgs[1].Err.Error(), "directory name (badchartfile) and chart name () must be the same") { + if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { t.Errorf("Unexpected message 1: %s", msgs[1].Err) } - if !strings.Contains(msgs[2].Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { + if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") { t.Errorf("Unexpected message 2: %s", msgs[2].Err) } - if !strings.Contains(msgs[3].Err.Error(), "version '0.0.0.0' is not a valid SemVer") { + if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") { t.Errorf("Unexpected message 3: %s", msgs[3].Err) } - if !strings.Contains(msgs[4].Err.Error(), "icon is recommended") { + if !strings.Contains(msgs[4].Err.Error(), "chart type is not valid in apiVersion") { t.Errorf("Unexpected message 4: %s", msgs[4].Err) } - if !strings.Contains(msgs[5].Err.Error(), "chart type is not valid in apiVersion") { + if !strings.Contains(msgs[5].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { t.Errorf("Unexpected message 5: %s", msgs[5].Err) } - if !strings.Contains(msgs[6].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { - t.Errorf("Unexpected message 6: %s", msgs[6].Err) - } - } diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 6032eb063..5d16779f1 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -169,7 +169,7 @@ func (s *Signatory) DecryptKey(fn PassphraseFetcher) error { if s.Entity == nil { return errors.New("private key not found") } else if s.Entity.PrivateKey == nil { - return errors.New("provided key is not a private key") + return errors.New("provided key is not a private key. Try providing a keyring with secret keys") } // Nothing else to do if key is not encrypted. @@ -203,7 +203,7 @@ func (s *Signatory) ClearSign(chartpath string) (string, error) { if s.Entity == nil { return "", errors.New("private key not found") } else if s.Entity.PrivateKey == nil { - return "", errors.New("provided key is not a private key") + return "", errors.New("provided key is not a private key. Try providing a keyring with secret keys") } if fi, err := os.Stat(chartpath); err != nil { diff --git a/pkg/release/status.go b/pkg/release/status.go index cd025e279..0e535f9a4 100644 --- a/pkg/release/status.go +++ b/pkg/release/status.go @@ -19,6 +19,7 @@ package release type Status string // Describe the status of a release +// NOTE: Make sure to update cmd/helm/status.go when adding or modifying any of these statuses. const ( // StatusUnknown indicates that a release is in an uncertain state. StatusUnknown Status = "unknown" diff --git a/scripts/validate-license.sh b/scripts/validate-license.sh index 00bd38ea2..dc247436f 100755 --- a/scripts/validate-license.sh +++ b/scripts/validate-license.sh @@ -27,14 +27,16 @@ find_files() { \( -name '*.go' -o -name '*.sh' \) } -mapfile -t failed_license_header < <(find_files | xargs grep -L 'Licensed under the Apache License, Version 2.0 (the "License")') +# Use "|| :" to ignore the error code when grep returns empty +failed_license_header=($(find_files | xargs grep -L 'Licensed under the Apache License, Version 2.0 (the "License")' || :)) if (( ${#failed_license_header[@]} > 0 )); then echo "Some source files are missing license headers." printf '%s\n' "${failed_license_header[@]}" exit 1 fi -mapfile -t failed_copyright_header < <(find_files | xargs grep -L 'Copyright The Helm Authors.') +# Use "|| :" to ignore the error code when grep returns empty +failed_copyright_header=($(find_files | xargs grep -L 'Copyright The Helm Authors.' || :)) if (( ${#failed_copyright_header[@]} > 0 )); then echo "Some source files are missing the copyright header." printf '%s\n' "${failed_copyright_header[@]}"