Merge branch 'master' into fix/sortEnv

pull/6891/head
Marc Khouzam 5 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:
GOCACHE: "/tmp/go/cache"
GOLANGCI_LINT_VERSION: "1.21.0"
steps:
- checkout
- run:
name: install test dependencies
command: .circleci/bootstrap.sh
- run:
name: test style
command: make test-style

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

@ -7,7 +7,6 @@ GOPATH = $(shell go env GOPATH)
DEP = $(GOPATH)/bin/dep
GOX = $(GOPATH)/bin/gox
GOIMPORTS = $(GOPATH)/bin/goimports
GOLANGCI_LINT = $(GOPATH)/bin/golangci-lint
ACCEPTANCE_DIR:=$(GOPATH)/src/helm.sh/acceptance-testing
# To specify the subset of acceptance tests to run. '.' means all tests
@ -81,8 +80,8 @@ test-coverage:
@ ./scripts/coverage.sh
.PHONY: test-style
test-style: $(GOLANGCI_LINT)
GO111MODULE=on $(GOLANGCI_LINT) run
test-style:
GO111MODULE=on golangci-lint run
@scripts/validate-license.sh
.PHONY: test-acceptance
@ -118,9 +117,6 @@ format: $(GOIMPORTS)
$(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):
(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:
- [Homebrew](https://brew.sh/) users can use `brew install kubernetes-helm`.
- [Homebrew](https://brew.sh/) users can use `brew install helm`.
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install helm`.
- [GoFish](https://gofi.sh/) users can use `gofish install helm`.
To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide).
See the [installation guide](https://docs.helm.sh/using_helm/#installing-helm) for more options,
See the [installation guide](https://helm.sh/docs/intro/install/) for more options,
including installing pre-releases.
## Docs
Get started with the [Quick Start guide](https://docs.helm.sh/using_helm/#quickstart-guide) or plunge into the [complete documentation](https://docs.helm.sh)
Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) or plunge into the [complete documentation](https://helm.sh/docs)
## Roadmap

@ -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) {
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)")

@ -77,12 +77,15 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.SetStateMask()
results, err := client.Run()
if err != nil {
return err
}
if client.Short {
for _, res := range results {
fmt.Fprintln(out, res.Name)
}
return err
return nil
}
return outfmt.Write(out, newReleaseListWriter(results))

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

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

@ -27,18 +27,14 @@ import (
// Options represents configurable options used to create client and server TLS configurations.
type Options struct {
CaCertFile string
// If either the KeyFile or CertFile is empty, ClientConfig() will not load them,
// preventing Helm from authenticating to Tiller. They are required to be non-empty
// when calling ServerConfig, otherwise an error is returned.
// If either the KeyFile or CertFile is empty, ClientConfig() will not load them.
KeyFile string
CertFile string
// Client-only options
InsecureSkipVerify bool
// Server-only options
ClientAuth tls.ClientAuthType
}
// ClientConfig retusn a TLS configuration for use by a Helm client.
// ClientConfig returns a TLS configuration for use by a Helm client.
func ClientConfig(opts Options) (cfg *tls.Config, err error) {
var cert *tls.Certificate
var pool *x509.CertPool
@ -60,24 +56,3 @@ func ClientConfig(opts Options) (cfg *tls.Config, err error) {
cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
return cfg, nil
}
// ServerConfig returns a TLS configuration for use by the Tiller server.
func ServerConfig(opts Options) (cfg *tls.Config, err error) {
var cert *tls.Certificate
var pool *x509.CertPool
if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil {
if os.IsNotExist(err) {
return nil, errors.Wrapf(err, "could not load x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
}
return nil, errors.Wrapf(err, "could not read x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
}
if opts.ClientAuth >= tls.VerifyClientCertIfGiven && opts.CaCertFile != "" {
if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil {
return nil, err
}
}
cfg = &tls.Config{MinVersion: tls.VersionTLS12, ClientAuth: opts.ClientAuth, Certificates: []tls.Certificate{*cert}, ClientCAs: pool}
return cfg, nil
}

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

@ -17,7 +17,6 @@ limitations under the License.
package tlsutil
import (
"crypto/tls"
"path/filepath"
"testing"
)
@ -54,30 +53,61 @@ func TestClientConfig(t *testing.T) {
}
}
func TestServerConfig(t *testing.T) {
opts := Options{
CaCertFile: testfile(t, testCaCertFile),
CertFile: testfile(t, testCertFile),
KeyFile: testfile(t, testKeyFile),
ClientAuth: tls.RequireAndVerifyClientCert,
func testfile(t *testing.T, file string) (path string) {
var err error
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
}
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 {
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 {
t.Errorf("expecting non-nil CA pool")
if cfg.InsecureSkipVerify {
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) {
var err error
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
cfg, err = NewClientTLS("", "", caCertFile)
if err != nil {
t.Error(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 {
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)
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{

@ -126,7 +126,7 @@ func (i *Install) installCRDs(crds []*chart.File) error {
i.cfg.Log("CRD %s is already present. Skipping.", crdName)
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...)
}
@ -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
// this stored in the future.
i.recordRelease(rel)
if err := i.recordRelease(rel); err != nil {
i.cfg.Log("failed to record the release: %s", err)
}
return rel, nil
}
@ -420,7 +422,7 @@ func (c *Configuration) renderResources(ch *chart.Chart, values chartutil.Values
if ch.Metadata.KubeVersion != "" {
if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) {
return hs, b, "", errors.Errorf("chart requires kubernetesVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String())
}
}

@ -306,7 +306,7 @@ func TestInstallRelease_KubeVersion(t *testing.T) {
vals = map[string]interface{}{}
_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
is.Error(err)
is.Contains(err.Error(), "chart requires kubernetesVersion")
is.Contains(err.Error(), "chart requires kubeVersion")
}
func TestInstallRelease_Wait(t *testing.T) {

@ -126,6 +126,12 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
continue
}
switch hd.Typeflag {
// We don't want to process these extension header files.
case tar.TypeXGlobalHeader, tar.TypeXHeader:
continue
}
// Archive could contain \ if generated on Windows
delimiter := "/"
if strings.ContainsRune(hd.Name, '\\') {

@ -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()
}
// Check that all of the repos we're dependent on actually exist.
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 {
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
// the repo index names.
repoNames, err := m.getRepoNames(req)
repoNames, err := m.resolveRepoNames(req)
if err != nil {
return err
}
@ -372,8 +377,9 @@ Loop:
return nil
}
// getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file.
func (m *Manager) getRepoNames(deps []*chart.Dependency) (map[string]string, error) {
// resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file
// 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)
if err != nil {
if os.IsNotExist(err) {

@ -161,7 +161,7 @@ func TestGetRepoNames(t *testing.T) {
}
for _, tt := range tests {
l, err := m.getRepoNames(tt.req)
l, err := m.resolveRepoNames(tt.req)
if err != nil {
if tt.err {
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
// 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.
@ -283,3 +284,11 @@ func TestBuild_WithTags(t *testing.T) {
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) {
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)
if err != nil {
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 {
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"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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 {
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) {
return false, nil
}
@ -229,7 +241,10 @@ func (w *waiter) daemonSetReady(ds *appsv1.DaemonSet) bool {
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 {
switch cond.Type {
case apiextv1beta1.Established:
@ -249,6 +264,26 @@ func (w *waiter) crdReady(crd apiextv1beta1.CustomResourceDefinition) bool {
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 {
// If the update strategy is not a rolling update, there will be nothing to wait for
if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType {

@ -84,7 +84,7 @@ func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error
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.
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
// 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 {
sc *bytes.Buffer
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
inner := map[string]interface{}{}
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
@ -367,6 +376,11 @@ func inMap(k rune, m map[rune]bool) bool {
func typedVal(v []rune, st bool) interface{} {
val := string(v)
if st {
return val
}
if strings.EqualFold(val, "true") {
return true
}
@ -375,8 +389,16 @@ func typedVal(v []rune, st bool) interface{} {
return false
}
// If this value does not start with zero, and not returnString, try parsing it to an int
if !st && len(val) != 0 && val[0] != '0' {
if strings.EqualFold(val, "null") {
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 {
return iv
}

@ -75,12 +75,32 @@ func TestParseSet(t *testing.T) {
expect: map[string]interface{}{"long_int_string": "1234567890"},
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 {
str string
expect map[string]interface{}
err bool
}{
{
"name1=null,f=false,t=true",
map[string]interface{}{"name1": nil, "f": false, "t": true},
false,
},
{
"name1=value1",
map[string]interface{}{"name1": "value1"},
@ -108,10 +128,23 @@ func TestParseSet(t *testing.T) {
str: "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",
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=",
err: true,
@ -270,6 +303,30 @@ func TestParseSet(t *testing.T) {
str: "nested[1][1]=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 {
@ -331,12 +388,13 @@ func TestParseInto(t *testing.T) {
"inner2": "value2",
},
}
input := "outer.inner1=value1,outer.inner3=value3"
input := "outer.inner1=value1,outer.inner3=value3,outer.inner4=4"
expect := map[string]interface{}{
"outer": map[string]interface{}{
"inner1": "value1",
"inner2": "value2",
"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)
}
}
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) {
got := map[string]interface{}{}
@ -367,7 +458,7 @@ func TestParseIntoFile(t *testing.T) {
rs2v := func(rs []rune) (interface{}, error) {
v := string(rs)
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 "value1", nil

@ -78,13 +78,16 @@ verifySupported() {
# checkDesiredVersion checks if the desired version is available.
checkDesiredVersion() {
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
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
# 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
@ -187,7 +190,7 @@ testVersion() {
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 defaults to latest"
echo -e "\t[--version|-v <desired_version>]"
echo -e "\te.g. --version v2.4.0 or -v latest"
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