fix: test lint/lint_test.go

Signed-off-by: Aurélien Lambert <aure@olli-ai.com>
pull/6876/head
Aurélien Lambert 6 years ago
commit f7d6f094da
No known key found for this signature in database
GPG Key ID: C749F51FF763C5DA

@ -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
BINNAME ?= helm TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-386.tar.gz linux-386.tar.gz.sha256 linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 windows-amd64.zip windows-amd64.zip.sha256
BINNAME ?= helm
GOPATH = $(shell go env GOPATH) 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 \

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

@ -130,7 +130,7 @@ 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)")

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

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

@ -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)
@ -120,6 +128,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.StringArrayVarP(&showFiles, "show-only", "s", []string{}, "only show manifests rendered from the given templates") f.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, "validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install") 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,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

@ -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) {
cert, err := CertFromFilePair(certFile, keyFile) config := tls.Config{}
if err != nil {
return nil, err if certFile != "" && keyFile != "" {
} cert, err := CertFromFilePair(certFile, keyFile)
config := tls.Config{ if err != nil {
Certificates: []tls.Certificate{*cert}, return nil, err
}
config.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"
) )
@ -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
} }

@ -109,9 +109,19 @@ 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 {
return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") 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")
}
} }
c.Capabilities = &chartutil.Capabilities{ c.Capabilities = &chartutil.Capabilities{

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

@ -86,6 +86,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 +128,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...)
} }
@ -193,11 +195,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 {
@ -233,7 +238,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")
} }
@ -297,7 +302,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
} }
@ -416,7 +423,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())
} }
} }

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

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

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

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

@ -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: "v1",
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.
@ -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",
})
}

@ -15,4 +15,9 @@ repositories:
- name: testing-relative - name: testing-relative
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

@ -99,11 +99,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
} }

@ -464,6 +464,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": map[string]interface{}{}, "Values": map[string]interface{}{},
"Chart": c.Metadata, "Chart": c.Metadata,
@ -481,6 +490,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) {

@ -89,7 +89,7 @@ func NewHTTPGetter(options ...Option) (Getter, error) {
} }
func (g *HTTPGetter) httpClient() (*http.Client, error) { func (g *HTTPGetter) httpClient() (*http.Client, error) {
if g.opts.certFile != "" && g.opts.keyFile != "" { if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" {
tlsConf, err := tlsutil.NewClientTLS(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 nil, errors.Wrap(err, "can't create TLS config for client") return nil, errors.Wrap(err, "can't create TLS config for client")

@ -180,4 +180,14 @@ func TestDownloadTLS(t *testing.T) {
if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil { if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil {
t.Error(err) 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)
}
} }

@ -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) != 9 { if len(m) != 8 {
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, e7 bool var i, w, e, e2, e3, e4, e5, e6 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,28 +59,25 @@ 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 strings.Contains(msg.Err.Error(), "chart.metadata.name is required") { if strings.Contains(msg.Err.Error(), "chart.metadata.name is required") {
e7 = true e6 = true
} }
} }
} }
if !e || !e2 || !e3 || !e4 || !e5 || !e6 || !e7 || !w || !i { if !e || !e2 || !e3 || !e4 || !e5 || !e6 || !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\"")

@ -30,18 +30,15 @@ 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 {

@ -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,7 +288,13 @@ 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 {
inner = list[i].(map[string]interface{}) 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{})
}
} }
// Recurse // Recurse
@ -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[@]}"

Loading…
Cancel
Save