Merge branch 'master' into fix/protectAgainstAliases

pull/6860/head
Marc Khouzam 6 years ago
commit c7ac4151e2

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# 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.
set -euo pipefail
curl -sSL https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz | tar xz
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

@ -9,9 +9,13 @@ jobs:
environment: environment:
GOCACHE: "/tmp/go/cache" GOCACHE: "/tmp/go/cache"
GOLANGCI_LINT_VERSION: "1.21.0"
steps: steps:
- checkout - checkout
- run:
name: install test dependencies
command: .circleci/bootstrap.sh
- run: - run:
name: test style name: test style
command: make test-style command: make test-style

@ -1,5 +1,5 @@
run: run:
deadline: 2m timeout: 2m
linters: linters:
disable-all: true disable-all: true

@ -1,13 +1,13 @@
BINDIR := $(CURDIR)/bin BINDIR := $(CURDIR)/bin
DIST_DIRS := find * -type d -exec DIST_DIRS := find * -type d -exec
TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le windows/amd64 TARGETS := darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le 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 BINNAME ?= helm
GOPATH = $(shell go env GOPATH) GOPATH = $(shell go env GOPATH)
DEP = $(GOPATH)/bin/dep DEP = $(GOPATH)/bin/dep
GOX = $(GOPATH)/bin/gox GOX = $(GOPATH)/bin/gox
GOIMPORTS = $(GOPATH)/bin/goimports GOIMPORTS = $(GOPATH)/bin/goimports
GOLANGCI_LINT = $(GOPATH)/bin/golangci-lint
ACCEPTANCE_DIR:=$(GOPATH)/src/helm.sh/acceptance-testing ACCEPTANCE_DIR:=$(GOPATH)/src/helm.sh/acceptance-testing
# To specify the subset of acceptance tests to run. '.' means all tests # To specify the subset of acceptance tests to run. '.' means all tests
@ -81,8 +81,8 @@ test-coverage:
@ ./scripts/coverage.sh @ ./scripts/coverage.sh
.PHONY: test-style .PHONY: test-style
test-style: $(GOLANGCI_LINT) test-style:
GO111MODULE=on $(GOLANGCI_LINT) run GO111MODULE=on golangci-lint run
@scripts/validate-license.sh @scripts/validate-license.sh
.PHONY: test-acceptance .PHONY: test-acceptance
@ -118,9 +118,6 @@ format: $(GOIMPORTS)
$(GOX): $(GOX):
(cd /; GO111MODULE=on go get -u github.com/mitchellh/gox) (cd /; GO111MODULE=on go get -u github.com/mitchellh/gox)
$(GOLANGCI_LINT):
(cd /; GO111MODULE=on go get -u github.com/golangci/golangci-lint/cmd/golangci-lint)
$(GOIMPORTS): $(GOIMPORTS):
(cd /; GO111MODULE=on go get -u golang.org/x/tools/cmd/goimports) (cd /; GO111MODULE=on go get -u golang.org/x/tools/cmd/goimports)
@ -142,6 +139,20 @@ dist:
$(DIST_DIRS) zip -r helm-${VERSION}-{}.zip {} \; \ $(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 .PHONY: checksum
checksum: checksum:
for f in _dist/*.{gz,zip} ; do \ for f in _dist/*.{gz,zip} ; do \

@ -4,6 +4,7 @@ maintainers:
- fibonacci1729 - fibonacci1729
- hickeyma - hickeyma
- jdolitsky - jdolitsky
- marckhouzam
- mattfarina - mattfarina
- michelleN - michelleN
- prydonius - prydonius

@ -2,7 +2,7 @@
[![CircleCI](https://circleci.com/gh/helm/helm.svg?style=shield)](https://circleci.com/gh/helm/helm) [![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) [![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) [![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. 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: 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`. - [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install helm`. - [Scoop](https://scoop.sh/) users can use `scoop install helm`.
- [GoFish](https://gofi.sh/) users can use `gofish 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). 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. including installing pre-releases.
## Docs ## 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 ## Roadmap

@ -91,7 +91,7 @@ func (o *createOptions) run(out io.Writer) error {
if o.starter != "" { if o.starter != "" {
// Create from the starter // Create from the starter
lstarter := filepath.Join(o.starterDir, o.starter) lstarter := filepath.Join(o.starterDir, o.starter)
// If path is absolute, we dont want to prefix it with helm starters folder // If path is absolute, we don't want to prefix it with helm starters folder
if filepath.IsAbs(o.starter) { if filepath.IsAbs(o.starter) {
lstarter = o.starter lstarter = o.starter
} }

@ -182,7 +182,7 @@ func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) {
func createTestingMetadata(name, baseURL string) *chart.Chart { func createTestingMetadata(name, baseURL string) *chart.Chart {
return &chart.Chart{ return &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV1, APIVersion: chart.APIVersionV2,
Name: name, Name: name,
Version: "1.2.3", Version: "1.2.3",
Dependencies: []*chart.Dependency{ Dependencies: []*chart.Dependency{

@ -19,6 +19,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"sort"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
@ -55,8 +56,18 @@ type envOptions struct {
} }
func (o *envOptions) run(out io.Writer) error { func (o *envOptions) run(out io.Writer) error {
for k, v := range o.settings.EnvVars() { envVars := o.settings.EnvVars()
fmt.Printf("%s=\"%s\"\n", k, v)
// 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 return nil
} }

@ -17,6 +17,7 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"time" "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) { func addInstallFlags(f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install") 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.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.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.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.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.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.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.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") 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 return nil, err
} }
if chartRequested.Metadata.Deprecated {
fmt.Fprintln(out, "WARNING: This chart is deprecated")
}
if req := chartRequested.Metadata.Dependencies; req != nil { if req := chartRequested.Metadata.Dependencies; req != nil {
// If CheckDependencies returns an error, we have unfulfilled dependencies. // If CheckDependencies returns an error, we have unfulfilled dependencies.
// As of Helm 2.4.0, this is treated as a stopping condition: // As of Helm 2.4.0, this is treated as a stopping condition:

@ -183,6 +183,12 @@ func TestInstall(t *testing.T) {
wantError: true, wantError: true,
golden: "output/subchart-schema-cli-negative.txt", 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) runTestActionCmd(t, tests)

@ -77,12 +77,15 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.SetStateMask() client.SetStateMask()
results, err := client.Run() results, err := client.Run()
if err != nil {
return err
}
if client.Short { if client.Short {
for _, res := range results { for _, res := range results {
fmt.Fprintln(out, res.Name) fmt.Fprintln(out, res.Name)
} }
return err return nil
} }
return outfmt.Write(out, newReleaseListWriter(results)) 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.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")
f.BoolVarP(&client.All, "all", "a", false, "show all releases, not just the ones marked deployed or failed") 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.Superseded, "superseded", false, "show superseded releases")
f.BoolVar(&client.Uninstalling, "uninstalling", false, "show releases that are currently being uninstalled") 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") f.BoolVar(&client.Deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
@ -110,13 +113,13 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
type releaseElement struct { type releaseElement struct {
Name string Name string `json:"name"`
Namespace string Namespace string `json:"namespace"`
Revision string Revision string `json:"revision"`
Updated string Updated string `json:"updated"`
Status string Status string `json:"status"`
Chart string Chart string `json:"chart"`
AppVersion string AppVersion string `json:"app_version"`
} }
type releaseListWriter struct { type releaseListWriter struct {

@ -127,7 +127,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
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", "--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 { knownArg := func(a string) bool {
for _, pre := range kvargs { for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") { if strings.HasPrefix(a, pre+"=") {
@ -136,13 +136,26 @@ func manuallyProcessArgs(args []string) ([]string, []string) {
} }
return false return false
} }
isKnown := func(v string) string {
for _, i := range kvargs {
if i == v {
return v
}
}
return ""
}
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
switch a := args[i]; a { switch a := args[i]; a {
case "--debug": case "--debug":
known = append(known, a) known = append(known, a)
case "--kube-context", "--namespace", "-n", "--kubeconfig", "--registry-config", "--repository-cache", "--repository-config": case isKnown(a):
known = append(known, a, args[i+1]) known = append(known, a)
i++ i++
if i < len(args) {
known = append(known, args[i])
}
default: default:
if knownArg(a) { if knownArg(a) {
known = append(known, a) known = append(known, a)

@ -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 is given, this will look at that path for a chart (which must contain a
Chart.yaml file) and then package that directory. 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. 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 { func newPackageCmd(out io.Writer) *cobra.Command {

@ -30,14 +30,27 @@ func TestManuallyProcessArgs(t *testing.T) {
"--debug", "--debug",
"--foo", "bar", "--foo", "bar",
"--kubeconfig=/home/foo", "--kubeconfig=/home/foo",
"--kubeconfig", "/home/foo",
"--kube-context=test1",
"--kube-context", "test1", "--kube-context", "test1",
"-n=test2",
"-n", "test2", "-n", "test2",
"--namespace=test2",
"--namespace", "test2",
"--home=/tmp", "--home=/tmp",
"command", "command",
} }
expectKnown := []string{ 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{ expectUnknown := []string{

@ -29,7 +29,7 @@ import (
"github.com/gofrs/flock" "github.com/gofrs/flock"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v2" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/getter"

@ -51,8 +51,8 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
} }
type repositoryElement struct { type repositoryElement struct {
Name string Name string `json:"name"`
URL string URL string `json:"url"`
} }
type repoListWriter struct { type repoListWriter struct {

@ -175,7 +175,7 @@ __helm_list_charts()
# 1- There are other completions found (if there are no completions, # 1- There are other completions found (if there are no completions,
# the shell will do file completion itself) # the shell will do file completion itself)
# 2- If there is some input from the user (or else we will end up # 2- If there is some input from the user (or else we will end up
# lising the entire content of the current directory which will # listing the entire content of the current directory which will
# be too many choices for the user to find the real repos) # be too many choices for the user to find the real repos)
if [ $wantFiles -eq 1 ] && [ -n "${out[*]}" ] && [ -n "${cur}" ]; then if [ $wantFiles -eq 1 ] && [ -n "${out[*]}" ] && [ -n "${cur}" ]; then
for file in $(\ls); do for file in $(\ls); do

@ -51,7 +51,7 @@ type Index struct {
const sep = "\v" const sep = "\v"
// NewIndex creats a new Index. // NewIndex creates a new Index.
func NewIndex() *Index { func NewIndex() *Index {
return &Index{lines: map[string]string{}, charts: map[string]*repo.ChartVersion{}} return &Index{lines: map[string]string{}, charts: map[string]*repo.ChartVersion{}}
} }

@ -84,10 +84,10 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
} }
type hubChartElement struct { type hubChartElement struct {
URL string URL string `json:"url"`
Version string Version string `json:"version"`
AppVersion string AppVersion string `json:"app_version"`
Description string Description string `json:"description"`
} }
type hubSearchWriter struct { type hubSearchWriter struct {

@ -191,10 +191,10 @@ func (o *searchRepoOptions) buildIndex(out io.Writer) (*search.Index, error) {
} }
type repoChartElement struct { type repoChartElement struct {
Name string Name string `json:"name"`
Version string Version string `json:"version"`
AppVersion string AppVersion string `json:"app_version"`
Description string Description string `json:"description"`
} }
type repoSearchWriter struct { type repoSearchWriter struct {

@ -31,12 +31,13 @@ import (
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
) )
// NOTE: Keep the list of statuses up-to-date with pkg/release/status.go.
var statusHelp = ` var statusHelp = `
This command shows the status of a named release. This command shows the status of a named release.
The status consists of: The status consists of:
- last deployment time - last deployment time
- k8s namespace in which the release lives - 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 - list of resources that this release consists of, sorted by kind
- details on last test suite run, if applicable - details on last test suite run, if applicable
- additional notes provided by the chart - additional notes provided by the chart

@ -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 { func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var validate bool var validate bool
var includeCrds bool
client := action.NewInstall(cfg) client := action.NewInstall(cfg)
valueOpts := &values.Options{} valueOpts := &values.Options{}
var extraAPIs []string var extraAPIs []string
@ -67,7 +68,14 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var manifests bytes.Buffer 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)) fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
if !client.DisableHooks { if !client.DisableHooks {
for _, m := range rel.Hooks { for _, m := range rel.Hooks {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
@ -119,7 +127,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
addInstallFlags(f, client, valueOpts) addInstallFlags(f, client, valueOpts)
f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates") 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.StringVar(&client.OutputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout")
f.BoolVar(&validate, "validate", false, "establish a connection to Kubernetes for schema validation") 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") f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
return cmd return cmd

@ -79,6 +79,11 @@ func TestTemplateCmd(t *testing.T) {
cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test '%s'", chartPath), cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test '%s'", chartPath),
golden: "output/template-with-api-version.txt", 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) runTestCmd(t, tests)
} }

@ -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

@ -1 +1 @@
[{"Name":"testing/mariadb","Version":"0.3.0","AppVersion":"","Description":"Chart for MariaDB"}] [{"name":"testing/mariadb","version":"0.3.0","app_version":"","description":"Chart for MariaDB"}]

@ -1,4 +1,4 @@
- AppVersion: 2.3.4 - app_version: 2.3.4
Description: Deploy a basic Alpine Linux pod description: Deploy a basic Alpine Linux pod
Name: testing/alpine name: testing/alpine
Version: 0.2.0 version: 0.2.0

@ -1,2 +0,0 @@
NAME CHART VERSION APP VERSION DESCRIPTION
testing/mariadb 0.3.0 Chart for MariaDB

@ -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

@ -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

@ -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

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

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

@ -1 +1 @@
v3.0+unreleased v3.0

@ -1 +1 @@
Version: v3.0+unreleased Version: v3.0

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

@ -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

@ -0,0 +1,3 @@
#Deprecated
This space intentionally left blank.

@ -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.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.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.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 return cmd
} }

@ -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) rel, err := client.Run(args[0], ch, vals)
if err != nil { if err != nil {
return errors.Wrap(err, "UPGRADE FAILED") 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.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.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.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) addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)

@ -61,6 +61,8 @@ func newVersionCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&o.short, "short", false, "print the version number") f.BoolVar(&o.short, "short", false, "print the version number")
f.StringVar(&o.template, "template", "", "template for version string format") f.StringVar(&o.template, "template", "", "template for version string format")
f.BoolP("client", "c", true, "display client version information")
f.MarkHidden("client")
return cmd return cmd
} }

@ -32,6 +32,14 @@ func TestVersion(t *testing.T) {
name: "template", name: "template",
cmd: "version --template='Version: {{.Version}}'", cmd: "version --template='Version: {{.Version}}'",
golden: "output/version-template.txt", golden: "output/version-template.txt",
}, {
name: "client",
cmd: "version --client",
golden: "output/version-client.txt",
}, {
name: "client shorthand",
cmd: "version -c",
golden: "output/version-client-shorthand.txt",
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)
} }

@ -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).

@ -27,18 +27,14 @@ import (
// Options represents configurable options used to create client and server TLS configurations. // Options represents configurable options used to create client and server TLS configurations.
type Options struct { type Options struct {
CaCertFile string CaCertFile string
// If either the KeyFile or CertFile is empty, ClientConfig() will not load them, // 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.
KeyFile string KeyFile string
CertFile string CertFile string
// Client-only options // Client-only options
InsecureSkipVerify bool 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) { func ClientConfig(opts Options) (cfg *tls.Config, err error) {
var cert *tls.Certificate var cert *tls.Certificate
var pool *x509.CertPool 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} cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
return cfg, nil 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
}

@ -26,13 +26,16 @@ import (
// NewClientTLS returns tls.Config appropriate for client auth. // NewClientTLS returns tls.Config appropriate for client auth.
func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) { func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) {
config := tls.Config{}
if certFile != "" && keyFile != "" {
cert, err := CertFromFilePair(certFile, keyFile) cert, err := CertFromFilePair(certFile, keyFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
config := tls.Config{ config.Certificates = []tls.Certificate{*cert}
Certificates: []tls.Certificate{*cert},
} }
if caFile != "" { if caFile != "" {
cp, err := CertPoolFromFile(caFile) cp, err := CertPoolFromFile(caFile)
if err != nil { if err != nil {
@ -40,6 +43,7 @@ func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) {
} }
config.RootCAs = cp config.RootCAs = cp
} }
return &config, nil return &config, nil
} }

@ -17,7 +17,6 @@ limitations under the License.
package tlsutil package tlsutil
import ( import (
"crypto/tls"
"path/filepath" "path/filepath"
"testing" "testing"
) )
@ -25,7 +24,7 @@ import (
const tlsTestDir = "../../testdata" const tlsTestDir = "../../testdata"
const ( const (
testCaCertFile = "ca.pem" testCaCertFile = "rootca.crt"
testCertFile = "crt.pem" testCertFile = "crt.pem"
testKeyFile = "key.pem" testKeyFile = "key.pem"
) )
@ -54,30 +53,61 @@ func TestClientConfig(t *testing.T) {
} }
} }
func TestServerConfig(t *testing.T) { func testfile(t *testing.T, file string) (path string) {
opts := Options{ var err error
CaCertFile: testfile(t, testCaCertFile), if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
CertFile: testfile(t, testCertFile), t.Fatalf("error getting absolute path to test file %q: %v", file, err)
KeyFile: testfile(t, testKeyFile),
ClientAuth: tls.RequireAndVerifyClientCert,
} }
return path
}
func TestNewClientTLS(t *testing.T) {
certFile := testfile(t, testCertFile)
keyFile := testfile(t, testKeyFile)
caCertFile := testfile(t, testCaCertFile)
cfg, err := ServerConfig(opts) cfg, err := NewClientTLS(certFile, keyFile, caCertFile)
if err != nil { if err != nil {
t.Fatalf("error building tls server config: %v", err) t.Error(err)
} }
if got := cfg.MinVersion; got != tls.VersionTLS12 {
t.Errorf("expecting TLS version 1.2, got %d", got) if got := len(cfg.Certificates); got != 1 {
t.Fatalf("expecting 1 client certificates, got %d", got)
} }
if got := cfg.ClientCAs; got == nil { if cfg.InsecureSkipVerify {
t.Errorf("expecting non-nil CA pool") t.Fatalf("insecure skip verify mistmatch, expecting false")
}
if cfg.RootCAs == nil {
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
} }
}
func testfile(t *testing.T, file string) (path string) { cfg, err = NewClientTLS("", "", caCertFile)
var err error if err != nil {
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil { t.Error(err)
t.Fatalf("error getting absolute path to test file %q: %v", file, err) }
if got := len(cfg.Certificates); got != 0 {
t.Fatalf("expecting 0 client certificates, got %d", got)
}
if cfg.InsecureSkipVerify {
t.Fatalf("insecure skip verify mistmatch, expecting false")
}
if cfg.RootCAs == nil {
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
}
cfg, err = NewClientTLS(certFile, keyFile, "")
if err != nil {
t.Error(err)
}
if got := len(cfg.Certificates); got != 1 {
t.Fatalf("expecting 1 client certificates, got %d", got)
}
if cfg.InsecureSkipVerify {
t.Fatalf("insecure skip verify mistmatch, expecting false")
}
if cfg.RootCAs != nil {
t.Fatalf("mismatch tls RootCAs, expecting nil")
} }
return path
} }

@ -33,7 +33,7 @@ var (
version = "v3.0" version = "v3.0"
// metadata is extra build time data // metadata is extra build time data
metadata = "unreleased" metadata = ""
// gitCommit is the git sha1 // gitCommit is the git sha1
gitCommit = "" gitCommit = ""
// gitTreeState is the state of the git tree // gitTreeState is the state of the git tree

@ -109,10 +109,20 @@ func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not get server version from Kubernetes") return nil, errors.Wrap(err, "could not get server version from Kubernetes")
} }
// Issue #6361:
// Client-Go emits an error when an API service is registered but unimplemented.
// We trap that error here and print a warning. But since the discovery client continues
// building the API object, it is correctly populated with all valid APIs.
// See https://github.com/kubernetes/kubernetes/issues/72051#issuecomment-521157642
apiVersions, err := GetVersionSet(dc) apiVersions, err := GetVersionSet(dc)
if err != nil { if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) {
c.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err)
c.Log("WARNING: To fix this, kubectl delete apiservice <service-name>")
} else {
return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
} }
}
c.Capabilities = &chartutil.Capabilities{ c.Capabilities = &chartutil.Capabilities{
APIVersions: apiVersions, APIVersions: apiVersions,

@ -39,6 +39,10 @@ func NewGetValues(cfg *Configuration) *GetValues {
// Run executes 'helm get values' against the given release. // Run executes 'helm get values' against the given release.
func (g *GetValues) Run(name string) (map[string]interface{}, error) { 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) rel, err := g.cfg.releaseContent(name, g.Version)
if err != nil { if err != nil {
return nil, err return nil, err

@ -79,6 +79,7 @@ type Install struct {
ReleaseName string ReleaseName string
GenerateName bool GenerateName bool
NameTemplate string NameTemplate string
Description string
OutputDir string OutputDir string
Atomic bool Atomic bool
SkipCRDs bool SkipCRDs bool
@ -86,6 +87,8 @@ type Install struct {
// APIVersions allows a manual set of supported API Versions to be passed // APIVersions allows a manual set of supported API Versions to be passed
// (for things like templating). These are ignored if ClientOnly is false // (for things like templating). These are ignored if ClientOnly is false
APIVersions chartutil.VersionSet 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 // ChartPathOptions captures common options used for controlling chart paths
@ -126,7 +129,7 @@ func (i *Install) installCRDs(crds []*chart.File) error {
i.cfg.Log("CRD %s is already present. Skipping.", crdName) i.cfg.Log("CRD %s is already present. Skipping.", crdName)
continue continue
} }
return errors.Wrapf(err, "failed to instal CRD %s", obj.Name) return errors.Wrapf(err, "failed to install CRD %s", obj.Name)
} }
totalItems = append(totalItems, res...) totalItems = append(totalItems, res...)
} }
@ -197,11 +200,14 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
return nil, err return nil, err
} }
//special case for helm template --is-upgrade
isUpgrade := i.IsUpgrade && i.DryRun
options := chartutil.ReleaseOptions{ options := chartutil.ReleaseOptions{
Name: i.ReleaseName, Name: i.ReleaseName,
Namespace: i.Namespace, Namespace: i.Namespace,
Revision: 1, Revision: 1,
IsInstall: true, IsInstall: !isUpgrade,
IsUpgrade: isUpgrade,
} }
valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps)
if err != nil { 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 // 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 // deleting the release because the manifest will be pointing at that
// resource // resource
if !i.ClientOnly { if !i.ClientOnly && !isUpgrade {
if err := existingResourceConflict(resources); err != nil { if err := existingResourceConflict(resources); err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") 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.
} }
} }
if len(i.Description) > 0 {
rel.SetStatus(release.StatusDeployed, i.Description)
} else {
rel.SetStatus(release.StatusDeployed, "Install complete") rel.SetStatus(release.StatusDeployed, "Install complete")
}
// This is a tricky case. The release has been created, but the result // 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 // 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 // One possible strategy would be to do a timed retry to see if we can get
// this stored in the future. // 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 return rel, nil
} }
@ -420,7 +432,7 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values
if ch.Metadata.KubeVersion != "" { if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { 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 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 { if len(args) == 2 {
return args[0], args[1], flagsNotSet() return args[0], args[1], flagsNotSet()
} }

@ -306,7 +306,7 @@ func TestInstallRelease_KubeVersion(t *testing.T) {
vals = map[string]interface{}{} vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
is.Error(err) is.Error(err)
is.Contains(err.Error(), "chart requires kubernetesVersion") is.Contains(err.Error(), "chart requires kubeVersion")
} }
func TestInstallRelease_Wait(t *testing.T) { func TestInstallRelease_Wait(t *testing.T) {
@ -505,6 +505,14 @@ func TestNameAndChart(t *testing.T) {
t.Fatal("expected an error") t.Fatal("expected an error")
} }
is.Equal("must either provide a name or specify --generate-name", err.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) { func TestNameAndChartGenerateName(t *testing.T) {

@ -237,27 +237,36 @@ func makeMeSomeReleases(store *storage.Storage, t *testing.T) {
} }
func TestFilterList(t *testing.T) { func TestFilterList(t *testing.T) {
one := releaseStub() t.Run("should filter old versions of the same release", func(t *testing.T) {
one.Name = "one" r1 := releaseStub()
one.Namespace = "default" r1.Name = "r"
one.Version = 1 r1.Version = 1
two := releaseStub() r2 := releaseStub()
two.Name = "two" r2.Name = "r"
two.Namespace = "default" r2.Version = 2
two.Version = 1 another := releaseStub()
anotherOldOne := releaseStub() another.Name = "another"
anotherOldOne.Name = "one" another.Version = 1
anotherOldOne.Namespace = "testing"
anotherOldOne.Version = 1 filteredList := filterList([]*release.Release{r1, r2, another})
anotherOne := releaseStub() expectedFilteredList := []*release.Release{r2, another}
anotherOne.Name = "one"
anotherOne.Namespace = "testing" assert.ElementsMatch(t, expectedFilteredList, filteredList)
anotherOne.Version = 2 })
list := []*release.Release{one, two, anotherOne} t.Run("should not filter out any version across namespaces", func(t *testing.T) {
expectedFilteredList := []*release.Release{one, two, anotherOne} r1 := releaseStub()
r1.Name = "r"
r1.Namespace = "default"
r1.Version = 1
r2 := releaseStub()
r2.Name = "r"
r2.Namespace = "testing"
r2.Version = 2
filteredList := filterList(list) filteredList := filterList([]*release.Release{r1, r2})
expectedFilteredList := []*release.Release{r1, r2}
assert.ElementsMatch(t, expectedFilteredList, filteredList) assert.ElementsMatch(t, expectedFilteredList, filteredList)
})
} }

@ -63,6 +63,7 @@ func (p *Pull) Run(chartRef string) (string, error) {
Getters: getter.All(p.Settings), Getters: getter.All(p.Settings),
Options: []getter.Option{ Options: []getter.Option{
getter.WithBasicAuth(p.Username, p.Password), getter.WithBasicAuth(p.Username, p.Password),
getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
}, },
RepositoryConfig: p.Settings.RepositoryConfig, RepositoryConfig: p.Settings.RepositoryConfig,
RepositoryCache: p.Settings.RepositoryCache, RepositoryCache: p.Settings.RepositoryCache,

@ -24,6 +24,7 @@ import (
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
) )
type ShowOutputFormat string type ShowOutputFormat string
@ -76,11 +77,11 @@ func (s *Show) Run(chartpath string) (string, error) {
if s.OutputFormat == ShowAll { if s.OutputFormat == ShowAll {
fmt.Fprintln(&out, "---") fmt.Fprintln(&out, "---")
} }
b, err := yaml.Marshal(chrt.Values) for _, f := range chrt.Raw {
if err != nil { if f.Name == chartutil.ValuesfileName {
return "", err fmt.Fprintln(&out, string(f.Data))
}
} }
fmt.Fprintf(&out, "%s\n", b)
} }
if s.OutputFormat == ShowReadme || s.OutputFormat == ShowAll { if s.OutputFormat == ShowReadme || s.OutputFormat == ShowAll {

@ -37,6 +37,7 @@ type Uninstall struct {
DryRun bool DryRun bool
KeepHistory bool KeepHistory bool
Timeout time.Duration Timeout time.Duration
Description string
} }
// NewUninstall creates a new Uninstall object with the given configuration. // 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.Status = release.StatusUninstalled
if len(u.Description) > 0 {
rel.Info.Description = u.Description
} else {
rel.Info.Description = "Uninstallation complete" rel.Info.Description = "Uninstallation complete"
}
if !u.KeepHistory { if !u.KeepHistory {
u.cfg.Log("purge requested for %s", name) u.cfg.Log("purge requested for %s", name)

@ -58,6 +58,7 @@ type Upgrade struct {
Atomic bool Atomic bool
CleanupOnFail bool CleanupOnFail bool
SubNotes bool SubNotes bool
Description string
} }
// NewUpgrade creates a new Upgrade object with the given configuration. // 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 { if u.DryRun {
u.cfg.Log("dry run for %s", upgradedRelease.Name) u.cfg.Log("dry run for %s", upgradedRelease.Name)
if len(u.Description) > 0 {
upgradedRelease.Info.Description = u.Description
} else {
upgradedRelease.Info.Description = "Dry run complete" upgradedRelease.Info.Description = "Dry run complete"
}
return upgradedRelease, nil return upgradedRelease, nil
} }
@ -270,7 +275,11 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
u.cfg.recordRelease(originalRelease) u.cfg.recordRelease(originalRelease)
upgradedRelease.Info.Status = release.StatusDeployed upgradedRelease.Info.Status = release.StatusDeployed
if len(u.Description) > 0 {
upgradedRelease.Info.Description = u.Description
} else {
upgradedRelease.Info.Description = "Upgrade complete" upgradedRelease.Info.Description = "Upgrade complete"
}
return upgradedRelease, nil return upgradedRelease, nil
} }
@ -415,5 +424,5 @@ func recreate(cfg *Configuration, resources kube.ResourceList) error {
func objectKey(r *resource.Info) string { func objectKey(r *resource.Info) string {
gvk := r.Object.GetObjectKind().GroupVersionKind() gvk := r.Object.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Name) return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name)
} }

@ -26,19 +26,24 @@ const APIVersionV2 = "v2"
// Chart is a helm package that contains metadata, a default config, zero or more // Chart is a helm package that contains metadata, a default config, zero or more
// optionally parameterizable templates, and zero or more charts (dependencies). // optionally parameterizable templates, and zero or more charts (dependencies).
type Chart struct { type Chart struct {
// Raw contains the raw contents of the files originally contained in the chart archive.
//
// This should not be used except in special cases like `helm show values`,
// where we want to display the raw values, comments and all.
Raw []*File `json:"-"`
// Metadata is the contents of the Chartfile. // Metadata is the contents of the Chartfile.
Metadata *Metadata Metadata *Metadata `json:"metadata"`
// LocK is the contents of Chart.lock. // LocK is the contents of Chart.lock.
Lock *Lock Lock *Lock `json:"lock"`
// Templates for this chart. // Templates for this chart.
Templates []*File Templates []*File `json:"templates"`
// Values are default config for this template. // Values are default config for this chart.
Values map[string]interface{} Values map[string]interface{} `json:"values"`
// Schema is an optional JSON schema for imposing structure on Values // Schema is an optional JSON schema for imposing structure on Values
Schema []byte Schema []byte `json:"schema"`
// Files are miscellaneous files in a chart archive, // Files are miscellaneous files in a chart archive,
// e.g. README, LICENSE, etc. // e.g. README, LICENSE, etc.
Files []*File Files []*File `json:"files"`
parent *Chart parent *Chart
dependencies []*Chart dependencies []*Chart

@ -16,6 +16,7 @@ limitations under the License.
package chart package chart
import ( import (
"encoding/json"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -49,3 +50,27 @@ func TestCRDs(t *testing.T) {
is.Equal("crds/foo.yaml", crds[0].Name) is.Equal("crds/foo.yaml", crds[0].Name)
is.Equal("crds/foo/bar/baz.yaml", crds[1].Name) is.Equal("crds/foo/bar/baz.yaml", crds[1].Name)
} }
func TestSaveChartNoRawData(t *testing.T) {
chrt := Chart{
Raw: []*File{
{
Name: "fhqwhgads.yaml",
Data: []byte("Everybody to the Limit"),
},
},
}
is := assert.New(t)
data, err := json.Marshal(chrt)
if err != nil {
t.Fatal(err)
}
res := &Chart{}
if err := json.Unmarshal(data, res); err != nil {
t.Fatal(err)
}
is.Equal([]*File(nil), res.Raw)
}

@ -21,7 +21,7 @@ package chart
// base directory. // base directory.
type File struct { type File struct {
// Name is the path-like name of the template. // Name is the path-like name of the template.
Name string Name string `json:"name"`
// Data is the template as byte data. // Data is the template as byte data.
Data []byte Data []byte `json:"data"`
} }

@ -126,6 +126,12 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
continue 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 // Archive could contain \ if generated on Windows
delimiter := "/" delimiter := "/"
if strings.ContainsRune(hd.Name, '\\') { if strings.ContainsRune(hd.Name, '\\') {

@ -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)
})
}
}

@ -74,6 +74,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
subcharts := make(map[string][]*BufferedFile) subcharts := make(map[string][]*BufferedFile)
for _, f := range files { for _, f := range files {
c.Raw = append(c.Raw, &chart.File{Name: f.Name, Data: f.Data})
switch { switch {
case f.Name == "Chart.yaml": case f.Name == "Chart.yaml":
if c.Metadata == nil { if c.Metadata == nil {
@ -113,12 +114,18 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil {
return c, errors.Wrap(err, "cannot load requirements.yaml") 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. // Deprecated: requirements.lock is deprecated use Chart.lock.
case f.Name == "requirements.lock": case f.Name == "requirements.lock":
c.Lock = new(chart.Lock) c.Lock = new(chart.Lock)
if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil {
return c, errors.Wrap(err, "cannot load requirements.lock") 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/"): case strings.HasPrefix(f.Name, "templates/"):
c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data}) c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data})

@ -179,6 +179,10 @@ icon: https://example.com/64x64.png
t.Error("Expected chart values to be populated with default values") t.Error("Expected chart values to be populated with default values")
} }
if len(c.Raw) != 5 {
t.Errorf("Expected %d files, got %d", 5, len(c.Raw))
}
if !bytes.Equal(c.Schema, []byte("type: Values")) { if !bytes.Equal(c.Schema, []byte("type: Values")) {
t.Error("Expected chart schema to be populated with default values") t.Error("Expected chart schema to be populated with default values")
} }

@ -42,7 +42,16 @@ func LoadChartfile(filename string) (*chart.Metadata, error) {
// //
// 'filename' should be the complete path and filename ('foo/Chart.yaml') // 'filename' should be the complete path and filename ('foo/Chart.yaml')
func SaveChartfile(filename string, cf *chart.Metadata) error { 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) out, err := yaml.Marshal(cf)
if cf.APIVersion == chart.APIVersionV1 {
cf.Dependencies = savedDependencies
}
if err != nil { if err != nil {
return err return err
} }

@ -141,8 +141,17 @@ func Save(c *chart.Chart, outDir string) (string, error) {
func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
base := filepath.Join(prefix, c.Name()) 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 // Save Chart.yaml
cdata, err := yaml.Marshal(c.Metadata) cdata, err := yaml.Marshal(c.Metadata)
if c.Metadata.APIVersion == chart.APIVersionV1 {
c.Metadata.Dependencies = savedDependencies
}
if err != nil { if err != nil {
return err return err
} }

@ -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

@ -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)) 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. // Next, we need to load the index, and actually look up the chart.
idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name))
i, err := repo.LoadIndexFile(idxFile) i, err := repo.LoadIndexFile(idxFile)

@ -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) { func TestVerifyChart(t *testing.T) {
v, err := VerifyChart("testdata/signtest-0.1.0.tgz", "testdata/helm-test-key.pub") v, err := VerifyChart("testdata/signtest-0.1.0.tgz", "testdata/helm-test-key.pub")
if err != nil { if err != nil {

@ -79,7 +79,12 @@ func (m *Manager) Build() error {
return m.Update() return m.Update()
} }
// Check that all of the repos we're dependent on actually exist.
req := c.Metadata.Dependencies req := c.Metadata.Dependencies
if _, err := m.resolveRepoNames(req); err != nil {
return err
}
if sum, err := resolver.HashReq(req, lock.Dependencies); err != nil || sum != lock.Digest { if sum, err := resolver.HashReq(req, lock.Dependencies); err != nil || sum != lock.Digest {
return errors.New("Chart.lock is out of sync with Chart.yaml") return errors.New("Chart.lock is out of sync with Chart.yaml")
} }
@ -120,7 +125,7 @@ func (m *Manager) Update() error {
// Check that all of the repos we're dependent on actually exist and // Check that all of the repos we're dependent on actually exist and
// the repo index names. // the repo index names.
repoNames, err := m.getRepoNames(req) repoNames, err := m.resolveRepoNames(req)
if err != nil { if err != nil {
return err return err
} }
@ -144,6 +149,13 @@ func (m *Manager) Update() error {
return err 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. // If the lock file hasn't changed, don't write a new one.
oldLock := c.Lock oldLock := c.Lock
if oldLock != nil && oldLock.Digest == lock.Digest { if oldLock != nil && oldLock.Digest == lock.Digest {
@ -151,7 +163,7 @@ func (m *Manager) Update() error {
} }
// Finally, we need to write the lockfile. // 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) { func (m *Manager) loadChartDir() (*chart.Chart, error) {
@ -372,8 +384,9 @@ Loop:
return nil return nil
} }
// getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file. // resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file
func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, error) { // and replaces aliased repository URLs into resolved URLs in dependencies.
func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string, error) {
rf, err := loadRepoConfig(m.RepositoryConfig) rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -621,12 +634,16 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err
} }
// writeLock writes a lockfile to disk // 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) data, err := yaml.Marshal(lock)
if err != nil { if err != nil {
return err 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) return ioutil.WriteFile(dest, data, 0644)
} }

@ -161,7 +161,7 @@ func TestGetRepoNames(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
l, err := m.getRepoNames(tt.req) l, err := m.resolveRepoNames(tt.req)
if err != nil { if err != nil {
if tt.err { if tt.err {
continue continue
@ -181,7 +181,76 @@ func TestGetRepoNames(t *testing.T) {
} }
} }
// This function is the skeleton test code of failing tests for #6416 and bugs due to #5874. 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 // This function is used by below tests that ensures success of build operation
// with optional fields, alias, condition, tags, and even with ranged version. // with optional fields, alias, condition, tags, and even with ranged version.
// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default. // Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default.
@ -216,7 +285,7 @@ func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Depe
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Name: chartName, Name: chartName,
Version: "0.1.0", Version: "0.1.0",
APIVersion: "v1", APIVersion: "v2",
Dependencies: []*chart.Dependency{&dep}, Dependencies: []*chart.Dependency{&dep},
}, },
} }
@ -283,3 +352,11 @@ func TestBuild_WithTags(t *testing.T) {
Tags: []string{"tag1", "tag2"}, Tags: []string{"tag1", "tag2"},
}) })
} }
// Failing test for #6871
func TestBuild_WithRepositoryAlias(t *testing.T) {
// Dependency repository is aliased in Chart.yaml
checkBuildWithOptionalFields(t, "with-repository-alias", chart.Dependency{
Repository: "@test",
})
}

@ -16,3 +16,8 @@ repositories:
url: "http://example.com/helm" url: "http://example.com/helm"
- name: testing-relative-trailing-slash - name: testing-relative-trailing-slash
url: "http://example.com/helm/" url: "http://example.com/helm/"
- name: testing-ca-file
url: "https://example.com"
certFile: "cert"
keyFile: "key"
caFile: "ca"

@ -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

@ -93,11 +93,19 @@ func warnWrap(warn string) string {
// initFunMap creates the Engine's FuncMap and adds context-specific functions. // initFunMap creates the Engine's FuncMap and adds context-specific functions.
func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) {
funcMap := funcMap() funcMap := funcMap()
includedNames := make([]string, 0)
// Add the 'include' function here so we can close over t. // Add the 'include' function here so we can close over t.
funcMap["include"] = func(name string, data interface{}) (string, error) { funcMap["include"] = func(name string, data interface{}) (string, error) {
var buf strings.Builder 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) err := t.ExecuteTemplate(&buf, name, data)
includedNames = includedNames[:len(includedNames)-1]
return buf.String(), err return buf.String(), err
} }

@ -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{ v := chartutil.Values{
"Values": "", "Values": "",
"Chart": c.Metadata, "Chart": c.Metadata,
@ -477,6 +486,12 @@ func TestAlterFuncMap_include(t *testing.T) {
if got := out["conrad/templates/quote"]; got != expect { if got := out["conrad/templates/quote"]; got != expect {
t.Errorf("Expected %q, got %q (%v)", expect, got, out) 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) { func TestAlterFuncMap_require(t *testing.T) {

@ -29,7 +29,6 @@ import (
// HTTPGetter is the efault HTTP(/S) backend handler // HTTPGetter is the efault HTTP(/S) backend handler
type HTTPGetter struct { type HTTPGetter struct {
client *http.Client
opts options opts options
} }
@ -60,7 +59,12 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) {
req.SetBasicAuth(g.opts.username, g.opts.password) req.SetBasicAuth(g.opts.username, g.opts.password)
} }
resp, err := g.client.Do(req) client, err := g.httpClient()
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return buf, err return buf, err
} }
@ -81,28 +85,31 @@ func NewHTTPGetter(options ...Option) (Getter, error) {
opt(&client.opts) opt(&client.opts)
} }
if client.opts.certFile != "" && client.opts.keyFile != "" { return &client, nil
tlsConf, err := tlsutil.NewClientTLS(client.opts.certFile, client.opts.keyFile, client.opts.caFile) }
func (g *HTTPGetter) httpClient() (*http.Client, error) {
if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" {
tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile)
if err != nil { if err != nil {
return &client, errors.Wrap(err, "can't create TLS config for client") return nil, errors.Wrap(err, "can't create TLS config for client")
} }
tlsConf.BuildNameToCertificate() tlsConf.BuildNameToCertificate()
sni, err := urlutil.ExtractHostname(client.opts.url) sni, err := urlutil.ExtractHostname(g.opts.url)
if err != nil { if err != nil {
return &client, err return nil, err
} }
tlsConf.ServerName = sni tlsConf.ServerName = sni
client.client = &http.Client{ client := &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: tlsConf, TLSClientConfig: tlsConf,
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
}, },
} }
} else {
client.client = http.DefaultClient
}
return &client, nil return client, nil
}
return http.DefaultClient, nil
} }

@ -24,7 +24,9 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/internal/test" "github.com/pkg/errors"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/internal/version" "helm.sh/helm/v3/internal/version"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
) )
@ -35,46 +37,25 @@ func TestHTTPGetter(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if hg, ok := g.(*HTTPGetter); !ok { if _, ok := g.(*HTTPGetter); !ok {
t.Fatal("Expected NewHTTPGetter to produce an *HTTPGetter") t.Fatal("Expected NewHTTPGetter to produce an *HTTPGetter")
} else if hg.client != http.DefaultClient {
t.Fatal("Expected NewHTTPGetter to return a default HTTP client.")
} }
// Test with SSL:
cd := "../../testdata" cd := "../../testdata"
join := filepath.Join join := filepath.Join
ca, pub, priv := join(cd, "ca.pem"), join(cd, "crt.pem"), join(cd, "key.pem") ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem")
g, err = NewHTTPGetter(
WithURL("http://example.com"),
WithTLSClientConfig(pub, priv, ca),
)
if err != nil {
t.Fatal(err)
}
hg, ok := g.(*HTTPGetter)
if !ok {
t.Fatal("Expected NewHTTPGetter to produce an *HTTPGetter")
}
transport, ok := hg.client.Transport.(*http.Transport)
if !ok {
t.Errorf("Expected NewHTTPGetter to set up an HTTP transport")
}
test.AssertGoldenString(t, transport.TLSClientConfig.ServerName, "output/httpgetter-servername.txt") // Test with options
// Test other options
g, err = NewHTTPGetter( g, err = NewHTTPGetter(
WithBasicAuth("I", "Am"), WithBasicAuth("I", "Am"),
WithUserAgent("Groot"), WithUserAgent("Groot"),
WithTLSClientConfig(pub, priv, ca),
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
hg, ok = g.(*HTTPGetter) hg, ok := g.(*HTTPGetter)
if !ok { if !ok {
t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
} }
@ -90,6 +71,18 @@ func TestHTTPGetter(t *testing.T) {
if hg.opts.userAgent != "Groot" { if hg.opts.userAgent != "Groot" {
t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent) t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent)
} }
if hg.opts.certFile != pub {
t.Errorf("Expected NewHTTPGetter to contain %q as the public key file, got %q", pub, hg.opts.certFile)
}
if hg.opts.keyFile != priv {
t.Errorf("Expected NewHTTPGetter to contain %q as the private key file, got %q", priv, hg.opts.keyFile)
}
if hg.opts.caFile != ca {
t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile)
}
} }
func TestDownload(t *testing.T) { func TestDownload(t *testing.T) {
@ -149,3 +142,52 @@ func TestDownload(t *testing.T) {
t.Errorf("Expected %q, got %q", expect, got.String()) t.Errorf("Expected %q, got %q", expect, got.String())
} }
} }
func TestDownloadTLS(t *testing.T) {
cd := "../../testdata"
ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem")
tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca)
if err != nil {
t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
}
tlsConf.BuildNameToCertificate()
tlsConf.ServerName = "helm.sh"
tlsSrv.TLS = tlsConf
tlsSrv.StartTLS()
defer tlsSrv.Close()
u, _ := url.ParseRequestURI(tlsSrv.URL)
g, err := NewHTTPGetter(
WithURL(u.String()),
WithTLSClientConfig(pub, priv, ca),
)
if err != nil {
t.Fatal(err)
}
if _, err := g.Get(u.String()); err != nil {
t.Error(err)
}
// now test with TLS config being passed along in .Get (see #6635)
g, err = NewHTTPGetter()
if err != nil {
t.Fatal(err)
}
if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil {
t.Error(err)
}
// test with only the CA file (see also #6635)
g, err = NewHTTPGetter()
if err != nil {
t.Fatal(err)
}
if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)); err != nil {
t.Error(err)
}
}

@ -167,7 +167,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
} }
kind := info.Mapping.GroupVersionKind.Kind kind := info.Mapping.GroupVersionKind.Kind
c.Log("Created a new %s called %q\n", kind, info.Name) c.Log("Created a new %s called %q in %s\n", kind, info.Name, info.Namespace)
return nil return nil
} }

@ -81,5 +81,5 @@ func (r ResourceList) Intersect(rs ResourceList) ResourceList {
// isMatchingInfo returns true if infos match on Name and GroupVersionKind. // isMatchingInfo returns true if infos match on Name and GroupVersionKind.
func isMatchingInfo(a, b *resource.Info) bool { func isMatchingInfo(a, b *resource.Info) bool {
return a.Name == b.Name && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind
} }

@ -27,6 +27,7 @@ import (
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
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"
@ -114,6 +115,17 @@ func (w *waiter) waitForResources(created ResourceList) error {
if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil { if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil {
return false, err return false, err
} }
if !w.crdBetaReady(*crd) {
return false, nil
}
case *apiextv1.CustomResourceDefinition:
if err := v.Get(); err != nil {
return false, err
}
crd := &apiextv1.CustomResourceDefinition{}
if err := scheme.Scheme.Convert(v.Object, crd, nil); err != nil {
return false, err
}
if !w.crdReady(*crd) { if !w.crdReady(*crd) {
return false, nil return false, nil
} }
@ -229,7 +241,10 @@ func (w *waiter) daemonSetReady(ds *appsv1.DaemonSet) bool {
return true return true
} }
func (w *waiter) crdReady(crd apiextv1beta1.CustomResourceDefinition) bool { // Because the v1 extensions API is not available on all supported k8s versions
// yet and because Go doesn't support generics, we need to have a duplicate
// function to support the v1beta1 types
func (w *waiter) crdBetaReady(crd apiextv1beta1.CustomResourceDefinition) bool {
for _, cond := range crd.Status.Conditions { for _, cond := range crd.Status.Conditions {
switch cond.Type { switch cond.Type {
case apiextv1beta1.Established: case apiextv1beta1.Established:
@ -249,6 +264,26 @@ func (w *waiter) crdReady(crd apiextv1beta1.CustomResourceDefinition) bool {
return false return false
} }
func (w *waiter) crdReady(crd apiextv1.CustomResourceDefinition) bool {
for _, cond := range crd.Status.Conditions {
switch cond.Type {
case apiextv1.Established:
if cond.Status == apiextv1.ConditionTrue {
return true
}
case apiextv1.NamesAccepted:
if cond.Status == apiextv1.ConditionFalse {
// This indicates a naming conflict, but it's probably not the
// job of this function to fail because of that. Instead,
// we treat it as a success, since the process should be able to
// continue.
return true
}
}
}
return false
}
func (w *waiter) statefulSetReady(sts *appsv1.StatefulSet) bool { func (w *waiter) 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 {

@ -35,12 +35,12 @@ const goodChartDir = "rules/testdata/goodone"
func TestBadChart(t *testing.T) { func TestBadChart(t *testing.T) {
m := All(badChartDir, values, namespace, strict).Messages 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("Number of errors %v", len(m))
t.Errorf("All didn't fail with expected errors, got %#v", 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 // 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 { for _, msg := range m {
if msg.Severity == support.InfoSev { if msg.Severity == support.InfoSev {
if strings.Contains(msg.Err.Error(), "icon is recommended") { 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") { if strings.Contains(msg.Err.Error(), "name is required") {
e2 = true e2 = true
} }
if strings.Contains(msg.Err.Error(), "directory name (badchartfile) and chart name () must be the same") {
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(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") {
e4 = true e3 = true
} }
if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") { if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") {
e5 = true e4 = true
} }
if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { 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) t.Errorf("Didn't find all the expected errors, got %#v", m)
} }
} }

@ -46,7 +46,6 @@ func Chartfile(linter *support.Linter) {
} }
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile)) linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile))
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNameDirMatch(linter.ChartDir, chartFile))
// Chart metadata // Chart metadata
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile)) linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile))
@ -82,13 +81,6 @@ func validateChartName(cf *chart.Metadata) error {
return nil 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 { func validateChartAPIVersion(cf *chart.Metadata) error {
if cf.APIVersion == "" { if cf.APIVersion == "" {
return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"") return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"")

@ -31,17 +31,14 @@ import (
const ( const (
badChartDir = "testdata/badchartfile" badChartDir = "testdata/badchartfile"
goodChartDir = "testdata/goodone"
) )
var ( var (
badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") badChartFilePath = filepath.Join(badChartDir, "Chart.yaml")
goodChartFilePath = filepath.Join(goodChartDir, "Chart.yaml")
nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml")
) )
var badChart, _ = chartutil.LoadChartfile(badChartFilePath) var badChart, _ = chartutil.LoadChartfile(badChartFilePath)
var goodChart, _ = chartutil.LoadChartfile(goodChartFilePath)
// Validation functions Test // Validation functions Test
func TestValidateChartYamlNotDirectory(t *testing.T) { 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) { func TestValidateChartVersion(t *testing.T) {
var failTest = []struct { var failTest = []struct {
Version string Version string
@ -209,36 +188,32 @@ func TestChartfile(t *testing.T) {
Chartfile(&linter) Chartfile(&linter)
msgs := linter.Messages msgs := linter.Messages
if len(msgs) != 7 { if len(msgs) != 6 {
t.Errorf("Expected 7 errors, got %d", len(msgs)) t.Errorf("Expected 6 errors, got %d", len(msgs))
} }
if !strings.Contains(msgs[0].Err.Error(), "name is required") { if !strings.Contains(msgs[0].Err.Error(), "name is required") {
t.Errorf("Unexpected message 0: %s", msgs[0].Err) 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) 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) 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) 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) 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) 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)
}
} }

@ -169,7 +169,7 @@ func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
if s.Entity == nil { if s.Entity == nil {
return errors.New("private key not found") return errors.New("private key not found")
} else if s.Entity.PrivateKey == nil { } 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. // 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 { if s.Entity == nil {
return "", errors.New("private key not found") return "", errors.New("private key not found")
} else if s.Entity.PrivateKey == nil { } 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 { if fi, err := os.Stat(chartpath); err != nil {

@ -19,6 +19,7 @@ package release
type Status string type Status string
// Describe the status of a release // Describe the status of a release
// NOTE: Make sure to update cmd/helm/status.go when adding or modifying any of these statuses.
const ( const (
// StatusUnknown indicates that a release is in an uncertain state. // StatusUnknown indicates that a release is in an uncertain state.
StatusUnknown Status = "unknown" StatusUnknown Status = "unknown"

@ -84,7 +84,7 @@ func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error
return vals, err return vals, err
} }
// ParseIntoString parses a strvals line nad merges the result into dest. // ParseIntoString parses a strvals 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.
func ParseIntoString(s string, dest map[string]interface{}) error { func ParseIntoString(s string, dest map[string]interface{}) error {
@ -108,6 +108,9 @@ type RunesValueReader func([]rune) (interface{}, error)
// parser is a simple parser that takes a strvals line and parses it into a // parser is a simple parser that takes a strvals line and parses it into a
// map representation. // map representation.
//
// where sc is the source of the original data being parsed
// 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{}
@ -285,8 +288,14 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
// We have a nested object. Send to t.key // We have a nested object. Send to t.key
inner := map[string]interface{}{} inner := map[string]interface{}{}
if len(list) > i { if len(list) > i {
var ok bool
inner, ok = list[i].(map[string]interface{})
if !ok {
// We have indices out of order. Initialize empty value.
list[i] = map[string]interface{}{}
inner = list[i].(map[string]interface{}) inner = list[i].(map[string]interface{})
} }
}
// Recurse // Recurse
e := t.key(inner) e := t.key(inner)
@ -367,6 +376,11 @@ func inMap(k rune, m map[rune]bool) bool {
func typedVal(v []rune, st bool) interface{} { func typedVal(v []rune, st bool) interface{} {
val := string(v) val := string(v)
if st {
return val
}
if strings.EqualFold(val, "true") { if strings.EqualFold(val, "true") {
return true return true
} }
@ -375,8 +389,16 @@ func typedVal(v []rune, st bool) interface{} {
return false return false
} }
// If this value does not start with zero, and not returnString, try parsing it to an int if strings.EqualFold(val, "null") {
if !st && len(val) != 0 && val[0] != '0' { return nil
}
if strings.EqualFold(val, "0") {
return int64(0)
}
// If this value does not start with zero, try parsing it to an int
if len(val) != 0 && val[0] != '0' {
if iv, err := strconv.ParseInt(val, 10, 64); err == nil { if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
return iv return iv
} }

@ -75,12 +75,32 @@ func TestParseSet(t *testing.T) {
expect: map[string]interface{}{"long_int_string": "1234567890"}, expect: map[string]interface{}{"long_int_string": "1234567890"},
err: false, err: false,
}, },
{
str: "boolean=true",
expect: map[string]interface{}{"boolean": "true"},
err: false,
},
{
str: "is_null=null",
expect: map[string]interface{}{"is_null": "null"},
err: false,
},
{
str: "zero=0",
expect: map[string]interface{}{"zero": "0"},
err: false,
},
} }
tests := []struct { tests := []struct {
str string str string
expect map[string]interface{} expect map[string]interface{}
err bool err bool
}{ }{
{
"name1=null,f=false,t=true",
map[string]interface{}{"name1": nil, "f": false, "t": true},
false,
},
{ {
"name1=value1", "name1=value1",
map[string]interface{}{"name1": "value1"}, map[string]interface{}{"name1": "value1"},
@ -108,10 +128,23 @@ func TestParseSet(t *testing.T) {
str: "leading_zeros=00009", str: "leading_zeros=00009",
expect: map[string]interface{}{"leading_zeros": "00009"}, expect: map[string]interface{}{"leading_zeros": "00009"},
}, },
{
str: "zero_int=0",
expect: map[string]interface{}{"zero_int": 0},
},
{ {
str: "long_int=1234567890", str: "long_int=1234567890",
expect: map[string]interface{}{"long_int": 1234567890}, expect: map[string]interface{}{"long_int": 1234567890},
}, },
{
str: "boolean=true",
expect: map[string]interface{}{"boolean": true},
},
{
str: "is_null=null",
expect: map[string]interface{}{"is_null": nil},
err: false,
},
{ {
str: "name1,name2=", str: "name1,name2=",
err: true, err: true,
@ -270,6 +303,30 @@ func TestParseSet(t *testing.T) {
str: "nested[1][1]=1", str: "nested[1][1]=1",
expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}}, expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}},
}, },
{
str: "name1.name2[0].foo=bar,name1.name2[1].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}},
},
},
},
{
str: "name1.name2[1].foo=bar,name1.name2[0].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}},
},
},
},
{
str: "name1.name2[1].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{nil, {"foo": "bar"}},
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -331,12 +388,13 @@ func TestParseInto(t *testing.T) {
"inner2": "value2", "inner2": "value2",
}, },
} }
input := "outer.inner1=value1,outer.inner3=value3" input := "outer.inner1=value1,outer.inner3=value3,outer.inner4=4"
expect := map[string]interface{}{ expect := map[string]interface{}{
"outer": map[string]interface{}{ "outer": map[string]interface{}{
"inner1": "value1", "inner1": "value1",
"inner2": "value2", "inner2": "value2",
"inner3": "value3", "inner3": "value3",
"inner4": 4,
}, },
} }
@ -357,6 +415,39 @@ func TestParseInto(t *testing.T) {
t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2)
} }
} }
func TestParseIntoString(t *testing.T) {
got := map[string]interface{}{
"outer": map[string]interface{}{
"inner1": "overwrite",
"inner2": "value2",
},
}
input := "outer.inner1=1,outer.inner3=3"
expect := map[string]interface{}{
"outer": map[string]interface{}{
"inner1": "1",
"inner2": "value2",
"inner3": "3",
},
}
if err := ParseIntoString(input, got); err != nil {
t.Fatal(err)
}
y1, err := yaml.Marshal(expect)
if err != nil {
t.Fatal(err)
}
y2, err := yaml.Marshal(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", input, y1, y2)
}
}
func TestParseIntoFile(t *testing.T) { func TestParseIntoFile(t *testing.T) {
got := map[string]interface{}{} got := map[string]interface{}{}
@ -367,7 +458,7 @@ func TestParseIntoFile(t *testing.T) {
rs2v := func(rs []rune) (interface{}, error) { rs2v := func(rs []rune) (interface{}, error) {
v := string(rs) v := string(rs)
if v != "path1" { if v != "path1" {
t.Errorf("%s: RunesValueReader: Expected value path1, got %s", input, v) t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v)
return "", nil return "", nil
} }
return "value1", nil return "value1", nil

@ -78,13 +78,16 @@ verifySupported() {
# checkDesiredVersion checks if the desired version is available. # checkDesiredVersion checks if the desired version is available.
checkDesiredVersion() { checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then if [ "x$DESIRED_VERSION" == "x" ]; then
# FIXME(bacongobbler): hard code the desired version for the time being.
# A better fix would be to filter for Helm 2 release pages.
TAG="v2.16.1"
# Get tag from release URL # Get tag from release URL
local latest_release_url="https://github.com/helm/helm/releases/latest" # local latest_release_url="https://github.com/helm/helm/releases/latest"
if type "curl" > /dev/null; then # if type "curl" > /dev/null; then
TAG=$(curl -Ls -o /dev/null -w %{url_effective} $latest_release_url | grep -oE "[^/]+$" ) # TAG=$(curl -Ls -o /dev/null -w %{url_effective} $latest_release_url | grep -oE "[^/]+$" )
elif type "wget" > /dev/null; then # elif type "wget" > /dev/null; then
TAG=$(wget $latest_release_url --server-response -O /dev/null 2>&1 | awk '/^ Location: /{DEST=$2} END{ print DEST}' | grep -oE "[^/]+$") # TAG=$(wget $latest_release_url --server-response -O /dev/null 2>&1 | awk '/^ Location: /{DEST=$2} END{ print DEST}' | grep -oE "[^/]+$")
fi # fi
else else
TAG=$DESIRED_VERSION TAG=$DESIRED_VERSION
fi fi
@ -187,7 +190,7 @@ testVersion() {
help () { help () {
echo "Accepted cli arguments are:" echo "Accepted cli arguments are:"
echo -e "\t[--help|-h ] ->> prints this help" echo -e "\t[--help|-h ] ->> prints this help"
echo -e "\t[--version|-v <desired_version>] . When not defined it defaults to latest" echo -e "\t[--version|-v <desired_version>]"
echo -e "\te.g. --version v2.4.0 or -v latest" echo -e "\te.g. --version v2.4.0 or -v latest"
echo -e "\t[--no-sudo] ->> install without sudo" echo -e "\t[--no-sudo] ->> install without sudo"
} }

@ -0,0 +1,236 @@
#!/usr/bin/env bash
# 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.
# The install script is based off of the MIT-licensed script from glide,
# the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get
PROJECT_NAME="helm"
: ${USE_SUDO:="true"}
: ${HELM_INSTALL_DIR:="/usr/local/bin"}
# initArch discovers the architecture for this system.
initArch() {
ARCH=$(uname -m)
case $ARCH in
armv5*) ARCH="armv5";;
armv6*) ARCH="armv6";;
armv7*) ARCH="arm";;
aarch64) ARCH="arm64";;
x86) ARCH="386";;
x86_64) ARCH="amd64";;
i686) ARCH="386";;
i386) ARCH="386";;
esac
}
# initOS discovers the operating system for this system.
initOS() {
OS=$(echo `uname`|tr '[:upper:]' '[:lower:]')
case "$OS" in
# Minimalist GNU for Windows
mingw*) OS='windows';;
esac
}
# runs the given command as root (detects if we are root already)
runAsRoot() {
local CMD="$*"
if [ $EUID -ne 0 -a $USE_SUDO = "true" ]; then
CMD="sudo $CMD"
fi
$CMD
}
# verifySupported checks that the os/arch combination is supported for
# binary builds.
verifySupported() {
local supported="darwin-386\ndarwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nwindows-386\nwindows-amd64"
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
echo "No prebuilt binary for ${OS}-${ARCH}."
echo "To build from source, go to https://github.com/helm/helm"
exit 1
fi
if ! type "curl" > /dev/null && ! type "wget" > /dev/null; then
echo "Either curl or wget is required"
exit 1
fi
}
# checkDesiredVersion checks if the desired version is available.
checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then
# Get tag from release URL
local latest_release_url="https://github.com/helm/helm/releases/latest"
if type "curl" > /dev/null; then
TAG=$(curl -Ls -o /dev/null -w %{url_effective} $latest_release_url | grep -oE "[^/]+$" )
elif type "wget" > /dev/null; then
TAG=$(wget $latest_release_url --server-response -O /dev/null 2>&1 | awk '/^ Location: /{DEST=$2} END{ print DEST}' | grep -oE "[^/]+$")
fi
else
TAG=$DESIRED_VERSION
fi
}
# checkHelmInstalledVersion checks which version of helm is installed and
# if it needs to be changed.
checkHelmInstalledVersion() {
if [[ -f "${HELM_INSTALL_DIR}/${PROJECT_NAME}" ]]; then
local version=$("${HELM_INSTALL_DIR}/${PROJECT_NAME}" version --template="{{ .Version }}")
if [[ "$version" == "$TAG" ]]; then
echo "Helm ${version} is already ${DESIRED_VERSION:-latest}"
return 0
else
echo "Helm ${TAG} is available. Changing from version ${version}."
return 1
fi
else
return 1
fi
}
# downloadFile downloads the latest binary package and also the checksum
# for that binary.
downloadFile() {
HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz"
DOWNLOAD_URL="https://get.helm.sh/$HELM_DIST"
CHECKSUM_URL="$DOWNLOAD_URL.sha256"
HELM_TMP_ROOT="$(mktemp -dt helm-installer-XXXXXX)"
HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST"
HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256"
echo "Downloading $DOWNLOAD_URL"
if type "curl" > /dev/null; then
curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE"
elif type "wget" > /dev/null; then
wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL"
fi
if type "curl" > /dev/null; then
curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE"
elif type "wget" > /dev/null; then
wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL"
fi
}
# installFile verifies the SHA256 for the file, then unpacks and
# installs it.
installFile() {
HELM_TMP="$HELM_TMP_ROOT/$PROJECT_NAME"
local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}')
local expected_sum=$(cat ${HELM_SUM_FILE})
if [ "$sum" != "$expected_sum" ]; then
echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting."
exit 1
fi
mkdir -p "$HELM_TMP"
tar xf "$HELM_TMP_FILE" -C "$HELM_TMP"
HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/$PROJECT_NAME"
echo "Preparing to install $PROJECT_NAME into ${HELM_INSTALL_DIR}"
runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR"
echo "$PROJECT_NAME installed into $HELM_INSTALL_DIR/$PROJECT_NAME"
}
# fail_trap is executed if an error occurs.
fail_trap() {
result=$?
if [ "$result" != "0" ]; then
if [[ -n "$INPUT_ARGUMENTS" ]]; then
echo "Failed to install $PROJECT_NAME with the arguments provided: $INPUT_ARGUMENTS"
help
else
echo "Failed to install $PROJECT_NAME"
fi
echo -e "\tFor support, go to https://github.com/helm/helm."
fi
cleanup
exit $result
}
# testVersion tests the installed client to make sure it is working.
testVersion() {
set +e
HELM="$(which $PROJECT_NAME)"
if [ "$?" = "1" ]; then
echo "$PROJECT_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?'
exit 1
fi
set -e
}
# help provides possible cli installation arguments
help () {
echo "Accepted cli arguments are:"
echo -e "\t[--help|-h ] ->> prints this help"
echo -e "\t[--version|-v <desired_version>] . When not defined it fetches the latest release from GitHub"
echo -e "\te.g. --version v3.0.0 or -v canary"
echo -e "\t[--no-sudo] ->> install without sudo"
}
# cleanup temporary files to avoid https://github.com/helm/helm/issues/2977
cleanup() {
if [[ -d "${HELM_TMP_ROOT:-}" ]]; then
rm -rf "$HELM_TMP_ROOT"
fi
}
# Execution
#Stop execution on any error
trap "fail_trap" EXIT
set -e
# Parsing input arguments (if any)
export INPUT_ARGUMENTS="${@}"
set -u
while [[ $# -gt 0 ]]; do
case $1 in
'--version'|-v)
shift
if [[ $# -ne 0 ]]; then
export DESIRED_VERSION="${1}"
else
echo -e "Please provide the desired version. e.g. --version v3.0.0 or -v canary"
exit 0
fi
;;
'--no-sudo')
USE_SUDO="false"
;;
'--help'|-h)
help
exit 0
;;
*) exit 1
;;
esac
shift
done
set +u
initArch
initOS
verifySupported
checkDesiredVersion
if ! checkHelmInstalledVersion; then
downloadFile
installFile
fi
testVersion
cleanup

@ -27,14 +27,16 @@ find_files() {
\( -name '*.go' -o -name '*.sh' \) \( -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 if (( ${#failed_license_header[@]} > 0 )); then
echo "Some source files are missing license headers." echo "Some source files are missing license headers."
printf '%s\n' "${failed_license_header[@]}" printf '%s\n' "${failed_license_header[@]}"
exit 1 exit 1
fi 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 if (( ${#failed_copyright_header[@]} > 0 )); then
echo "Some source files are missing the copyright header." echo "Some source files are missing the copyright header."
printf '%s\n' "${failed_copyright_header[@]}" printf '%s\n' "${failed_copyright_header[@]}"

35
testdata/ca.pem vendored

@ -1,35 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIGADCCA+igAwIBAgIJALbFKeU+io3AMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDTzEQMA4GA1UEBxMHQm91bGRlcjEPMA0GA1UEChMG
VGlsbGVyMQ8wDQYDVQQLEwZUaWxsZXIxDTALBgNVBAMTBEhlbG0wHhcNMTcwNDA0
MTYwNDQ5WhcNMTgwNDA0MTYwNDQ5WjBdMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
Q08xEDAOBgNVBAcTB0JvdWxkZXIxDzANBgNVBAoTBlRpbGxlcjEPMA0GA1UECxMG
VGlsbGVyMQ0wCwYDVQQDEwRIZWxtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAyFOriVMm3vTeVerwMuBEIt07EJFzAn+5R1eqdNEJ0k08/ZPKPLnhkg+/
sRZuzah4lbszbAb7frtqtXKT8u28/tsQofCt5M9VZLK21yS4QX1kBS3CvN9mfw4r
S+yzoP/7oFPydwVhSsOZ3kRUrU7jyxZjFMPCLJU5O1WTRA/PEKagjf5Y63q0jhU7
/VDPazeUKSvfyPW9HxVMLkWYK6hLb2sDoopbeV5L/wPDb66sLuIPcGw25SprzDqq
9OtM2pMG89h1cDhXeH8NJPOVzCkkalqwl+Ytl2alh9HWT8cb0nJ+TKhFtvTpM60U
Ku+H+zLTIaHBIUxKrNiTowBQe4JcHmyYp+IJnZv/l4kH5CkWIX3SIcOACSbLlzWB
QjBCWDtgmT4bdCDtnQF6eTVdMOy76/Yyzj9xLKUEr/fNqE4CtZMEfJdELHsX9hpC
Dq031NgKNZvMd+llv259QWFVltZ+GOctCaT4TlTWRiFYl0ysYnsZ5HbA6eKt810l
rpjtnrKCBenzrHLRCP+BGcfhGlisiutaclUwwgKow8/OV4+9Eg4RTeIhzWIIcfDI
UDgkecNcTPK2VZt4Kj6D2vvWJHqUNpiL1FVekki7FrhkoXR5BOvHfoDqpvl+BTyb
AfBmPyVx9/0zoAdYfpRsMUjVeWtS/oS9UDt2UJojSa1hMhd8pIECAwEAAaOBwjCB
vzAdBgNVHQ4EFgQU7NrQViMsDpfYfVZITtwOuT2J6HYwgY8GA1UdIwSBhzCBhIAU
7NrQViMsDpfYfVZITtwOuT2J6HahYaRfMF0xCzAJBgNVBAYTAlVTMQswCQYDVQQI
EwJDTzEQMA4GA1UEBxMHQm91bGRlcjEPMA0GA1UEChMGVGlsbGVyMQ8wDQYDVQQL
EwZUaWxsZXIxDTALBgNVBAMTBEhlbG2CCQC2xSnlPoqNwDAMBgNVHRMEBTADAQH/
MA0GCSqGSIb3DQEBCwUAA4ICAQCs+RwppSZugKN+LZ226wf+A86+BEFXNyVQ5all
YgBA4Oiai3O3XGMpNmm60TbumjzVq8PrNNuQxR2VfK/N7qLLJMktIVBntRsiQnTR
Yw/EuhcuvYOhJ7P8RwifkhusZTLI6eQhES5bmUYuXmp887qkr/dN1XmiubTKLDTE
fZAhOVAvA55YgJzEvBkVAXpT5tzrOakjo+PM6NoUcEWQsh3z1RRgFowUi3aKjM7k
J38h5iCJCLlo5Av+bhdw/rP+qw7d6DgKemrxC91qyk48BhTXp3qR3XLmuqjtQq6u
xMPgKNs6/fornWbvCX+vQq9Hncm7X4ZHBdoaWAs5P9lpACuR77/Ad30rY026bM4m
br8VQxWU2qlTt8vfp8jIuiylJP/YU9aMsKc8lIue19As+Llw9t9Zdq3z/Q3xul7N
hXLa/NJeban9iTNgjzPWigSGpaXIFxYZ3fl0flYkMG2KzhuYttHVuWyIJ8WLpsPN
Os9SIkekZipwsCdtL65fCLj5DjAmX6LwnxVf6Z5K9hsOEM+uZvq0qsrLjndxmbrG
+Br+p4jxH8kkUNdoNVlbg1F+0+sgtD9drgSLM4cZ9wVWUl64qbDpQR+/pVlSepiQ
kPTthsGtcrW8sTSMlLY4XpCLcS/hwO4jwNCB+8bLsz/6p9vCDMIkb5zkhjPc/Awe
mlK3dw==
-----END CERTIFICATE-----

98
testdata/crt.pem vendored

@ -1,29 +1,73 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
55:31:53:9b:41:72:05:dc:90:49:bd:48:13:7c:59:9e:5a:53:5e:86
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh
Validity
Not Before: Nov 1 22:51:49 2019 GMT
Not After : Oct 29 22:51:49 2029 GMT
Subject: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:c8:89:55:0d:0b:f1:da:e6:c0:70:7d:d3:27:cd:
b8:a8:81:8b:7c:a4:89:e5:d1:b1:78:01:1d:df:44:
88:0b:fc:d6:81:35:3d:d1:3b:5e:8f:bb:93:b3:7e:
28:db:ed:ff:a0:13:3a:70:a3:fe:94:6b:0b:fe:fb:
63:00:b0:cb:dc:81:cd:80:dc:d0:2f:bf:b2:4f:9a:
81:d4:22:dc:97:c8:8f:27:86:59:91:fa:92:05:75:
c4:cc:6b:f5:a9:6b:74:1e:f5:db:a9:f8:bf:8c:a2:
25:fd:a0:cc:79:f4:25:57:74:a9:23:9b:e2:b7:22:
7a:14:7a:3d:ea:f1:7e:32:6b:57:6c:2e:c6:4f:75:
54:f9:6b:54:d2:ca:eb:54:1c:af:39:15:9b:d0:7c:
0f:f8:55:51:04:ea:da:fa:7b:8b:63:0f:ac:39:b1:
f6:4b:8e:4e:f6:ea:e9:7b:e6:ba:5e:5a:8e:91:ef:
dc:b1:7d:52:3f:73:83:52:46:83:48:49:ff:f2:2d:
ca:54:f2:36:bb:49:cc:59:99:c0:9e:cf:8e:78:55:
6c:ed:7d:7e:83:b8:59:2c:7d:f8:1a:81:f0:7d:f5:
27:f2:db:ae:d4:31:54:38:fe:47:b2:ee:16:20:0f:
f1:db:2d:28:bf:6f:38:eb:11:bb:9a:d4:b2:5a:3a:
4a:7f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:helm.sh, IP Address:127.0.0.1
Signature Algorithm: sha256WithRSAEncryption
4e:17:27:3d:36:4e:6c:2b:f7:d4:28:33:7e:05:26:7a:42:a0:
2c:44:57:04:a0:de:df:40:fb:af:70:27:e6:55:20:f1:f8:c0:
50:63:ab:b8:f1:31:5d:1e:f4:ca:8d:65:0b:d4:5e:5b:77:2f:
2a:af:74:5f:18:2d:92:29:7f:2d:97:fb:ec:aa:e3:1e:db:b3:
8d:01:aa:82:1a:f6:28:a8:b3:ee:15:9f:9a:f5:76:37:30:f2:
3b:38:13:b2:d4:14:94:c6:38:fa:f9:6e:94:e8:1f:11:0b:b0:
69:1a:b3:f9:f1:27:b4:d2:f5:64:54:7c:8f:e7:83:31:f6:0d:
a7:0e:0e:66:d8:33:2f:e0:a1:93:56:92:58:bf:50:da:56:8e:
db:42:22:f5:0c:6f:f8:4c:ef:f5:7c:2d:a6:b8:60:e4:bb:df:
a3:6c:c2:6b:99:0b:d3:0a:ad:7c:f4:74:72:9a:52:5e:81:d9:
a2:a2:dd:68:38:fb:b7:54:7f:f6:aa:ee:53:de:3d:3a:0e:86:
53:ad:af:72:db:fb:6b:18:ce:ac:e4:64:70:13:68:da:be:e1:
6b:46:dd:a0:72:96:9b:3f:ba:cf:11:6e:98:03:0a:69:83:9e:
37:25:c9:36:b9:68:4f:73:ca:c6:32:5c:be:46:64:bb:a8:cc:
71:25:8f:be
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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 g543Jck2uWhPc8rGMly+RmS7qMxxJY++
acynThdcI4eZGc2fKSAIw6RN7iIln74Sf4MNmEuQu6Dnq4QkZKAWtnY7Uq5ooFJS
JA+Joqif8SvEvMgq02XdUhjijlBAanxI/xp64k37k18+pHAxcS22HzrjwDQ4ELqY
gBq9g20JYXoUxjBFUfj+cxBx+LBKfPVTpcbicI4wwP4a2BA6LDUHgcnSMhle1zeq
pHuOIOT6XqYLhO0Yr7WRG9Yzuxs0GV4TH+FlDpDHWL8XG0gjDUZ/2viPlKBr+FoN
inW8jqQ2NYMzYF9zHNzXVGK+5oyH4Y7r/8WxQLfdSR/5S1DXPLSkzkYbduHf9UmF
Dvh6NrCGU0UxypA1NvF5o11cnTQ22GPywVSc0ILKWDRlu8DiGq71bYQu8hTTkTnb
2hOr5JHcGaloms7WM3q0hc2PIhwYXw2V3b9I9lbnvv3Y/yKPNN7IzU5No6siRuIH
paj83V0flMWj1EqJMDxk9ECHgDyl/1ftgJVx1G/f/+UnXoRdR2kFqVVeJTeSIZi7
dSsAOIMN/weZMZF55Q61vgUgYXKp4g2/Zk8BJn0cx9pjEMIw/pc7Eq1x/R8=
-----END CERTIFICATE----- -----END CERTIFICATE-----

@ -0,0 +1,4 @@
#!/bin/sh
openssl req -new -config openssl.conf -key key.pem -out key.csr
openssl ca -config openssl.conf -create_serial -batch -in key.csr -out crt.pem -key rootca.key -cert rootca.crt

74
testdata/key.pem vendored

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

@ -0,0 +1,42 @@
[ca]
default_ca = CA_default
[CA_default]
dir = ./
database = $dir/index.txt
new_certs_dir = ./
serial = $dir/serial
private_key = ./rootca.key
certificate = ./rootca.crt
default_days = 3650
default_md = sha256
policy = policy_anything
copy_extensions = copyall
[policy_anything]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name (full name)
localityName = Locality Name (eg, city)
organizationName = Organization Name (eg, company)
commonName = Common Name (e.g. server FQDN or YOUR name)
[ v3_req ]
subjectAltName = @alternate_names
[alternate_names]
DNS.1 = helm.sh
IP.1 = 127.0.0.1

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDITCCAgkCFAasUT/De3J4aee7b1VEESf+3ndyMA0GCSqGSIb3DQEBCwUAME0x
CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEQMA4GA1UEBwwHQm91bGRlcjENMAsG
A1UECgwESGVsbTEQMA4GA1UEAwwHaGVsbS5zaDAeFw0xOTExMDEyMjM2MzZaFw0y
MjA4MjEyMjM2MzZaME0xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEQMA4GA1UE
BwwHQm91bGRlcjENMAsGA1UECgwESGVsbTEQMA4GA1UEAwwHaGVsbS5zaDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMinBcDJwiG3OVb1bCWQqTAOS3s6
QwWkEXkoYyFFpCNvqEzQPtp+OkfD6gczc0ByGQibDLBApEQhq17inqtAxIUrTgXP
ym3l+0/U7ejuTka3ue84slkw2lVobfVEvJWGro+93GzbxvVNNYGJcD2BKJqmCCxD
I6tdTEL855kzgQUAvGITzDUxABU9+f06CW/9AlZlmBIuwrzRVjFNjflBrcm1PIUG
upMCu8zaWat8o1TnLCDKizw1JJzCgCnMxGXfzeAd1MGUG/rOFkBImHf39Jakp/7L
Icq+2FDE+0vNai0lpUpxPVTp8dcug8U3//bL3q0OqROA7Ks4wc0URGH71W8CAwEA
ATANBgkqhkiG9w0BAQsFAAOCAQEAMJqzeg6cBbUkrh9a6+qa66IFR1Mf3wVB1c61
JN6Z70kjgSdOZ/NexxxSu347fIPyKGkmokbnE1MJVEETPmzhpuTkQDcq7KT4IcQF
S+H4l0lNn09thIlIiAJmpQrNOlrHVtpLCFB4+YnsqqFKPlcO/dGy9U26L4xfn6+n
24/o7pNEu44GnktXPjfcbajaPUSKHxeYibjdftoUEYX/79ROu7E1QnNXj7mXymw0
rqOgIlyCUGw8WvRR8RzR6m+1lnwOc+nxFKXzTt0LqOQt9sHI1V71WrxgDE+Lck+W
fybfsgodM2Y7VXnH4A4xoKeOHxW1YcqIKt0ribt8602lD1pYBg==
-----END CERTIFICATE-----

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyKcFwMnCIbc5VvVsJZCpMA5LezpDBaQReShjIUWkI2+oTNA+
2n46R8PqBzNzQHIZCJsMsECkRCGrXuKeq0DEhStOBc/KbeX7T9Tt6O5ORre57ziy
WTDaVWht9US8lYauj73cbNvG9U01gYlwPYEomqYILEMjq11MQvznmTOBBQC8YhPM
NTEAFT35/ToJb/0CVmWYEi7CvNFWMU2N+UGtybU8hQa6kwK7zNpZq3yjVOcsIMqL
PDUknMKAKczEZd/N4B3UwZQb+s4WQEiYd/f0lqSn/sshyr7YUMT7S81qLSWlSnE9
VOnx1y6DxTf/9sverQ6pE4DsqzjBzRREYfvVbwIDAQABAoIBAHwyTbBP8baWx4oY
rNDvoplZL8VdgaCbNimNIxa0GW3Jrh2lhFIPcZl8HX5JjVvlg7M87XSm/kYhpQY9
NUMA+uMGs+uK+1xcztpSDNRxtMe27wKwUEw+ndXhprX6ztOqop/cP/StcI/jM2wz
muKm8HAQttxWzlxCinKoQd4k8AYcnqc728FSODP7EsdDgiU6BhBZDqjgmqggye0y
niog+JBPDgwTgGodJWtSYuP/G2iJDUvm7bGU2gftXTJstrATLftGKX8XOgJMmDx9
8OgDtU21LzggarOQ/iwUKX2MEfYnP8kgGLgu5nNonJCHWYGeCZoxIn70rs3WoBsU
5+FzmHkCgYEA7MFYixlTSxXfen1MwctuZ9YiwoneSLfjmBb+LP0Pfa2r0CVMPaXM
OexroIY14h64nunb7y3YifGk01RXzCBpEF5KhsZuYXAl3lGxbjbTjncU5/11Dim+
W9g+T4zDimlK2tuweAjMfWz6XG2inZ3xvK73mGkEsUnqhWQKXBRf7VsCgYEA2PZp
KAwbpRFSYFwcZoRm81fLijZ5NbmOJtND6oG1LZVaVSYuvljvjQzeVfL4+Iju6FzT
zbnEfVsatu0cTs6jMy0yJUl6wRbHlH/G6Ra8UxSvUUEFe1Xap33RmjkK+atzALQi
pZPCIfLr+f9qQWrPMdZwzRnws0u2pKepSdXR0H0CgYB9chDdWyTkIwnPmDakdIri
X/b5Bx4Nf8oLGxvAcLHVkMD5v9l+zKvCgT+hxZslXcvK//S17Z/Pr4b7JrSChyXE
M4HfmaKA5HBcNQMDd+9ujDA6n/R29a1UcubJNbeiThoIjuEZKOhZCPY7JShFxZuB
s1+jlPmUiqrF1PUcRvtxAwKBgQDGpuelmWB+hRutyujeHQC+cnaU+EeHH3y+o9Wd
lGG1ePia2jkWZAwCU/QHMk8wEQDelJAB38O/G3mcYAH5Tk4zf4BYj6zrutXGbDBO
H1kToO7dMPG5+eQYU6Vk1jHsZEUKMeU/QckQmIHkBy7c8tT/Rt9FjCjNodd7b2Ab
kMFpaQKBgQDggmgsPFSZmo+yYDZucueXqfc8cbSWd9K1UruKMaPOsyoUWJNYARHA
cpHTpaIjDth8MUp2zLIZnPUSDkSgEAOcRH4C5CxmgSkmeJdlEEzWMF2yugczlYGO
l9SOX07w4/WJCZFeRWTqRGWs7X6iL8um0P9yFelw3SZt33ON+1fRPg==
-----END RSA PRIVATE KEY-----
Loading…
Cancel
Save