Merge branch 'master' into fix/sortEnv

pull/6891/head
Marc Khouzam 6 years ago
commit f504708b9c

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

@ -7,7 +7,6 @@ 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 +80,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 +117,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)

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

@ -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,11 +136,21 @@ 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, args[i+1])
i++ i++
default: default:

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

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

@ -126,7 +126,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...)
} }
@ -301,7 +301,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 +422,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) {

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

@ -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
} }
@ -372,8 +377,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,8 @@ func TestGetRepoNames(t *testing.T) {
} }
} }
// This function is the skeleton test code of failing tests for #6416 and bugs due to #5874. // 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 +284,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",
})
}

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

@ -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
Loading…
Cancel
Save