diff --git a/_proto/hapi/release/hook.proto b/_proto/hapi/release/hook.proto index 413dcfb08..2237883ca 100644 --- a/_proto/hapi/release/hook.proto +++ b/_proto/hapi/release/hook.proto @@ -46,4 +46,6 @@ message Hook { repeated Event events = 5; // LastRun indicates the date/time this was last run. google.protobuf.Timestamp last_run = 6; + // Weight indicates the sort order for execution among similar Hook type + int32 weight = 7; } diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index f47a47f14..f16d68238 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -203,6 +203,9 @@ message UpdateReleaseRequest { // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state // before marking the release as successful. It will wait for as long as timeout bool wait = 9; + // ReuseValues will cause Tiller to reuse the values from the last release. + // This is ignored if reset_values is set. + bool reuse_values = 10; } // UpdateReleaseResponse is the response to an update request. diff --git a/cmd/helm/dependency.go b/cmd/helm/dependency.go index 44cb29e4d..9c51be600 100644 --- a/cmd/helm/dependency.go +++ b/cmd/helm/dependency.go @@ -47,10 +47,10 @@ For example, this requirements file declares two dependencies: dependencies: - name: nginx version: "1.2.3" - repository: "https://example.com/charts" + repository: "https://example.com/charts" - name: memcached version: "3.2.1" - repository: "https://another.example.com/charts" + repository: "https://another.example.com/charts" The 'name' should be the name of a chart, where that name must match the name in that chart's 'Chart.yaml' file. diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 64200adaf..9ade3e344 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -45,6 +45,14 @@ const ( tillerNamespaceEnvVar = "TILLER_NAMESPACE" ) +var ( + tlsCaCertFile string // path to TLS CA certificate file + tlsCertFile string // path to TLS certificate file + tlsKeyFile string // path to TLS key file + tlsVerify bool // enable TLS and verify remote certificates + tlsEnable bool // enable TLS +) + var ( helmHome string tillerHost string diff --git a/cmd/helm/init.go b/cmd/helm/init.go index f855abe9a..eb3eea207 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -62,15 +62,17 @@ const ( ) type initCmd struct { - image string - clientOnly bool - canary bool - upgrade bool - namespace string - dryRun bool - out io.Writer - home helmpath.Home - kubeClient internalclientset.Interface + image string + clientOnly bool + canary bool + upgrade bool + namespace string + dryRun bool + skipRefresh bool + out io.Writer + home helmpath.Home + opts installer.Options + kubeClient internalclientset.Interface } func newInitCmd(out io.Writer) *cobra.Command { @@ -98,26 +100,106 @@ func newInitCmd(out io.Writer) *cobra.Command { f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if tiller is already installed") f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install tiller") f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") + f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") + + // f.BoolVar(&tlsEnable, "tiller-tls", false, "install tiller with TLS enabled") + // f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install tiller with TLS enabled and to verify remote certificates") + // f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with tiller") + // f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with tiller") + // f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate") return cmd } +// tlsOptions sanitizes the tls flags as well as checks for the existence of required +// tls files indicated by those flags, if any. +func (i *initCmd) tlsOptions() error { + i.opts.EnableTLS = tlsEnable || tlsVerify + i.opts.VerifyTLS = tlsVerify + + if i.opts.EnableTLS { + missing := func(file string) bool { + _, err := os.Stat(file) + return os.IsNotExist(err) + } + if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { + return errors.New("missing required TLS key file") + } + if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { + return errors.New("missing required TLS certificate file") + } + if i.opts.VerifyTLS { + if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { + return errors.New("missing required TLS CA file") + } + } + } + return nil +} + // runInit initializes local config and installs tiller to Kubernetes Cluster func (i *initCmd) run() error { + if err := i.tlsOptions(); err != nil { + return err + } + i.opts.Namespace = i.namespace + i.opts.UseCanary = i.canary + i.opts.ImageSpec = i.image + if flagDebug { - dm, err := installer.DeploymentManifest(i.namespace, i.image, i.canary) - if err != nil { + writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { + w := i.out + if !first { + // YAML starting document boundary marker + if _, err := fmt.Fprintln(w, "---"); err != nil { + return err + } + } + if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil { + return err + } + if _, err := fmt.Fprintln(w, "kind:", kind); err != nil { + return err + } + if _, err := fmt.Fprint(w, body); err != nil { + return err + } + if !last { + return nil + } + // YAML ending document boundary marker + _, err := fmt.Fprintln(w, "...") return err } - fm := fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", dm) - fmt.Fprintln(i.out, fm) - sm, err := installer.ServiceManifest(i.namespace) - if err != nil { + var body string + var err error + + // write Deployment manifest + if body, err = installer.DeploymentManifest(&i.opts); err != nil { + return err + } + if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { + return err + } + + // write Service manifest + if body, err = installer.ServiceManifest(i.namespace); err != nil { + return err + } + if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil { return err } - fm = fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", sm) - fmt.Fprintln(i.out, fm) + + // write Secret manifest + if i.opts.EnableTLS { + if body, err = installer.SecretManifest(&i.opts); err != nil { + return err + } + if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil { + return err + } + } } if i.dryRun { @@ -127,7 +209,7 @@ func (i *initCmd) run() error { if err := ensureDirectories(i.home, i.out); err != nil { return err } - if err := ensureDefaultRepos(i.home, i.out); err != nil { + if err := ensureDefaultRepos(i.home, i.out, i.skipRefresh); err != nil { return err } if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil { @@ -143,13 +225,12 @@ func (i *initCmd) run() error { } i.kubeClient = c } - opts := &installer.Options{Namespace: i.namespace, ImageSpec: i.image, UseCanary: i.canary} - if err := installer.Install(i.kubeClient, opts); err != nil { + if err := installer.Install(i.kubeClient, &i.opts); err != nil { if !kerrors.IsAlreadyExists(err) { return fmt.Errorf("error installing: %s", err) } if i.upgrade { - if err := installer.Upgrade(i.kubeClient, opts); err != nil { + if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { return fmt.Errorf("error when upgrading: %s", err) } fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.") @@ -194,12 +275,12 @@ func ensureDirectories(home helmpath.Home, out io.Writer) error { return nil } -func ensureDefaultRepos(home helmpath.Home, out io.Writer) error { +func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error { repoFile := home.RepositoryFile() if fi, err := os.Stat(repoFile); err != nil { fmt.Fprintf(out, "Creating %s \n", repoFile) f := repo.NewRepoFile() - sr, err := initStableRepo(home.CacheIndex(stableRepository)) + sr, err := initStableRepo(home.CacheIndex(stableRepository), skipRefresh) if err != nil { return err } @@ -218,7 +299,7 @@ func ensureDefaultRepos(home helmpath.Home, out io.Writer) error { return nil } -func initStableRepo(cacheFile string) (*repo.Entry, error) { +func initStableRepo(cacheFile string, skipRefresh bool) (*repo.Entry, error) { c := repo.Entry{ Name: stableRepository, URL: stableRepositoryURL, @@ -229,6 +310,10 @@ func initStableRepo(cacheFile string) (*repo.Entry, error) { return nil, err } + if skipRefresh { + return &c, nil + } + // In this case, the cacheFile is always absolute. So passing empty string // is safe. if err := r.DownloadIndexFile(""); err != nil { diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 00c754cfa..29861c25a 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -156,13 +156,19 @@ func TestInitCmd_dryRun(t *testing.T) { if err := cmd.run(); err != nil { t.Fatal(err) } - if len(fc.Actions()) != 0 { - t.Error("expected no server calls") + if got := len(fc.Actions()); got != 0 { + t.Errorf("expected no server calls, got %d", got) } - var y map[string]interface{} - if err := yaml.Unmarshal(buf.Bytes(), &y); err != nil { - t.Errorf("Expected parseable YAML, got %q\n\t%s", buf.String(), err) + docs := bytes.Split(buf.Bytes(), []byte("\n---")) + if got, want := len(docs), 2; got != want { + t.Fatalf("Expected document count of %d, got %d", want, got) + } + for _, doc := range docs { + var y map[string]interface{} + if err := yaml.Unmarshal(doc, &y); err != nil { + t.Errorf("Expected parseable YAML, got %q\n\t%s", doc, err) + } } } @@ -179,7 +185,10 @@ func TestEnsureHome(t *testing.T) { if err := ensureDirectories(hh, b); err != nil { t.Error(err) } - if err := ensureDefaultRepos(hh, b); err != nil { + if err := ensureDefaultRepos(hh, b, false); err != nil { + t.Error(err) + } + if err := ensureDefaultRepos(hh, b, true); err != nil { t.Error(err) } if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil { diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index 2ca06a9c1..338e31eca 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -17,7 +17,7 @@ limitations under the License. package installer // import "k8s.io/helm/cmd/helm/installer" import ( - "fmt" + "io/ioutil" "github.com/ghodss/yaml" @@ -28,22 +28,23 @@ import ( "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" "k8s.io/kubernetes/pkg/util/intstr" - - "k8s.io/helm/pkg/version" ) -const defaultImage = "gcr.io/kubernetes-helm/tiller" - // Install uses kubernetes client to install tiller. // // Returns an error if the command failed. func Install(client internalclientset.Interface, opts *Options) error { - if err := createDeployment(client.Extensions(), opts.Namespace, opts.ImageSpec, opts.UseCanary); err != nil { + if err := createDeployment(client.Extensions(), opts); err != nil { return err } if err := createService(client.Core(), opts.Namespace); err != nil { return err } + if opts.tls() { + if err := createSecret(client.Core(), opts); err != nil { + return err + } + } return nil } @@ -55,7 +56,7 @@ func Upgrade(client internalclientset.Interface, opts *Options) error { if err != nil { return err } - obj.Spec.Template.Spec.Containers[0].Image = selectImage(opts.ImageSpec, opts.UseCanary) + obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage() if _, err := client.Extensions().Deployments(opts.Namespace).Update(obj); err != nil { return err } @@ -73,15 +74,15 @@ func Upgrade(client internalclientset.Interface, opts *Options) error { } // createDeployment creates the Tiller deployment reource -func createDeployment(client extensionsclient.DeploymentsGetter, namespace, image string, canary bool) error { - obj := deployment(namespace, image, canary) +func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { + obj := deployment(opts) _, err := client.Deployments(obj.Namespace).Create(obj) return err } // deployment gets the deployment object that installs Tiller. -func deployment(namespace, image string, canary bool) *extensions.Deployment { - return generateDeployment(namespace, selectImage(image, canary)) +func deployment(opts *Options) *extensions.Deployment { + return generateDeployment(opts) } // createService creates the Tiller service resource @@ -96,21 +97,10 @@ func service(namespace string) *api.Service { return generateService(namespace) } -func selectImage(image string, canary bool) string { - switch { - case canary: - image = defaultImage + ":canary" - case image == "": - image = fmt.Sprintf("%s:%s", defaultImage, version.Version) - } - return image -} - // DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment // resource. -func DeploymentManifest(namespace, image string, canary bool) (string, error) { - obj := deployment(namespace, image, canary) - +func DeploymentManifest(opts *Options) (string, error) { + obj := deployment(opts) buf, err := yaml.Marshal(obj) return string(buf), err } @@ -129,11 +119,11 @@ func generateLabels(labels map[string]string) map[string]string { return labels } -func generateDeployment(namespace, image string) *extensions.Deployment { +func generateDeployment(opts *Options) *extensions.Deployment { labels := generateLabels(map[string]string{"name": "tiller"}) d := &extensions.Deployment{ ObjectMeta: api.ObjectMeta{ - Namespace: namespace, + Namespace: opts.Namespace, Name: "tiller-deploy", Labels: labels, }, @@ -147,13 +137,13 @@ func generateDeployment(namespace, image string) *extensions.Deployment { Containers: []api.Container{ { Name: "tiller", - Image: image, + Image: opts.selectImage(), ImagePullPolicy: "IfNotPresent", Ports: []api.ContainerPort{ {ContainerPort: 44134, Name: "tiller"}, }, Env: []api.EnvVar{ - {Name: "TILLER_NAMESPACE", Value: namespace}, + {Name: "TILLER_NAMESPACE", Value: opts.Namespace}, }, LivenessProbe: &api.Probe{ Handler: api.Handler{ @@ -181,6 +171,37 @@ func generateDeployment(namespace, image string) *extensions.Deployment { }, }, } + + if opts.tls() { + const certsDir = "/etc/certs" + + var tlsVerify, tlsEnable = "", "1" + if opts.VerifyTLS { + tlsVerify = "1" + } + + // Mount secret to "/etc/certs" + d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, api.VolumeMount{ + Name: "tiller-certs", + ReadOnly: true, + MountPath: certsDir, + }) + // Add environment variable required for enabling TLS + d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []api.EnvVar{ + {Name: "TILLER_TLS_VERIFY", Value: tlsVerify}, + {Name: "TILLER_TLS_ENABLE", Value: tlsEnable}, + {Name: "TILLER_TLS_CERTS", Value: certsDir}, + }...) + // Add secret volume to deployment + d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, api.Volume{ + Name: "tiller-certs", + VolumeSource: api.VolumeSource{ + Secret: &api.SecretVolumeSource{ + SecretName: "tiller-secret", + }, + }, + }) + } return d } @@ -206,3 +227,54 @@ func generateService(namespace string) *api.Service { } return s } + +// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource. +func SecretManifest(opts *Options) (string, error) { + o, err := generateSecret(opts) + if err != nil { + return "", err + } + buf, err := yaml.Marshal(o) + return string(buf), err +} + +// createSecret creates the Tiller secret resource. +func createSecret(client internalversion.SecretsGetter, opts *Options) error { + o, err := generateSecret(opts) + if err != nil { + return err + } + _, err = client.Secrets(o.Namespace).Create(o) + return err +} + +// generateSecret builds the secret object that hold Tiller secrets. +func generateSecret(opts *Options) (*api.Secret, error) { + const secretName = "tiller-secret" + + labels := generateLabels(map[string]string{"name": "tiller"}) + secret := &api.Secret{ + Type: api.SecretTypeOpaque, + Data: make(map[string][]byte), + ObjectMeta: api.ObjectMeta{ + Name: secretName, + Labels: labels, + Namespace: opts.Namespace, + }, + } + var err error + if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil { + return nil, err + } + if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil { + return nil, err + } + if opts.VerifyTLS { + if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil { + return nil, err + } + } + return secret, nil +} + +func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) } diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index daa09b0d8..4d9a2cbca 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -45,7 +45,7 @@ func TestDeploymentManifest(t *testing.T) { } for _, tt := range tests { - o, err := DeploymentManifest(api.NamespaceDefault, tt.image, tt.canary) + o, err := DeploymentManifest(&Options{Namespace: api.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary}) if err != nil { t.Fatalf("%s: error %q", tt.name, err) } @@ -146,7 +146,11 @@ func TestInstall_canary(t *testing.T) { func TestUpgrade(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" - existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) + existingDeployment := deployment(&Options{ + Namespace: api.NamespaceDefault, + ImageSpec: "imageToReplace", + UseCanary: false, + }) existingService := service(api.NamespaceDefault) fc := &fake.Clientset{} @@ -178,7 +182,11 @@ func TestUpgrade(t *testing.T) { func TestUpgrade_serviceNotFound(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" - existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) + existingDeployment := deployment(&Options{ + Namespace: api.NamespaceDefault, + ImageSpec: "imageToReplace", + UseCanary: false, + }) fc := &fake.Clientset{} fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { diff --git a/cmd/helm/installer/options.go b/cmd/helm/installer/options.go index 909b7e9e4..41f8aaf2a 100644 --- a/cmd/helm/installer/options.go +++ b/cmd/helm/installer/options.go @@ -16,6 +16,13 @@ limitations under the License. package installer // import "k8s.io/helm/cmd/helm/installer" +import ( + "fmt" + "k8s.io/helm/pkg/version" +) + +const defaultImage = "gcr.io/kubernetes-helm/tiller" + // Options control how to install tiller into a cluster, upgrade, and uninstall tiller from a cluster. type Options struct { // EnableTLS instructs tiller to serve with TLS enabled. @@ -43,7 +50,7 @@ type Options struct { // key tiller should use. // // Required and valid if and only if EnableTLS or VerifyTLS is set. - TLSKey string + TLSKeyFile string // TLSCertFile identifies the file containing the pem encoded TLS // certificate tiller should use. @@ -57,3 +64,16 @@ type Options struct { // Required and valid if and only if VerifyTLS is set. TLSCaCertFile string } + +func (opts *Options) selectImage() string { + switch { + case opts.UseCanary: + return defaultImage + ":canary" + case opts.ImageSpec == "": + return fmt.Sprintf("%s:%s", defaultImage, version.Version) + default: + return opts.ImageSpec + } +} + +func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS } diff --git a/cmd/helm/installer/uninstall_test.go b/cmd/helm/installer/uninstall_test.go index e0db07fe6..bda91ca1c 100644 --- a/cmd/helm/installer/uninstall_test.go +++ b/cmd/helm/installer/uninstall_test.go @@ -55,7 +55,11 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e func TestUninstall(t *testing.T) { existingService := service(api.NamespaceDefault) - existingDeployment := deployment(api.NamespaceDefault, "image", false) + existingDeployment := deployment(&Options{ + Namespace: api.NamespaceDefault, + ImageSpec: "image", + UseCanary: false, + }) fc := &fake.Clientset{} fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { @@ -92,7 +96,7 @@ func TestUninstall(t *testing.T) { } func TestUninstall_serviceNotFound(t *testing.T) { - existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) + existingDeployment := deployment(&Options{Namespace: api.NamespaceDefault, ImageSpec: "imageToReplace", UseCanary: false}) fc := &fake.Clientset{} fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { diff --git a/cmd/helm/package.go b/cmd/helm/package.go index ac8ff9d8e..100a4663d 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -48,14 +48,16 @@ Versioned chart archives are used by Helm package repositories. ` type packageCmd struct { - save bool - sign bool - path string - key string - keyring string - version string - out io.Writer - home helmpath.Home + save bool + sign bool + path string + key string + keyring string + version string + destination string + + out io.Writer + home helmpath.Home } func newPackageCmd(out io.Writer) *cobra.Command { @@ -96,6 +98,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true") f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "location of a public keyring") f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version") + f.StringVarP(&pkg.destination, "destination", "d", ".", "location to write the chart.") return cmd } @@ -129,12 +132,19 @@ func (p *packageCmd) run(cmd *cobra.Command, args []string) error { checkDependencies(ch, reqs, p.out) } - // Save to the current working directory. - cwd, err := os.Getwd() - if err != nil { - return err + var dest string + if p.destination == "." { + // Save to the current working directory. + dest, err = os.Getwd() + if err != nil { + return err + } + } else { + // Otherwise save to set destination + dest = p.destination } - name, err := chartutil.Save(ch, cwd) + + name, err := chartutil.Save(ch, dest) if err == nil && flagDebug { fmt.Fprintf(p.out, "Saved %s to current directory\n", name) } diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index a9dc021a3..340da0fdb 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -94,6 +94,20 @@ func TestPackage(t *testing.T) { expect: "", hasfile: "alpine-0.1.0.tgz", }, + { + name: "package --destination toot", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"destination": "toot"}, + expect: "", + hasfile: "toot/alpine-0.1.0.tgz", + }, + { + name: "package --destination does-not-exist", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"destination": "does-not-exist"}, + expect: "stat does-not-exist: no such file or directory", + err: true, + }, { name: "package --sign --key=KEY --keyring=KEYRING testdata/testcharts/alpine", args: []string{"testdata/testcharts/alpine"}, @@ -124,6 +138,10 @@ func TestPackage(t *testing.T) { t.Fatal(err) } + if err := os.Mkdir("toot", 0777); err != nil { + t.Fatal(err) + } + ensureTestHome(helmpath.Home(tmp), t) oldhome := homePath() helmHome = tmp diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index cf8b3f33f..dbece6dc2 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -72,6 +72,7 @@ type upgradeCmd struct { version string timeout int64 resetValues bool + reuseValues bool wait bool } @@ -114,6 +115,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") + f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values, and merge in any new values. If '--reset-values' is specified, this is ignored.") f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.MarkDeprecated("disable-hooks", "use --no-hooks instead") @@ -177,6 +179,7 @@ func (u *upgradeCmd) run() error { helm.UpgradeDisableHooks(u.disableHooks), helm.UpgradeTimeout(u.timeout), helm.ResetValues(u.resetValues), + helm.ReuseValues(u.reuseValues), helm.UpgradeWait(u.wait)) if err != nil { return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err)) diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 2ae4abb25..a913016e2 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -109,6 +109,13 @@ func TestUpgradeCmd(t *testing.T) { resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 4, chart: ch2}), expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", }, + { + name: "upgrade a release with --reuse-values", + args: []string{"funny-bunny", chartPath}, + flags: []string{"--reuse-values", "true"}, + resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 5, chart: ch2}), + expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + }, { name: "install a release with 'upgrade --install'", args: []string{"zany-bunny", chartPath}, diff --git a/docs/chart_best_practices/requirements.md b/docs/chart_best_practices/requirements.md index ce99649f9..0bbdfdfd7 100644 --- a/docs/chart_best_practices/requirements.md +++ b/docs/chart_best_practices/requirements.md @@ -16,6 +16,8 @@ This will match version 1.2.0 and any patches to that release (1.2.1, 1.2.999, a Where possible, use `https://` repository URLs, followed by `http://` URLs. +If the repository has been added to the repository index file, the repository name can be used as an alias of URL. Use `alias:` or `@` followed by repository names. + File URLs (`file://...`) are considered a "special case" for charts that are assembled by a fixed deployment pipeline. Charts that use `file://` in a `requirements.yaml` file are not allowed in the official Helm repository. ## Conditions and Tags diff --git a/docs/chart_template_guide/subcharts_and_globals.md b/docs/chart_template_guide/subcharts_and_globals.md index 059b0b601..69d828c11 100644 --- a/docs/chart_template_guide/subcharts_and_globals.md +++ b/docs/chart_template_guide/subcharts_and_globals.md @@ -137,7 +137,7 @@ data: salad: {{ .Values.global.salad }} ``` -`mysubchart/tempaltes/configmap.yaml`: +`mysubchart/templates/configmap.yaml`: ```yaml apiVersion: v1 diff --git a/docs/charts.md b/docs/charts.md index b7d97cc0b..414f80bf7 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -302,6 +302,107 @@ helm install --set tags.front-end=true --set subchart2.enabled=false * The `tags:` key in values must be a top level key. Globals and nested `tags:` tables are not currently supported. +#### Importing Child Values via requirements.yaml + +In some cases it is desirable to allow a child chart's values to propagate to the parent chart and be +shared as common defaults. An additional benefit of using the `exports` format is that it will enable future +tooling to introspect user-settable values. + +The keys containing the values to be imported can be specified in the parent chart's `requirements.yaml` file +using a YAML list. Each item in the list is a key which is imported from the child chart's `exports` field. + +To import values not contained in the `exports` key, use the [child/parent](#using-the-child/parent-format) format. +Examples of both formats are described below. + +##### Using the exports format + +If a child chart's `values.yaml` file contains an `exports` field at the root, its contents may be imported +directly into the parent's values by specifying the keys to import as in the example below: + +```yaml +# parent's requirements.yaml file + ... + import-values: + - data +``` +```yaml +# child's values.yaml file +... +exports: + data: + myint: 99 +``` + +Since we are specifying the key `data` in our import list, Helm looks in the the `exports` field of the child +chart for `data` key and imports its contents. + +The final parent values would contain our exported field: + +```yaml +# parent's values file +... +myint: 99 + +``` + +Please note the parent key `data` is not contained in the parent's final values. If you need to specify the +parent key, use the 'child/parent' format. + +##### Using the child/parent format + +To access values that are not contained in the `exports` key of the child chart's values, you will need to +specify the source key of the values to be imported (`child`) and the destination path in the parent chart's +values (`parent`). + +The `import-values` in the example below instructs Helm to take any values found at `child:` path and copy them +to the parent's values at the path specified in `parent:` + +```yaml +# parent's requirements.yaml file +dependencies: + - name: subchart1 + repository: http://localhost:10191 + version: 0.1.0 + ... + import-values: + - child: default.data + parent: myimports +``` +In the above example, values found at `default.data` in the subchart1's values will be imported +to the `myimports` key in the parent chart's values as detailed below: + +```yaml +# parent's values.yaml file + +myimports: + myint: 0 + mybool: false + mystring: "helm rocks!" + +``` +```yaml +# subchart1's values.yaml file + +default: + data: + myint: 999 + mybool: true + +``` +The parent chart's resulting values would be: + +```yaml +# parent's final values + +myimports: + myint: 999 + mybool: true + mystring: "helm rocks!" + +``` + +The parent's final values now contains the `myint` and `mybool` fields imported from subchart1. + ## Templates and Values Helm Chart templates are written in the diff --git a/docs/charts_hooks.md b/docs/charts_hooks.md index 56cecd788..a5babc481 100644 --- a/docs/charts_hooks.md +++ b/docs/charts_hooks.md @@ -58,16 +58,18 @@ hooks, the lifecycle is altered like this: 1. User runs `helm install foo` 2. Chart is loaded into Tiller 3. After some verification, Tiller renders the `foo` templates -4. Tiller executes the `pre-install` hook (loading hook resources into +4. Tiller prepares to execute the `pre-install` hooks (loading hook resources into Kubernetes) -5. Tiller waits until the hook is "Ready" -6. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait` +5. Tiller sorts hooks by weight (assigning a weight of 0 by default) and by name for those hooks with the same weight in ascending order. +6. Tiller then loads the hook with the lowest weight first (negative to positive) +7. Tiller waits until the hook is "Ready" +8. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait` flag is set, Tiller will wait until all resources are in a ready state and will not run the `post-install` hook until they are ready. -7. Tiller executes the `post-install` hook (loading hook resources) -8. Tiller waits until the hook is "Ready" -9. Tiller returns the release name (and other data) to the client -10. The client exits +9. Tiller executes the `post-install` hook (loading hook resources) +10. Tiller waits until the hook is "Ready" +11. Tiller returns the release name (and other data) to the client +12. The client exits What does it mean to wait until a hook is ready? This depends on the resource declared in the hook. If the resources is a `Job` kind, Tiller @@ -114,6 +116,7 @@ metadata: # This is what defines this resource as a hook. Without this line, the # job is considered part of the release. "helm.sh/hook": post-install + "helm.sh/hook-weight": "-5" spec: template: metadata: @@ -154,3 +157,12 @@ When subcharts declare hooks, those are also evaluated. There is no way for a top-level chart to disable the hooks declared by subcharts. And again, there is no guaranteed ordering. +It is also possible to define a weight for a hook which will help build a deterministic executing order. Weights are defined using the following annotation: + +``` + annotations: + "helm.sh/hook-weight": "5" +``` + +Hook weights can be positive or negative numbers but must be represented as strings. When Tiller starts the execution cycle of hooks of a particular Kind it will sort those hooks in ascending order. + diff --git a/docs/helm/helm_dependency.md b/docs/helm/helm_dependency.md index 08cf5d7c2..c2a46e39e 100644 --- a/docs/helm/helm_dependency.md +++ b/docs/helm/helm_dependency.md @@ -36,8 +36,20 @@ The 'version' field should contain a semantic version or version range. The 'repository' URL should point to a Chart Repository. Helm expects that by appending '/index.yaml' to the URL, it should be able to retrieve the chart -repository's index. Note: 'repository' cannot be a repository alias. It must be -a URL. +repository's index. + +A repository can also be represented by a repository name defined in the index file +in lieu of a repository URL. If a repository alias is used, it is expected to start with +'alias:' or '@', followed by a repository name. For example, + # requirements.yaml + dependencies: + - name: nginx + version: "1.2.3" + repository: "alias:stable" + +Note: In the above example, if the '@' syntax is used, the repository alias '@stable' +must be quoted, as YAML requires to use quotes if the value includes a special character +like '@'. Starting from 2.2.0, repository can be defined as the path to the directory of the dependency charts stored locally. The path should start with a prefix of @@ -53,7 +65,6 @@ If the dependency chart is retrieved locally, it is not required to have the repository added to helm by "helm add repo". Version matching is also supported for this case. - ### Options inherited from parent commands ``` diff --git a/docs/related.md b/docs/related.md index e2955498b..c2540b5f4 100644 --- a/docs/related.md +++ b/docs/related.md @@ -8,17 +8,20 @@ or [pull request](https://github.com/kubernetes/helm/pulls). ## Article, Blogs, How-Tos, and Extra Documentation - [Using Helm to Deploy to Kubernetes](https://daemonza.github.io/2017/02/20/using-helm-to-deploy-to-kubernetes/) - [Honestbee's Helm Chart Conventions](https://gist.github.com/so0k/f927a4b60003cedd101a0911757c605a) +- [Deploying Kubernetes Applications with Helm](http://cloudacademy.com/blog/deploying-kubernetes-applications-with-helm/) - [Releasing backward-incompatible changes: Kubernetes, Jenkins, Prometheus Operator, Helm and Traefik](https://medium.com/@enxebre/releasing-backward-incompatible-changes-kubernetes-jenkins-plugin-prometheus-operator-helm-self-6263ca61a1b1#.e0c7elxhq) - [CI/CD with Kubernetes, Helm & Wercker ](http://www.slideshare.net/Diacode/cicd-with-kubernetes-helm-wercker-madscalability) - [The missing CI/CD Kubernetes component: Helm package manager](https://hackernoon.com/the-missing-ci-cd-kubernetes-component-helm-package-manager-1fe002aac680#.691sk2zhu) -- [CI/CD with Jenkins, Kubernetes, and Helm](https://www.youtube.com/watch?v=NVoln4HdZOY) - [The Workflow "Umbrella" Helm Chart](https://deis.com/blog/2017/workflow-chart-assembly) - [GitLab, Consumer Driven Contracts, Helm and Kubernetes](https://medium.com/@enxebre/gitlab-consumer-driven-contracts-helm-and-kubernetes-b7235a60a1cb#.xwp1y4tgi) - [Writing a Helm Chart](https://www.influxdata.com/packaged-kubernetes-deployments-writing-helm-chart/) +- [Creating a Helm Plugin in 3 Steps](http://technosophos.com/2017/03/21/creating-a-helm-plugin.html) -## Videos +## Video, Audio, and Podcast +- [CI/CD with Jenkins, Kubernetes, and Helm](https://www.youtube.com/watch?v=NVoln4HdZOY): AKA "The Infamous Croc Hunter Video". - [KubeCon2016: Delivering Kubernetes-Native Applications by Michelle Noorali](https://www.youtube.com/watch?v=zBc1goRfk3k&index=49&list=PLj6h78yzYM2PqgIGU1Qmi8nY7dqn9PCr4) +- [Helm with Michelle Noorali and Matthew Butcher](https://gcppodcast.com/post/episode-50-helm-with-michelle-noorali-and-matthew-butcher/): The official Google CloudPlatform Podcast interviews Michelle and Matt about Helm. ## Helm Plugins @@ -32,6 +35,7 @@ or [pull request](https://github.com/kubernetes/helm/pulls). Tools layered on top of Helm or Tiller. +- [Quay App Registry](https://coreos.com/blog/quay-application-registry-for-kubernetes.html) - Open Kubernetes application registry, including a Helm access client - [Chartify](https://github.com/appscode/chartify) - Generate Helm charts from existing Kubernetes resources. - [VIM-Kubernetes](https://github.com/andrewstuart/vim-kubernetes) - VIM plugin for Kubernetes and Helm - [Landscaper](https://github.com/Eneco/landscaper/) - "Landscaper takes a set of Helm Chart references with values (a desired state), and realizes this in a Kubernetes cluster." diff --git a/glide.lock b/glide.lock index 0fa856f87..3ea2cc617 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 0d1c5b7304a853820dcaa296d3aa1f5f3466a8491dcef80cbcaf43c954acb2a8 -updated: 2017-03-15T15:56:18.814305691-06:00 +hash: df0fa621e6a6f80dbfeb815d9d8aa308c50346a9821e401b19b6f10782da3774 +updated: 2017-04-03T17:00:07.670429885-06:00 imports: - name: cloud.google.com/go version: 3b1ae45394a234c385be014e9a488f2bb6eef821 @@ -182,7 +182,7 @@ imports: - jlexer - jwriter - name: github.com/Masterminds/semver - version: 59c29afe1a994eacb71c833025ca7acf874bb1da + version: 3f0ab6d4ab4bed1c61caf056b63a6e62190c7801 - name: github.com/Masterminds/sprig version: 23597e5f6ad0e4d590e71314bfd0251a4a3cf849 - name: github.com/mattn/go-runewidth @@ -300,7 +300,7 @@ imports: - name: gopkg.in/yaml.v2 version: a83829b6f1293c91addabc89d0571c246397bbf4 - name: k8s.io/kubernetes - version: 00a1fb254bd8e5235575fba1398b958943e39078 + version: ea8f6637b639246faa14a8d5c6f864100fcb77a9 subpackages: - cmd/kubeadm/app/apis/kubeadm - cmd/kubeadm/app/apis/kubeadm/install diff --git a/glide.yaml b/glide.yaml index f8a7af4f4..a703bff5b 100644 --- a/glide.yaml +++ b/glide.yaml @@ -12,7 +12,7 @@ import: version: ^2.10 - package: github.com/ghodss/yaml - package: github.com/Masterminds/semver - version: ~1.2.2 + version: ~1.2.3 - package: github.com/technosophos/moniker - package: github.com/golang/protobuf version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e diff --git a/pkg/chartutil/requirements.go b/pkg/chartutil/requirements.go index 3a31042d6..53e28e788 100644 --- a/pkg/chartutil/requirements.go +++ b/pkg/chartutil/requirements.go @@ -62,6 +62,9 @@ type Dependency struct { Tags []string `json:"tags"` // Enabled bool determines if chart should be loaded Enabled bool `json:"enabled"` + // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a + // string or pair of child/parent sublist items. + ImportValues []interface{} `json:"import-values"` } // ErrNoRequirementsFile to detect error condition @@ -266,3 +269,128 @@ func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error { return nil } + +// pathToMap creates a nested map given a YAML path in dot notation. +func pathToMap(path string, data map[string]interface{}) map[string]interface{} { + if path == "." { + return data + } + ap := strings.Split(path, ".") + if len(ap) == 0 { + return nil + } + n := []map[string]interface{}{} + // created nested map for each key, adding to slice + for _, v := range ap { + nm := make(map[string]interface{}) + nm[v] = make(map[string]interface{}) + n = append(n, nm) + } + // find the last key (map) and set our data + for i, d := range n { + for k := range d { + z := i + 1 + if z == len(n) { + n[i][k] = data + break + } + n[i][k] = n[z] + } + } + + return n[0] +} + +// getParents returns a slice of parent charts in reverse order. +func getParents(c *chart.Chart, out []*chart.Chart) []*chart.Chart { + if len(out) == 0 { + out = []*chart.Chart{c} + } + for _, ch := range c.Dependencies { + if len(ch.Dependencies) > 0 { + out = append(out, ch) + out = getParents(ch, out) + } + } + + return out +} + +// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. +func processImportValues(c *chart.Chart, v *chart.Config) error { + reqs, err := LoadRequirements(c) + if err != nil { + return err + } + // combine chart values and its dependencies' values + cvals, err := CoalesceValues(c, v) + if err != nil { + return err + } + nv := v.GetValues() + b := make(map[string]interface{}, len(nv)) + // convert values to map + for kk, vvv := range nv { + b[kk] = vvv + } + // import values from each dependency if specified in import-values + for _, r := range reqs.Dependencies { + if len(r.ImportValues) > 0 { + var outiv []interface{} + for _, riv := range r.ImportValues { + switch iv := riv.(type) { + case map[string]interface{}: + nm := map[string]string{ + "child": iv["child"].(string), + "parent": iv["parent"].(string), + } + outiv = append(outiv, nm) + s := r.Name + "." + nm["child"] + // get child table + vv, err := cvals.Table(s) + if err != nil { + log.Printf("Warning: ImportValues missing table: %v", err) + continue + } + // create value map from child to be merged into parent + vm := pathToMap(nm["parent"], vv.AsMap()) + b = coalesceTables(cvals, vm) + case string: + nm := map[string]string{ + "child": "exports." + iv, + "parent": ".", + } + outiv = append(outiv, nm) + s := r.Name + "." + nm["child"] + vm, err := cvals.Table(s) + if err != nil { + log.Printf("Warning: ImportValues missing table: %v", err) + continue + } + b = coalesceTables(b, vm.AsMap()) + } + } + // set our formatted import values + r.ImportValues = outiv + } + } + b = coalesceTables(b, cvals) + y, err := yaml.Marshal(b) + if err != nil { + return err + } + // set the new values + c.Values.Raw = string(y) + + return nil +} + +// ProcessRequirementsImportValues imports specified chart values from child to parent. +func ProcessRequirementsImportValues(c *chart.Chart, v *chart.Config) error { + pc := getParents(c, nil) + for i := len(pc) - 1; i >= 0; i-- { + processImportValues(pc[i], v) + } + + return nil +} diff --git a/pkg/chartutil/requirements_test.go b/pkg/chartutil/requirements_test.go index b9a5ae12a..c92c9f052 100644 --- a/pkg/chartutil/requirements_test.go +++ b/pkg/chartutil/requirements_test.go @@ -18,6 +18,8 @@ import ( "sort" "testing" + "strconv" + "k8s.io/helm/pkg/proto/hapi/chart" ) @@ -206,3 +208,114 @@ func extractCharts(c *chart.Chart, out []*chart.Chart) []*chart.Chart { } return out } +func TestProcessRequirementsImportValues(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + v := &chart.Config{Raw: ""} + + e := make(map[string]string) + + e["imported-chart1.SC1bool"] = "true" + e["imported-chart1.SC1float"] = "3.14" + e["imported-chart1.SC1int"] = "100" + e["imported-chart1.SC1string"] = "dollywood" + e["imported-chart1.SC1extra1"] = "11" + e["imported-chart1.SPextra1"] = "helm rocks" + e["imported-chart1.SC1extra1"] = "11" + + e["imported-chartA.SCAbool"] = "false" + e["imported-chartA.SCAfloat"] = "3.1" + e["imported-chartA.SCAint"] = "55" + e["imported-chartA.SCAstring"] = "jabba" + e["imported-chartA.SPextra3"] = "1.337" + e["imported-chartA.SC1extra2"] = "1.337" + e["imported-chartA.SCAnested1.SCAnested2"] = "true" + + e["imported-chartA-B.SCAbool"] = "false" + e["imported-chartA-B.SCAfloat"] = "3.1" + e["imported-chartA-B.SCAint"] = "55" + e["imported-chartA-B.SCAstring"] = "jabba" + + e["imported-chartA-B.SCBbool"] = "true" + e["imported-chartA-B.SCBfloat"] = "7.77" + e["imported-chartA-B.SCBint"] = "33" + e["imported-chartA-B.SCBstring"] = "boba" + e["imported-chartA-B.SPextra5"] = "k8s" + e["imported-chartA-B.SC1extra5"] = "tiller" + + e["overridden-chart1.SC1bool"] = "false" + e["overridden-chart1.SC1float"] = "3.141592" + e["overridden-chart1.SC1int"] = "99" + e["overridden-chart1.SC1string"] = "pollywog" + e["overridden-chart1.SPextra2"] = "42" + + e["overridden-chartA.SCAbool"] = "true" + e["overridden-chartA.SCAfloat"] = "41.3" + e["overridden-chartA.SCAint"] = "808" + e["overridden-chartA.SCAstring"] = "jaberwocky" + e["overridden-chartA.SPextra4"] = "true" + + e["overridden-chartA-B.SCAbool"] = "true" + e["overridden-chartA-B.SCAfloat"] = "41.3" + e["overridden-chartA-B.SCAint"] = "808" + e["overridden-chartA-B.SCAstring"] = "jaberwocky" + e["overridden-chartA-B.SCBbool"] = "false" + e["overridden-chartA-B.SCBfloat"] = "1.99" + e["overridden-chartA-B.SCBint"] = "77" + e["overridden-chartA-B.SCBstring"] = "jango" + e["overridden-chartA-B.SPextra6"] = "111" + e["overridden-chartA-B.SCAextra1"] = "23" + e["overridden-chartA-B.SCBextra1"] = "13" + e["overridden-chartA-B.SC1extra6"] = "77" + + // `exports` style + e["SCBexported1B"] = "1965" + e["SC1extra7"] = "true" + e["SCBexported2A"] = "blaster" + e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" + + verifyRequirementsImportValues(t, c, v, e) +} +func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v *chart.Config, e map[string]string) { + + err := ProcessRequirementsImportValues(c, v) + if err != nil { + t.Errorf("Error processing import values requirements %v", err) + } + cv := c.GetValues() + cc, err := ReadValues([]byte(cv.Raw)) + if err != nil { + t.Errorf("Error reading import values %v", err) + } + for kk, vv := range e { + pv, err := cc.PathValue(kk) + if err != nil { + t.Fatalf("Error retrieving import values table %v %v", kk, err) + return + } + + switch pv.(type) { + case float64: + s := strconv.FormatFloat(pv.(float64), 'f', -1, 64) + if s != vv { + t.Errorf("Failed to match imported float value %v with expected %v", s, vv) + return + } + case bool: + b := strconv.FormatBool(pv.(bool)) + if b != vv { + t.Errorf("Failed to match imported bool value %v with expected %v", b, vv) + return + } + default: + if pv.(string) != vv { + t.Errorf("Failed to match imported string value %v with expected %v", pv, vv) + return + } + } + + } +} diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml index 5e5b21065..712b3a2fa 100644 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml @@ -1,21 +1,17 @@ # Default values for subchart. # This is a YAML-formatted file. # Declare variables to be passed into your templates. -replicaCount: 1 -image: - repository: nginx - tag: stable - pullPolicy: IfNotPresent +# subchartA service: name: nginx type: ClusterIP externalPort: 80 internalPort: 80 -resources: - limits: - cpu: 100m - memory: 128Mi - requests: - cpu: 100m - memory: 128Mi +SCAdata: + SCAbool: false + SCAfloat: 3.1 + SCAint: 55 + SCAstring: "jabba" + SCAnested1: + SCAnested2: true diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml index 5e5b21065..774fdd75c 100644 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml @@ -1,21 +1,35 @@ # Default values for subchart. # This is a YAML-formatted file. # Declare variables to be passed into your templates. -replicaCount: 1 -image: - repository: nginx - tag: stable - pullPolicy: IfNotPresent service: name: nginx type: ClusterIP externalPort: 80 internalPort: 80 -resources: - limits: - cpu: 100m - memory: 128Mi - requests: - cpu: 100m - memory: 128Mi + +SCBdata: + SCBbool: true + SCBfloat: 7.77 + SCBint: 33 + SCBstring: "boba" + +exports: + SCBexported1: + SCBexported1A: + SCBexported1B: 1965 + + SCBexported2: + SCBexported2A: "blaster" + +global: + kolla: + nova: + api: + all: + port: 8774 + metadata: + all: + port: 8775 + + diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml index 94d278234..abfe85e76 100644 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml @@ -6,10 +6,27 @@ dependencies: tags: - front-end - subcharta + import-values: + - child: SCAdata + parent: imported-chartA + - child: SCAdata + parent: overridden-chartA + - child: SCAdata + parent: imported-chartA-B + - name: subchartb repository: http://localhost:10191 version: 0.1.0 condition: subchartb.enabled + import-values: + - child: SCBdata + parent: imported-chartB + - child: SCBdata + parent: imported-chartA-B + - child: exports.SCBexported2 + parent: exports.SCBexported2 + - SCBexported1 + tags: - front-end - subchartb diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml index 5e5b21065..72d3fa5c8 100644 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml +++ b/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml @@ -1,21 +1,55 @@ # Default values for subchart. # This is a YAML-formatted file. # Declare variables to be passed into your templates. -replicaCount: 1 -image: - repository: nginx - tag: stable - pullPolicy: IfNotPresent +# subchart1 service: name: nginx type: ClusterIP externalPort: 80 internalPort: 80 -resources: - limits: - cpu: 100m - memory: 128Mi - requests: - cpu: 100m - memory: 128Mi + +SC1data: + SC1bool: true + SC1float: 3.14 + SC1int: 100 + SC1string: "dollywood" + SC1extra1: 11 + +imported-chartA: + SC1extra2: 1.337 + +overridden-chartA: + SCAbool: true + SCAfloat: 3.14 + SCAint: 100 + SCAstring: "jabathehut" + SC1extra3: true + +imported-chartA-B: + SC1extra5: "tiller" + +overridden-chartA-B: + SCAbool: true + SCAfloat: 3.33 + SCAint: 555 + SCAstring: "wormwood" + SCAextra1: 23 + + SCBbool: true + SCBfloat: 0.25 + SCBint: 98 + SCBstring: "murkwood" + SCBextra1: 13 + + SC1extra6: 77 + +SCBexported1A: + SC1extra7: true + +exports: + SC1exported1: + global: + SC1exported2: + all: + SC1exported3: "SC1expstr" \ No newline at end of file diff --git a/pkg/chartutil/testdata/subpop/requirements.yaml b/pkg/chartutil/testdata/subpop/requirements.yaml index 9840e047d..a8eb0aace 100644 --- a/pkg/chartutil/testdata/subpop/requirements.yaml +++ b/pkg/chartutil/testdata/subpop/requirements.yaml @@ -6,6 +6,22 @@ dependencies: tags: - front-end - subchart1 + import-values: + - child: SC1data + parent: imported-chart1 + - child: SC1data + parent: overridden-chart1 + - child: imported-chartA + parent: imported-chartA + - child: imported-chartA-B + parent: imported-chartA-B + - child: overridden-chartA-B + parent: overridden-chartA-B + - child: SCBexported1A + parent: . + - SCBexported2 + - SC1exported1 + - name: subchart2 repository: http://localhost:10191 version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/values.yaml b/pkg/chartutil/testdata/subpop/values.yaml index 85fc7b49d..55e872d41 100644 --- a/pkg/chartutil/testdata/subpop/values.yaml +++ b/pkg/chartutil/testdata/subpop/values.yaml @@ -1,6 +1,40 @@ # parent/values.yaml -# switch-like +imported-chart1: + SPextra1: "helm rocks" + +overridden-chart1: + SC1bool: false + SC1float: 3.141592 + SC1int: 99 + SC1string: "pollywog" + SPextra2: 42 + + +imported-chartA: + SPextra3: 1.337 + +overridden-chartA: + SCAbool: true + SCAfloat: 41.3 + SCAint: 808 + SCAstring: "jaberwocky" + SPextra4: true + +imported-chartA-B: + SPextra5: "k8s" + +overridden-chartA-B: + SCAbool: true + SCAfloat: 41.3 + SCAint: 808 + SCAstring: "jaberwocky" + SCBbool: false + SCBfloat: 1.99 + SCBint: 77 + SCBstring: "jango" + SPextra6: 111 + tags: front-end: true back-end: false diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index bf9b384fd..e9a509306 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -124,6 +124,13 @@ func (m *Manager) Update() error { } return err } + + // Hash requirements.yaml + hash, err := resolver.HashReq(req) + if err != nil { + return err + } + // Check that all of the repos we're dependent on actually exist and // the repo index names. repoNames, err := m.getRepoNames(req.Dependencies) @@ -140,7 +147,7 @@ func (m *Manager) Update() error { // Now we need to find out which version of a chart best satisfies the // requirements the requirements.yaml - lock, err := m.resolve(req, repoNames) + lock, err := m.resolve(req, repoNames, hash) if err != nil { return err } @@ -172,9 +179,9 @@ func (m *Manager) loadChartDir() (*chart.Chart, error) { // resolve takes a list of requirements and translates them into an exact version to download. // // This returns a lock file, which has all of the requirements normalized to a specific version. -func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]string) (*chartutil.RequirementsLock, error) { +func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]string, hash string) (*chartutil.RequirementsLock, error) { res := resolver.New(m.ChartPath, m.HelmHome) - return res.Resolve(req, repoNames) + return res.Resolve(req, repoNames, hash) } // downloadAll takes a list of dependencies and downloads them into charts/ @@ -325,14 +332,7 @@ func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string, for _, dd := range deps { // if dep chart is from local path, verify the path is valid if strings.HasPrefix(dd.Repository, "file://") { - depPath, err := filepath.Abs(strings.TrimPrefix(dd.Repository, "file://")) - if err != nil { - return nil, err - } - - if _, err = os.Stat(depPath); os.IsNotExist(err) { - return nil, fmt.Errorf("directory %s not found", depPath) - } else if err != nil { + if _, err := resolver.GetLocalPath(dd.Repository, m.ChartPath); err != nil { return nil, err } @@ -346,7 +346,13 @@ func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string, found := false for _, repo := range repos { - if urlutil.Equal(repo.URL, dd.Repository) { + if (strings.HasPrefix(dd.Repository, "@") && strings.TrimPrefix(dd.Repository, "@") == repo.Name) || + (strings.HasPrefix(dd.Repository, "alias:") && strings.TrimPrefix(dd.Repository, "alias:") == repo.Name) { + found = true + dd.Repository = repo.URL + reposMap[dd.Name] = repo.Name + break + } else if urlutil.Equal(repo.URL, dd.Repository) { found = true reposMap[dd.Name] = repo.Name break @@ -537,17 +543,11 @@ func tarFromLocalDir(chartpath string, name string, repo string, version string) return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo) } - origPath, err := filepath.Abs(strings.TrimPrefix(repo, "file://")) + origPath, err := resolver.GetLocalPath(repo, chartpath) if err != nil { return "", err } - if _, err = os.Stat(origPath); os.IsNotExist(err) { - return "", fmt.Errorf("directory %s not found: %s", origPath, err) - } else if err != nil { - return "", err - } - ch, err := chartutil.LoadDir(origPath) if err != nil { return "", err diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 2035676c4..683e80e00 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -120,6 +120,20 @@ func TestGetRepoNames(t *testing.T) { }, expect: map[string]string{"local-dep": "file://./testdata/signtest"}, }, + { + name: "repo alias (alias:)", + req: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "alias:testing"}, + }, + expect: map[string]string{"oedipus-rex": "testing"}, + }, + { + name: "repo alias (@)", + req: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "@testing"}, + }, + expect: map[string]string{"oedipus-rex": "testing"}, + }, } for _, tt := range tests { diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 2e773cca7..4fb28d101 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -97,6 +97,10 @@ func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ... if err != nil { return nil, err } + err = chartutil.ProcessRequirementsImportValues(req.Chart, req.Values) + if err != nil { + return nil, err + } return h.install(ctx, req) } @@ -155,6 +159,7 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts req.DisableHooks = h.opts.disableHooks req.Recreate = h.opts.recreate req.ResetValues = h.opts.resetValues + req.ReuseValues = h.opts.reuseValues ctx := NewContext() if h.opts.before != nil { @@ -166,6 +171,10 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts if err != nil { return nil, err } + err = chartutil.ProcessRequirementsImportValues(req.Chart, req.Values) + if err != nil { + return nil, err + } return h.update(ctx, req) } diff --git a/pkg/helm/helm_test.go b/pkg/helm/helm_test.go index 00db8b4e8..bcaeec2f0 100644 --- a/pkg/helm/helm_test.go +++ b/pkg/helm/helm_test.go @@ -87,7 +87,9 @@ func TestListReleases_VerifyOptions(t *testing.T) { return errSkip }) - NewClient(b4c).ListReleases(ops...) + if _, err := NewClient(b4c).ListReleases(ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } } // Verify InstallOption's are applied to an InstallReleaseRequest correctly. @@ -99,6 +101,7 @@ func TestInstallRelease_VerifyOptions(t *testing.T) { var reuseName = true var dryRun = true var chartName = "alpine" + var chartPath = filepath.Join(chartsDir, chartName) var overrides = []byte("key1=value1,key2=value2") // Expected InstallReleaseRequest message @@ -133,7 +136,9 @@ func TestInstallRelease_VerifyOptions(t *testing.T) { return errSkip }) - NewClient(b4c).InstallRelease(chartName, namespace, ops...) + if _, err := NewClient(b4c).InstallRelease(chartPath, namespace, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } } // Verify DeleteOptions's are applied to an UninstallReleaseRequest correctly. @@ -168,13 +173,16 @@ func TestDeleteRelease_VerifyOptions(t *testing.T) { return errSkip }) - NewClient(b4c).DeleteRelease(releaseName, ops...) + if _, err := NewClient(b4c).DeleteRelease(releaseName, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } } // Verify UpdateOption's are applied to an UpdateReleaseRequest correctly. func TestUpdateRelease_VerifyOptions(t *testing.T) { // Options testdata var chartName = "alpine" + var chartPath = filepath.Join(chartsDir, chartName) var releaseName = "test" var disableHooks = true var overrides = []byte("key1=value1,key2=value2") @@ -208,7 +216,9 @@ func TestUpdateRelease_VerifyOptions(t *testing.T) { return errSkip }) - NewClient(b4c).UpdateRelease(releaseName, chartName, ops...) + if _, err := NewClient(b4c).UpdateRelease(releaseName, chartPath, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } } // Verify RollbackOption's are applied to a RollbackReleaseRequest correctly. @@ -246,7 +256,9 @@ func TestRollbackRelease_VerifyOptions(t *testing.T) { return errSkip }) - NewClient(b4c).RollbackRelease(releaseName, ops...) + if _, err := NewClient(b4c).RollbackRelease(releaseName, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } } // Verify StatusOption's are applied to a GetReleaseStatusRequest correctly. @@ -273,7 +285,9 @@ func TestReleaseStatus_VerifyOptions(t *testing.T) { return errSkip }) - NewClient(b4c).ReleaseStatus(releaseName, StatusReleaseVersion(revision)) + if _, err := NewClient(b4c).ReleaseStatus(releaseName, StatusReleaseVersion(revision)); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } } // Verify ContentOption's are applied to a GetReleaseContentRequest correctly. @@ -300,7 +314,9 @@ func TestReleaseContent_VerifyOptions(t *testing.T) { return errSkip }) - NewClient(b4c).ReleaseContent(releaseName, ContentReleaseVersion(revision)) + if _, err := NewClient(b4c).ReleaseContent(releaseName, ContentReleaseVersion(revision)); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } } func assert(t *testing.T, expect, actual interface{}) { diff --git a/pkg/helm/option.go b/pkg/helm/option.go index 3853133ef..9dadafd11 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -66,6 +66,8 @@ type options struct { histReq rls.GetHistoryRequest // resetValues instructs Tiller to reset values to their defaults. resetValues bool + // reuseValues instructs Tiller to reuse the values from the last release. + reuseValues bool // release test options are applied directly to the test release history request testReq rls.TestReleaseRequest } @@ -323,6 +325,13 @@ func ResetValues(reset bool) UpdateOption { } } +// ReuseValues will (if true) trigger resetting the values to their original state. +func ReuseValues(reuse bool) UpdateOption { + return func(opts *options) { + opts.reuseValues = reuse + } +} + // UpgradeRecreate will (if true) recreate pods after upgrade. func UpgradeRecreate(recreate bool) UpdateOption { return func(opts *options) { diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go index ee467ea27..e1fccb416 100644 --- a/pkg/hooks/hooks.go +++ b/pkg/hooks/hooks.go @@ -23,6 +23,9 @@ import ( // HookAnno is the label name for a hook const HookAnno = "helm.sh/hook" +// HookWeightAnno is the label name for a hook weight +const HookWeightAnno = "helm.sh/hook-weight" + // Types of hooks const ( PreInstall = "pre-install" diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 7bcf8754e..68f07c469 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -159,11 +159,14 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { if err != nil { return "", err } + missing := []string{} err = perform(c, namespace, infos, func(info *resource.Info) error { - log.Printf("Doing get for: '%s'", info.Name) + log.Printf("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, info.Export) if err != nil { - return err + log.Printf("WARNING: Failed Get for resource %q: %s", info.Name, err) + missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name)) + return nil } // We need to grab the ObjectReference so we can correctly group the objects. or, err := api.GetReference(obj) @@ -194,7 +197,7 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { } for _, o := range ot { if err := p.PrintObj(o, buf); err != nil { - log.Printf("failed to print object type '%s', object: '%s' :\n %v", t, o, err) + log.Printf("failed to print object type %s, object: %q :\n %v", t, o, err) return "", err } } @@ -202,6 +205,12 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { return "", err } } + if len(missing) > 0 { + buf.WriteString("==> MISSING\nKIND\t\tNAME\n") + for _, s := range missing { + fmt.Fprintln(buf, s) + } + } return buf.String(), nil } @@ -241,17 +250,17 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader } kind := info.Mapping.GroupVersionKind.Kind - log.Printf("Created a new %s called %s\n", kind, info.Name) + log.Printf("Created a new %s called %q\n", kind, info.Name) return nil } originalInfo := original.Get(info) if originalInfo == nil { - return fmt.Errorf("no resource with the name %s found", info.Name) + return fmt.Errorf("no resource with the name %q found", info.Name) } if err := updateResource(c, info, originalInfo.Object, recreate); err != nil { - log.Printf("error updating the resource %s:\n\t %v", info.Name, err) + log.Printf("error updating the resource %q:\n\t %v", info.Name, err) updateErrors = append(updateErrors, err.Error()) } @@ -266,9 +275,9 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader } for _, info := range original.Difference(target) { - log.Printf("Deleting %s in %s...", info.Name, info.Namespace) + log.Printf("Deleting %q in %s...", info.Name, info.Namespace) if err := deleteResource(c, info); err != nil { - log.Printf("Failed to delete %s, err: %s", info.Name, err) + log.Printf("Failed to delete %q, err: %s", info.Name, err) } } if shouldWait { @@ -286,7 +295,7 @@ func (c *Client) Delete(namespace string, reader io.Reader) error { return err } return perform(c, namespace, infos, func(info *resource.Info) error { - log.Printf("Starting delete for %s %s", info.Name, info.Mapping.GroupVersionKind.Kind) + log.Printf("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) err := deleteResource(c, info) return skipIfNotFound(err) }) @@ -358,7 +367,7 @@ func deleteResource(c *Client, info *resource.Info) error { } return err } - log.Printf("Using reaper for deleting %s", info.Name) + log.Printf("Using reaper for deleting %q", info.Name) return reaper.Stop(info.Namespace, info.Name, 0, nil) } @@ -398,7 +407,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, return fmt.Errorf("failed to create patch: %s", err) } if patch == nil { - log.Printf("Looks like there are no changes for %s", target.Name) + log.Printf("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) // This needs to happen to make sure that tiller has the latest info from the API // Otherwise there will be no labels and other functions that use labels will panic if err := target.Get(); err != nil { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 8a9980abb..02736f3bf 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -59,6 +59,7 @@ func newPodWithStatus(name string, status api.PodStatus, namespace string) api.P ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, + SelfLink: "/api/v1/namespaces/default/pods/" + name, }, Spec: api.PodSpec{ Containers: []api.Container{{ @@ -279,6 +280,49 @@ func TestBuild(t *testing.T) { } } +func TestGet(t *testing.T) { + list := newPodList("starfish", "otter") + f, tf, _, ns := cmdtesting.NewAPIFactory() + tf.Client = &fake.RESTClient{ + NegotiatedSerializer: ns, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + p, m := req.URL.Path, req.Method + //actions = append(actions, p+":"+m) + t.Logf("got request %s %s", p, m) + switch { + case p == "/namespaces/default/pods/starfish" && m == "GET": + return newResponse(404, notFoundBody()) + case p == "/namespaces/default/pods/otter" && m == "GET": + return newResponse(200, &list.Items[1]) + default: + t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) + return nil, nil + } + }), + } + c := &Client{Factory: f} + + // Test Success + data := strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: otter") + o, err := c.Get("default", data) + if err != nil { + t.Errorf("Expected missing results, got %q", err) + } + if !strings.Contains(o, "==> v1/Pod") && !strings.Contains(o, "otter") { + t.Errorf("Expected v1/Pod otter, got %s", o) + } + + // Test failure + data = strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: starfish") + o, err = c.Get("default", data) + if err != nil { + t.Errorf("Expected missing results, got %q", err) + } + if !strings.Contains(o, "MISSING") && !strings.Contains(o, "pods\t\tstarfish") { + t.Errorf("Expected missing starfish, got %s", o) + } +} + func TestPerform(t *testing.T) { tests := []struct { name string diff --git a/pkg/proto/hapi/release/hook.pb.go b/pkg/proto/hapi/release/hook.pb.go index 956ca15f3..3a1391745 100644 --- a/pkg/proto/hapi/release/hook.pb.go +++ b/pkg/proto/hapi/release/hook.pb.go @@ -100,6 +100,8 @@ type Hook struct { Events []Hook_Event `protobuf:"varint,5,rep,packed,name=events,enum=hapi.release.Hook_Event" json:"events,omitempty"` // LastRun indicates the date/time this was last run. LastRun *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` + // Weight indicates the sort order for execution among similar Hook type + Weight int32 `protobuf:"varint,7,opt,name=weight" json:"weight,omitempty"` } func (m *Hook) Reset() { *m = Hook{} } @@ -122,28 +124,29 @@ func init() { func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 354 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xdd, 0x6e, 0xa2, 0x40, - 0x18, 0x86, 0x17, 0x41, 0xd0, 0xd1, 0x75, 0x67, 0x27, 0x9b, 0xec, 0xc4, 0x93, 0x35, 0x1e, 0x79, - 0x34, 0x6c, 0x6c, 0x7a, 0x01, 0xa8, 0xd3, 0xd6, 0x48, 0xd0, 0x0c, 0x90, 0x26, 0x3d, 0x21, 0x98, - 0x8e, 0x4a, 0x14, 0x86, 0x08, 0xf6, 0x72, 0x7a, 0x55, 0xbd, 0xa0, 0x66, 0x86, 0x9f, 0x34, 0xe9, - 0xd9, 0xc7, 0xf3, 0x3e, 0x7c, 0x33, 0xef, 0x80, 0xbf, 0xa7, 0x38, 0x4f, 0xec, 0x2b, 0xbf, 0xf0, - 0xb8, 0xe0, 0xf6, 0x49, 0x88, 0x33, 0xc9, 0xaf, 0xa2, 0x14, 0x68, 0x28, 0x03, 0x52, 0x07, 0xe3, - 0x7f, 0x47, 0x21, 0x8e, 0x17, 0x6e, 0xab, 0x6c, 0x7f, 0x3b, 0xd8, 0x65, 0x92, 0xf2, 0xa2, 0x8c, - 0xd3, 0xbc, 0xd2, 0xa7, 0xef, 0x3a, 0x30, 0x9e, 0x84, 0x38, 0x23, 0x04, 0x8c, 0x2c, 0x4e, 0x39, - 0xd6, 0x26, 0xda, 0xac, 0xcf, 0xd4, 0x2c, 0xd9, 0x39, 0xc9, 0x5e, 0x71, 0xa7, 0x62, 0x72, 0x96, - 0x2c, 0x8f, 0xcb, 0x13, 0xd6, 0x2b, 0x26, 0x67, 0x34, 0x06, 0xbd, 0x34, 0xce, 0x92, 0x03, 0x2f, - 0x4a, 0x6c, 0x28, 0xde, 0x7e, 0xa3, 0xff, 0xc0, 0xe4, 0x6f, 0x3c, 0x2b, 0x0b, 0xdc, 0x9d, 0xe8, - 0xb3, 0xd1, 0x1c, 0x93, 0xaf, 0x17, 0x24, 0xf2, 0x6c, 0x42, 0xa5, 0xc0, 0x6a, 0x0f, 0xdd, 0x83, - 0xde, 0x25, 0x2e, 0xca, 0xe8, 0x7a, 0xcb, 0xb0, 0x39, 0xd1, 0x66, 0x83, 0xf9, 0x98, 0x54, 0x35, - 0x48, 0x53, 0x83, 0x04, 0x4d, 0x0d, 0x66, 0x49, 0x97, 0xdd, 0xb2, 0xe9, 0x87, 0x06, 0xba, 0x6a, - 0x11, 0x1a, 0x00, 0x2b, 0xf4, 0x36, 0xde, 0xf6, 0xd9, 0x83, 0x3f, 0xd0, 0x2f, 0x30, 0xd8, 0x31, - 0x1a, 0xad, 0x3d, 0x3f, 0x70, 0x5c, 0x17, 0x6a, 0x08, 0x82, 0xe1, 0x6e, 0xeb, 0x07, 0x2d, 0xe9, - 0xa0, 0x11, 0x00, 0x52, 0x59, 0x51, 0x97, 0x06, 0x14, 0xea, 0xea, 0x17, 0x69, 0xd4, 0xc0, 0x68, - 0x76, 0x84, 0xbb, 0x47, 0xe6, 0xac, 0x28, 0xec, 0xb6, 0x3b, 0x1a, 0x62, 0x2a, 0xc2, 0x68, 0xc4, - 0xb6, 0xae, 0xbb, 0x70, 0x96, 0x1b, 0x68, 0xa1, 0xdf, 0xe0, 0xa7, 0x72, 0x5a, 0xd4, 0x43, 0x18, - 0xfc, 0x61, 0xd4, 0xa5, 0x8e, 0x4f, 0xa3, 0x80, 0xfa, 0x41, 0xe4, 0x87, 0xcb, 0x25, 0xf5, 0x7d, - 0xd8, 0xff, 0x96, 0x3c, 0x38, 0x6b, 0x37, 0x64, 0x14, 0x82, 0x45, 0xff, 0xc5, 0xaa, 0xdf, 0x6a, - 0x6f, 0xaa, 0xfa, 0x77, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x69, 0x41, 0x62, 0x57, 0xfc, 0x01, - 0x00, 0x00, + // 371 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0xdf, 0x8e, 0x93, 0x40, + 0x14, 0x87, 0x65, 0x5b, 0xa0, 0x3d, 0x5d, 0xd7, 0x71, 0x62, 0x74, 0xd2, 0x1b, 0xc9, 0x5e, 0x71, + 0x35, 0x98, 0x35, 0x3e, 0x00, 0xdb, 0x1d, 0x75, 0xb3, 0x84, 0x36, 0x03, 0xc4, 0xc4, 0x1b, 0xc2, + 0xc6, 0x69, 0x21, 0x2d, 0x0c, 0x29, 0x53, 0x7d, 0x33, 0x9f, 0xc4, 0x07, 0x32, 0x33, 0xfc, 0x89, + 0x89, 0x77, 0x67, 0xbe, 0xdf, 0xc7, 0x39, 0x9c, 0x03, 0xef, 0xca, 0xa2, 0xad, 0x82, 0xb3, 0x38, + 0x89, 0xa2, 0x13, 0x41, 0x29, 0xe5, 0x91, 0xb6, 0x67, 0xa9, 0x24, 0xbe, 0xd6, 0x01, 0x1d, 0x82, + 0xf5, 0xfb, 0x83, 0x94, 0x87, 0x93, 0x08, 0x4c, 0xf6, 0x7c, 0xd9, 0x07, 0xaa, 0xaa, 0x45, 0xa7, + 0x8a, 0xba, 0xed, 0xf5, 0xdb, 0xdf, 0x33, 0x98, 0x7f, 0x95, 0xf2, 0x88, 0x31, 0xcc, 0x9b, 0xa2, + 0x16, 0xc4, 0xf2, 0x2c, 0x7f, 0xc9, 0x4d, 0xad, 0xd9, 0xb1, 0x6a, 0x7e, 0x90, 0xab, 0x9e, 0xe9, + 0x5a, 0xb3, 0xb6, 0x50, 0x25, 0x99, 0xf5, 0x4c, 0xd7, 0x78, 0x0d, 0x8b, 0xba, 0x68, 0xaa, 0xbd, + 0xe8, 0x14, 0x99, 0x1b, 0x3e, 0xbd, 0xf1, 0x07, 0x70, 0xc4, 0x4f, 0xd1, 0xa8, 0x8e, 0xd8, 0xde, + 0xcc, 0xbf, 0xb9, 0x23, 0xf4, 0xdf, 0x1f, 0xa4, 0x7a, 0x36, 0x65, 0x5a, 0xe0, 0x83, 0x87, 0x3f, + 0xc1, 0xe2, 0x54, 0x74, 0x2a, 0x3f, 0x5f, 0x1a, 0xe2, 0x78, 0x96, 0xbf, 0xba, 0x5b, 0xd3, 0x7e, + 0x0d, 0x3a, 0xae, 0x41, 0xd3, 0x71, 0x0d, 0xee, 0x6a, 0x97, 0x5f, 0x1a, 0xfc, 0x16, 0x9c, 0x5f, + 0xa2, 0x3a, 0x94, 0x8a, 0xb8, 0x9e, 0xe5, 0xdb, 0x7c, 0x78, 0xdd, 0xfe, 0xb1, 0xc0, 0x36, 0x03, + 0xf0, 0x0a, 0xdc, 0x2c, 0x7e, 0x8a, 0xb7, 0xdf, 0x62, 0xf4, 0x02, 0xbf, 0x82, 0xd5, 0x8e, 0xb3, + 0xfc, 0x31, 0x4e, 0xd2, 0x30, 0x8a, 0x90, 0x85, 0x11, 0x5c, 0xef, 0xb6, 0x49, 0x3a, 0x91, 0x2b, + 0x7c, 0x03, 0xa0, 0x95, 0x07, 0x16, 0xb1, 0x94, 0xa1, 0x99, 0xf9, 0x44, 0x1b, 0x03, 0x98, 0x8f, + 0x3d, 0xb2, 0xdd, 0x17, 0x1e, 0x3e, 0x30, 0x64, 0x4f, 0x3d, 0x46, 0xe2, 0x18, 0xc2, 0x59, 0xce, + 0xb7, 0x51, 0x74, 0x1f, 0x6e, 0x9e, 0x90, 0x8b, 0x5f, 0xc3, 0x4b, 0xe3, 0x4c, 0x68, 0x81, 0x09, + 0xbc, 0xe1, 0x2c, 0x62, 0x61, 0xc2, 0xf2, 0x94, 0x25, 0x69, 0x9e, 0x64, 0x9b, 0x0d, 0x4b, 0x12, + 0xb4, 0xfc, 0x2f, 0xf9, 0x1c, 0x3e, 0x46, 0x19, 0x67, 0x08, 0xee, 0x97, 0xdf, 0xdd, 0xe1, 0x86, + 0xcf, 0x8e, 0x39, 0xcb, 0xc7, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x82, 0x3c, 0x7a, 0x0e, 0x14, + 0x02, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 4e772dd0d..8d2873741 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -259,6 +259,9 @@ type UpdateReleaseRequest struct { // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state // before marking the release as successful. It will wait for as long as timeout Wait bool `protobuf:"varint,9,opt,name=wait" json:"wait,omitempty"` + // ReuseValues will cause Tiller to reuse the values from the last release. + // This is ignored if reset_values is set. + ReuseValues bool `protobuf:"varint,10,opt,name=reuse_values,json=reuseValues" json:"reuse_values,omitempty"` } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } @@ -996,78 +999,79 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 1162 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0xc4, - 0x17, 0xaf, 0xe3, 0x7c, 0x9e, 0x76, 0xfb, 0x4f, 0xa7, 0x5f, 0xae, 0xf5, 0x07, 0x15, 0x23, 0x68, - 0x76, 0x61, 0x53, 0x08, 0x57, 0x48, 0x08, 0xa9, 0xdb, 0x8d, 0xda, 0x42, 0xe9, 0x4a, 0xce, 0x76, - 0x91, 0x10, 0x22, 0x72, 0x93, 0x49, 0x6b, 0xd6, 0xf1, 0x04, 0xcf, 0xb8, 0x6c, 0x6f, 0xb9, 0xe3, - 0x51, 0xe0, 0x29, 0x78, 0x02, 0x5e, 0x80, 0x97, 0x41, 0x9e, 0x0f, 0xd7, 0xe3, 0xda, 0xad, 0xe9, - 0x4d, 0x3c, 0x33, 0xe7, 0xcc, 0xf9, 0xf8, 0x9d, 0x33, 0xbf, 0x99, 0x80, 0x7d, 0xe5, 0x2d, 0xfc, - 0x7d, 0x8a, 0xa3, 0x6b, 0x7f, 0x82, 0xe9, 0x3e, 0xf3, 0x83, 0x00, 0x47, 0xfd, 0x45, 0x44, 0x18, - 0x41, 0x1b, 0x89, 0xac, 0xaf, 0x64, 0x7d, 0x21, 0xb3, 0xb7, 0xf8, 0x8e, 0xc9, 0x95, 0x17, 0x31, - 0xf1, 0x2b, 0xb4, 0xed, 0xed, 0xec, 0x3a, 0x09, 0x67, 0xfe, 0xa5, 0x14, 0x08, 0x17, 0x11, 0x0e, - 0xb0, 0x47, 0xb1, 0xfa, 0x6a, 0x9b, 0x94, 0xcc, 0x0f, 0x67, 0x44, 0x0a, 0x76, 0x34, 0x01, 0x65, - 0x1e, 0x8b, 0xa9, 0x66, 0xef, 0x1a, 0x47, 0xd4, 0x27, 0xa1, 0xfa, 0x0a, 0x99, 0xf3, 0x57, 0x0d, - 0xd6, 0x4f, 0x7d, 0xca, 0x5c, 0xb1, 0x91, 0xba, 0xf8, 0x97, 0x18, 0x53, 0x86, 0x36, 0xa0, 0x11, - 0xf8, 0x73, 0x9f, 0x59, 0xc6, 0xae, 0xd1, 0x33, 0x5d, 0x31, 0x41, 0x5b, 0xd0, 0x24, 0xb3, 0x19, - 0xc5, 0xcc, 0xaa, 0xed, 0x1a, 0xbd, 0x8e, 0x2b, 0x67, 0xe8, 0x6b, 0x68, 0x51, 0x12, 0xb1, 0xf1, - 0xc5, 0x8d, 0x65, 0xee, 0x1a, 0xbd, 0xd5, 0xc1, 0x47, 0xfd, 0x22, 0x28, 0xfa, 0x89, 0xa7, 0x11, - 0x89, 0x58, 0x3f, 0xf9, 0x79, 0x71, 0xe3, 0x36, 0x29, 0xff, 0x26, 0x76, 0x67, 0x7e, 0xc0, 0x70, - 0x64, 0xd5, 0x85, 0x5d, 0x31, 0x43, 0x47, 0x00, 0xdc, 0x2e, 0x89, 0xa6, 0x38, 0xb2, 0x1a, 0xdc, - 0x74, 0xaf, 0x82, 0xe9, 0x57, 0x89, 0xbe, 0xdb, 0xa1, 0x6a, 0x88, 0xbe, 0x82, 0x15, 0x01, 0xc9, - 0x78, 0x42, 0xa6, 0x98, 0x5a, 0xcd, 0x5d, 0xb3, 0xb7, 0x3a, 0xd8, 0x11, 0xa6, 0x14, 0xc2, 0x23, - 0x01, 0xda, 0x21, 0x99, 0x62, 0x77, 0x59, 0xa8, 0x27, 0x63, 0x8a, 0xfe, 0x0f, 0x9d, 0xd0, 0x9b, - 0x63, 0xba, 0xf0, 0x26, 0xd8, 0x6a, 0xf1, 0x08, 0x6f, 0x17, 0x9c, 0x9f, 0xa0, 0xad, 0x9c, 0x3b, - 0x03, 0x68, 0x8a, 0xd4, 0xd0, 0x32, 0xb4, 0xce, 0xcf, 0xbe, 0x3d, 0x7b, 0xf5, 0xfd, 0x59, 0x77, - 0x09, 0xb5, 0xa1, 0x7e, 0x76, 0xf0, 0xdd, 0xb0, 0x6b, 0xa0, 0x35, 0x78, 0x72, 0x7a, 0x30, 0x7a, - 0x3d, 0x76, 0x87, 0xa7, 0xc3, 0x83, 0xd1, 0xf0, 0x65, 0xb7, 0xe6, 0xbc, 0x0f, 0x9d, 0x34, 0x66, - 0xd4, 0x02, 0xf3, 0x60, 0x74, 0x28, 0xb6, 0xbc, 0x1c, 0x8e, 0x0e, 0xbb, 0x86, 0xf3, 0xbb, 0x01, - 0x1b, 0x7a, 0x89, 0xe8, 0x82, 0x84, 0x14, 0x27, 0x35, 0x9a, 0x90, 0x38, 0x4c, 0x6b, 0xc4, 0x27, - 0x08, 0x41, 0x3d, 0xc4, 0xef, 0x54, 0x85, 0xf8, 0x38, 0xd1, 0x64, 0x84, 0x79, 0x01, 0xaf, 0x8e, - 0xe9, 0x8a, 0x09, 0xfa, 0x1c, 0xda, 0x32, 0x75, 0x6a, 0xd5, 0x77, 0xcd, 0xde, 0xf2, 0x60, 0x53, - 0x07, 0x44, 0x7a, 0x74, 0x53, 0x35, 0xe7, 0x08, 0xb6, 0x8f, 0xb0, 0x8a, 0x44, 0xe0, 0xa5, 0x3a, - 0x26, 0xf1, 0xeb, 0xcd, 0x31, 0x0f, 0x26, 0xf1, 0xeb, 0xcd, 0x31, 0xb2, 0xa0, 0x25, 0xdb, 0x8d, - 0x87, 0xd3, 0x70, 0xd5, 0xd4, 0x61, 0x60, 0xdd, 0x35, 0x24, 0xf3, 0x2a, 0xb2, 0xf4, 0x31, 0xd4, - 0x93, 0x66, 0xe7, 0x66, 0x96, 0x07, 0x48, 0x8f, 0xf3, 0x24, 0x9c, 0x11, 0x97, 0xcb, 0xf5, 0x52, - 0x99, 0xf9, 0x52, 0x1d, 0x67, 0xbd, 0x1e, 0x92, 0x90, 0xe1, 0x90, 0x3d, 0x2e, 0xfe, 0x53, 0xd8, - 0x29, 0xb0, 0x24, 0x13, 0xd8, 0x87, 0x96, 0x0c, 0x8d, 0x5b, 0x2b, 0xc5, 0x55, 0x69, 0x39, 0x7f, - 0xd6, 0x60, 0xe3, 0x7c, 0x31, 0xf5, 0x18, 0x56, 0xa2, 0x7b, 0x82, 0xda, 0x83, 0x06, 0x27, 0x0d, - 0x89, 0xc5, 0x9a, 0xb0, 0x2d, 0x98, 0xe5, 0x30, 0xf9, 0x75, 0x85, 0x1c, 0x3d, 0x83, 0xe6, 0xb5, - 0x17, 0xc4, 0x98, 0x72, 0x20, 0x52, 0xd4, 0xa4, 0x26, 0x67, 0x1c, 0x57, 0x6a, 0xa0, 0x6d, 0x68, - 0x4d, 0xa3, 0x9b, 0x71, 0x14, 0x87, 0xfc, 0x08, 0xb6, 0xdd, 0xe6, 0x34, 0xba, 0x71, 0xe3, 0x10, - 0x7d, 0x08, 0x4f, 0xa6, 0x3e, 0xf5, 0x2e, 0x02, 0x3c, 0xbe, 0x22, 0xe4, 0x2d, 0xe5, 0xa7, 0xb0, - 0xed, 0xae, 0xc8, 0xc5, 0xe3, 0x64, 0x0d, 0xd9, 0x49, 0x27, 0x4d, 0x22, 0xec, 0x31, 0x6c, 0x35, - 0xb9, 0x3c, 0x9d, 0x27, 0x18, 0x32, 0x7f, 0x8e, 0x49, 0xcc, 0xf8, 0xd1, 0x31, 0x5d, 0x35, 0x45, - 0x1f, 0xc0, 0x4a, 0x84, 0x29, 0x66, 0x63, 0x19, 0x65, 0x9b, 0xef, 0x5c, 0xe6, 0x6b, 0x6f, 0x44, - 0x58, 0x08, 0xea, 0xbf, 0x7a, 0x3e, 0xb3, 0x3a, 0x5c, 0xc4, 0xc7, 0xce, 0x31, 0x6c, 0xe6, 0xb0, - 0x7a, 0x2c, 0xec, 0x7f, 0x1b, 0xb0, 0xe5, 0x92, 0x20, 0xb8, 0xf0, 0x26, 0x6f, 0x2b, 0x00, 0x9f, - 0xc1, 0xa8, 0x76, 0x3f, 0x46, 0x66, 0x01, 0x46, 0x99, 0x5e, 0xaa, 0x6b, 0xbd, 0xa4, 0xa1, 0xd7, - 0x28, 0x47, 0xaf, 0xa9, 0xa3, 0xa7, 0xa0, 0x69, 0x65, 0xa0, 0xf9, 0x06, 0xb6, 0xef, 0xe4, 0xf3, - 0x58, 0x70, 0xfe, 0xa8, 0xc1, 0xe6, 0x49, 0x48, 0x99, 0x17, 0x04, 0x39, 0x6c, 0xd2, 0x06, 0x34, - 0x2a, 0x37, 0x60, 0xed, 0xbf, 0x34, 0xa0, 0xa9, 0x81, 0xab, 0x2a, 0x51, 0xcf, 0x54, 0xa2, 0x52, - 0x53, 0x6a, 0x54, 0xd0, 0xcc, 0x51, 0x01, 0x7a, 0x0f, 0x20, 0xc2, 0x31, 0xc5, 0x63, 0x6e, 0x5c, - 0x80, 0xd8, 0xe1, 0x2b, 0x67, 0xf2, 0xe4, 0x2b, 0xdc, 0xdb, 0xc5, 0xb8, 0x67, 0x5b, 0xf2, 0x04, - 0xb6, 0xf2, 0x50, 0x3d, 0x16, 0xf6, 0xdf, 0x0c, 0xd8, 0x3e, 0x0f, 0xfd, 0x42, 0xe0, 0x8b, 0x9a, - 0xf2, 0x0e, 0x14, 0xb5, 0x02, 0x28, 0x36, 0xa0, 0xb1, 0x88, 0xa3, 0x4b, 0x2c, 0xa1, 0x15, 0x93, - 0x6c, 0x8e, 0x75, 0x2d, 0x47, 0x67, 0x0c, 0xd6, 0xdd, 0x18, 0x1e, 0x99, 0x51, 0x12, 0x75, 0x4a, - 0xdd, 0x1d, 0x41, 0xd3, 0xce, 0x3a, 0xac, 0x1d, 0x61, 0xf6, 0x46, 0x1c, 0x00, 0x99, 0x9e, 0x33, - 0x04, 0x94, 0x5d, 0xbc, 0xf5, 0x27, 0x97, 0x74, 0x7f, 0xea, 0x1d, 0xa3, 0xf4, 0x95, 0x96, 0xf3, - 0x25, 0xb7, 0x7d, 0xec, 0x53, 0x46, 0xa2, 0x9b, 0xfb, 0xa0, 0xeb, 0x82, 0x39, 0xf7, 0xde, 0x49, - 0x66, 0x4f, 0x86, 0xce, 0x11, 0x8f, 0x20, 0xdd, 0x2a, 0x23, 0xc8, 0xde, 0x93, 0x46, 0xb5, 0x7b, - 0xf2, 0x47, 0x40, 0xaf, 0x71, 0x7a, 0x65, 0x3f, 0x70, 0xc5, 0xa8, 0x22, 0xd4, 0xf4, 0x46, 0xb3, - 0xa0, 0x35, 0x09, 0xb0, 0x17, 0xc6, 0x0b, 0x59, 0x36, 0x35, 0x75, 0xf6, 0x60, 0x5d, 0xb3, 0x2e, - 0xe3, 0x4c, 0xf2, 0xa1, 0x97, 0xd2, 0x7a, 0x32, 0x1c, 0xfc, 0xd3, 0x86, 0x55, 0x75, 0xc7, 0x8a, - 0xf7, 0x12, 0xf2, 0x61, 0x25, 0xfb, 0x98, 0x40, 0x4f, 0xcb, 0x9f, 0x53, 0xb9, 0x37, 0xa1, 0xfd, - 0xac, 0x8a, 0xaa, 0x88, 0xc5, 0x59, 0xfa, 0xcc, 0x40, 0x14, 0xba, 0xf9, 0x3b, 0x1e, 0x3d, 0x2f, - 0xb6, 0x51, 0xf2, 0xa8, 0xb0, 0xfb, 0x55, 0xd5, 0x95, 0x5b, 0x74, 0xcd, 0xab, 0xaf, 0x5f, 0xcc, - 0xe8, 0x41, 0x33, 0xfa, 0x5b, 0xc0, 0xde, 0xaf, 0xac, 0x9f, 0xfa, 0xfd, 0x19, 0x9e, 0x68, 0xb7, - 0x12, 0x2a, 0x41, 0xab, 0xe8, 0x9a, 0xb7, 0x3f, 0xa9, 0xa4, 0x9b, 0xfa, 0x9a, 0xc3, 0xaa, 0x4e, - 0x37, 0xa8, 0xc4, 0x40, 0x21, 0x7f, 0xdb, 0x9f, 0x56, 0x53, 0x4e, 0xdd, 0x51, 0xe8, 0xe6, 0xd9, - 0xa0, 0xac, 0x8e, 0x25, 0xcc, 0x55, 0x56, 0xc7, 0x32, 0x92, 0x71, 0x96, 0x90, 0x07, 0x70, 0x4b, - 0x06, 0x68, 0xaf, 0xb4, 0x20, 0x3a, 0x87, 0xd8, 0xbd, 0x87, 0x15, 0x53, 0x17, 0x0b, 0xf8, 0x5f, - 0xee, 0xb6, 0x44, 0x25, 0xd0, 0x14, 0x3f, 0x12, 0xec, 0xe7, 0x15, 0xb5, 0x73, 0x49, 0x49, 0x7e, - 0xb9, 0x27, 0x29, 0x9d, 0xbc, 0xee, 0x49, 0x2a, 0x47, 0x55, 0xce, 0x12, 0xf2, 0x61, 0xd5, 0x8d, - 0x43, 0xe9, 0x3a, 0x61, 0x09, 0x54, 0xb2, 0xfb, 0x2e, 0x3f, 0xd9, 0x4f, 0x2b, 0x68, 0xde, 0x9e, - 0xef, 0x17, 0xf0, 0x43, 0x5b, 0xa9, 0x5e, 0x34, 0xf9, 0xdf, 0xc9, 0x2f, 0xfe, 0x0d, 0x00, 0x00, - 0xff, 0xff, 0xf9, 0x32, 0x44, 0xf7, 0x1f, 0x0f, 0x00, 0x00, + // 1170 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x57, 0xdb, 0x6e, 0xe3, 0x44, + 0x18, 0xae, 0xe3, 0x1c, 0xff, 0x1e, 0x48, 0xa7, 0x27, 0xd7, 0x02, 0x54, 0x8c, 0xa0, 0xd9, 0x85, + 0x4d, 0x21, 0x5c, 0x21, 0x21, 0xa4, 0x6e, 0x37, 0x6a, 0x0b, 0xa5, 0x2b, 0x39, 0xdb, 0x45, 0x42, + 0x88, 0xc8, 0x4d, 0x26, 0xad, 0x59, 0xc7, 0x13, 0x3c, 0xe3, 0xb2, 0xbd, 0xe5, 0x8e, 0x47, 0xe1, + 0x2d, 0x78, 0x01, 0x78, 0x01, 0x5e, 0x06, 0x79, 0x0e, 0x6e, 0xc6, 0xb5, 0x5b, 0x6f, 0x6e, 0x62, + 0xcf, 0xfc, 0xe7, 0xef, 0xff, 0xfd, 0xcd, 0x04, 0xec, 0x6b, 0x6f, 0xe6, 0x1f, 0x50, 0x1c, 0xdd, + 0xf8, 0x23, 0x4c, 0x0f, 0x98, 0x1f, 0x04, 0x38, 0xea, 0xce, 0x22, 0xc2, 0x08, 0xda, 0x4c, 0x64, + 0x5d, 0x25, 0xeb, 0x0a, 0x99, 0xbd, 0xcd, 0x2d, 0x46, 0xd7, 0x5e, 0xc4, 0xc4, 0xaf, 0xd0, 0xb6, + 0x77, 0xe6, 0xf7, 0x49, 0x38, 0xf1, 0xaf, 0xa4, 0x40, 0x84, 0x88, 0x70, 0x80, 0x3d, 0x8a, 0xd5, + 0x53, 0x33, 0x52, 0x32, 0x3f, 0x9c, 0x10, 0x29, 0xd8, 0xd5, 0x04, 0x94, 0x79, 0x2c, 0xa6, 0x9a, + 0xbf, 0x1b, 0x1c, 0x51, 0x9f, 0x84, 0xea, 0x29, 0x64, 0xce, 0xdf, 0x15, 0xd8, 0x38, 0xf3, 0x29, + 0x73, 0x85, 0x21, 0x75, 0xf1, 0x6f, 0x31, 0xa6, 0x0c, 0x6d, 0x42, 0x2d, 0xf0, 0xa7, 0x3e, 0xb3, + 0x8c, 0x3d, 0xa3, 0x63, 0xba, 0x62, 0x81, 0xb6, 0xa1, 0x4e, 0x26, 0x13, 0x8a, 0x99, 0x55, 0xd9, + 0x33, 0x3a, 0x2d, 0x57, 0xae, 0xd0, 0xb7, 0xd0, 0xa0, 0x24, 0x62, 0xc3, 0xcb, 0x5b, 0xcb, 0xdc, + 0x33, 0x3a, 0x6b, 0xbd, 0x4f, 0xba, 0x79, 0x50, 0x74, 0x93, 0x48, 0x03, 0x12, 0xb1, 0x6e, 0xf2, + 0xf3, 0xfc, 0xd6, 0xad, 0x53, 0xfe, 0x4c, 0xfc, 0x4e, 0xfc, 0x80, 0xe1, 0xc8, 0xaa, 0x0a, 0xbf, + 0x62, 0x85, 0x8e, 0x01, 0xb8, 0x5f, 0x12, 0x8d, 0x71, 0x64, 0xd5, 0xb8, 0xeb, 0x4e, 0x09, 0xd7, + 0x2f, 0x13, 0x7d, 0xb7, 0x45, 0xd5, 0x2b, 0xfa, 0x06, 0x56, 0x04, 0x24, 0xc3, 0x11, 0x19, 0x63, + 0x6a, 0xd5, 0xf7, 0xcc, 0xce, 0x5a, 0x6f, 0x57, 0xb8, 0x52, 0x08, 0x0f, 0x04, 0x68, 0x47, 0x64, + 0x8c, 0xdd, 0x65, 0xa1, 0x9e, 0xbc, 0x53, 0xf4, 0x3e, 0xb4, 0x42, 0x6f, 0x8a, 0xe9, 0xcc, 0x1b, + 0x61, 0xab, 0xc1, 0x33, 0xbc, 0xdb, 0x70, 0x7e, 0x81, 0xa6, 0x0a, 0xee, 0xf4, 0xa0, 0x2e, 0x4a, + 0x43, 0xcb, 0xd0, 0xb8, 0x38, 0xff, 0xfe, 0xfc, 0xe5, 0x8f, 0xe7, 0xed, 0x25, 0xd4, 0x84, 0xea, + 0xf9, 0xe1, 0x0f, 0xfd, 0xb6, 0x81, 0xd6, 0x61, 0xf5, 0xec, 0x70, 0xf0, 0x6a, 0xe8, 0xf6, 0xcf, + 0xfa, 0x87, 0x83, 0xfe, 0x8b, 0x76, 0xc5, 0xf9, 0x10, 0x5a, 0x69, 0xce, 0xa8, 0x01, 0xe6, 0xe1, + 0xe0, 0x48, 0x98, 0xbc, 0xe8, 0x0f, 0x8e, 0xda, 0x86, 0xf3, 0xa7, 0x01, 0x9b, 0x7a, 0x8b, 0xe8, + 0x8c, 0x84, 0x14, 0x27, 0x3d, 0x1a, 0x91, 0x38, 0x4c, 0x7b, 0xc4, 0x17, 0x08, 0x41, 0x35, 0xc4, + 0x6f, 0x55, 0x87, 0xf8, 0x7b, 0xa2, 0xc9, 0x08, 0xf3, 0x02, 0xde, 0x1d, 0xd3, 0x15, 0x0b, 0xf4, + 0x25, 0x34, 0x65, 0xe9, 0xd4, 0xaa, 0xee, 0x99, 0x9d, 0xe5, 0xde, 0x96, 0x0e, 0x88, 0x8c, 0xe8, + 0xa6, 0x6a, 0xce, 0x31, 0xec, 0x1c, 0x63, 0x95, 0x89, 0xc0, 0x4b, 0x4d, 0x4c, 0x12, 0xd7, 0x9b, + 0x62, 0x9e, 0x4c, 0x12, 0xd7, 0x9b, 0x62, 0x64, 0x41, 0x43, 0x8e, 0x1b, 0x4f, 0xa7, 0xe6, 0xaa, + 0xa5, 0xc3, 0xc0, 0xba, 0xef, 0x48, 0xd6, 0x95, 0xe7, 0xe9, 0x53, 0xa8, 0x26, 0xc3, 0xce, 0xdd, + 0x2c, 0xf7, 0x90, 0x9e, 0xe7, 0x69, 0x38, 0x21, 0x2e, 0x97, 0xeb, 0xad, 0x32, 0xb3, 0xad, 0x3a, + 0x99, 0x8f, 0x7a, 0x44, 0x42, 0x86, 0x43, 0xb6, 0x58, 0xfe, 0x67, 0xb0, 0x9b, 0xe3, 0x49, 0x16, + 0x70, 0x00, 0x0d, 0x99, 0x1a, 0xf7, 0x56, 0x88, 0xab, 0xd2, 0x72, 0xfe, 0xa9, 0xc0, 0xe6, 0xc5, + 0x6c, 0xec, 0x31, 0xac, 0x44, 0x0f, 0x24, 0xb5, 0x0f, 0x35, 0x4e, 0x1a, 0x12, 0x8b, 0x75, 0xe1, + 0x5b, 0x30, 0xcb, 0x51, 0xf2, 0xeb, 0x0a, 0x39, 0x7a, 0x0a, 0xf5, 0x1b, 0x2f, 0x88, 0x31, 0xe5, + 0x40, 0xa4, 0xa8, 0x49, 0x4d, 0xce, 0x38, 0xae, 0xd4, 0x40, 0x3b, 0xd0, 0x18, 0x47, 0xb7, 0xc3, + 0x28, 0x0e, 0xf9, 0x27, 0xd8, 0x74, 0xeb, 0xe3, 0xe8, 0xd6, 0x8d, 0x43, 0xf4, 0x31, 0xac, 0x8e, + 0x7d, 0xea, 0x5d, 0x06, 0x78, 0x78, 0x4d, 0xc8, 0x1b, 0xca, 0xbf, 0xc2, 0xa6, 0xbb, 0x22, 0x37, + 0x4f, 0x92, 0x3d, 0x64, 0x27, 0x93, 0x34, 0x8a, 0xb0, 0xc7, 0xb0, 0x55, 0xe7, 0xf2, 0x74, 0x9d, + 0x60, 0xc8, 0xfc, 0x29, 0x26, 0x31, 0xe3, 0x9f, 0x8e, 0xe9, 0xaa, 0x25, 0xfa, 0x08, 0x56, 0x22, + 0x4c, 0x31, 0x1b, 0xca, 0x2c, 0x9b, 0xdc, 0x72, 0x99, 0xef, 0xbd, 0x16, 0x69, 0x21, 0xa8, 0xfe, + 0xee, 0xf9, 0xcc, 0x6a, 0x71, 0x11, 0x7f, 0x17, 0x66, 0x31, 0xc5, 0xca, 0x0c, 0x94, 0x59, 0x4c, + 0xb1, 0x30, 0x73, 0x4e, 0x60, 0x2b, 0x03, 0xe7, 0xa2, 0x9d, 0xf9, 0xd7, 0x80, 0x6d, 0x97, 0x04, + 0xc1, 0xa5, 0x37, 0x7a, 0x53, 0xa2, 0x37, 0x73, 0x30, 0x56, 0x1e, 0x86, 0xd1, 0xcc, 0x81, 0x71, + 0x6e, 0xdc, 0xaa, 0xda, 0xb8, 0x69, 0x00, 0xd7, 0x8a, 0x01, 0xae, 0xeb, 0x00, 0x2b, 0xf4, 0x1a, + 0x77, 0xe8, 0x39, 0xdf, 0xc1, 0xce, 0xbd, 0x7a, 0x16, 0x05, 0xe7, 0xaf, 0x0a, 0x6c, 0x9d, 0x86, + 0x94, 0x79, 0x41, 0x90, 0xc1, 0x26, 0x9d, 0x51, 0xa3, 0xf4, 0x8c, 0x56, 0xde, 0x65, 0x46, 0x4d, + 0x0d, 0x5c, 0xd5, 0x89, 0xea, 0x5c, 0x27, 0x4a, 0xcd, 0xad, 0xc6, 0x16, 0xf5, 0x0c, 0x5b, 0xa0, + 0x0f, 0x00, 0xc4, 0xa0, 0x71, 0xe7, 0x02, 0xc4, 0x16, 0xdf, 0x39, 0x97, 0xe4, 0xa0, 0x70, 0x6f, + 0xe6, 0xe3, 0x3e, 0x37, 0xb5, 0xce, 0x29, 0x6c, 0x67, 0xa1, 0x5a, 0x14, 0xf6, 0x3f, 0x0c, 0xd8, + 0xb9, 0x08, 0xfd, 0x5c, 0xe0, 0xf3, 0x86, 0xf2, 0x1e, 0x14, 0x95, 0x1c, 0x28, 0x36, 0xa1, 0x36, + 0x8b, 0xa3, 0x2b, 0x2c, 0xa1, 0x15, 0x8b, 0xf9, 0x1a, 0xab, 0x5a, 0x8d, 0xce, 0x10, 0xac, 0xfb, + 0x39, 0x2c, 0x58, 0x51, 0x92, 0x75, 0xca, 0xee, 0x2d, 0xc1, 0xe4, 0xce, 0x06, 0xac, 0x1f, 0x63, + 0xf6, 0x5a, 0x7c, 0x00, 0xb2, 0x3c, 0xa7, 0x0f, 0x68, 0x7e, 0xf3, 0x2e, 0x9e, 0xdc, 0xd2, 0xe3, + 0xa9, 0xab, 0x8e, 0xd2, 0x57, 0x5a, 0xce, 0xd7, 0xdc, 0xf7, 0x89, 0x4f, 0x19, 0x89, 0x6e, 0x1f, + 0x82, 0xae, 0x0d, 0xe6, 0xd4, 0x7b, 0x2b, 0xc9, 0x3f, 0x79, 0x75, 0x8e, 0x79, 0x06, 0xa9, 0xa9, + 0xcc, 0x60, 0xfe, 0x28, 0x35, 0xca, 0x1d, 0xa5, 0x3f, 0x03, 0x7a, 0x85, 0xd3, 0x53, 0xfd, 0x91, + 0x53, 0x48, 0x35, 0xa1, 0xa2, 0x0f, 0x9a, 0x05, 0x8d, 0x51, 0x80, 0xbd, 0x30, 0x9e, 0xc9, 0xb6, + 0xa9, 0xa5, 0xb3, 0x0f, 0x1b, 0x9a, 0x77, 0x99, 0x67, 0x52, 0x0f, 0xbd, 0x92, 0xde, 0x93, 0xd7, + 0xde, 0x7f, 0x4d, 0x58, 0x53, 0xc7, 0xb0, 0xb8, 0x52, 0x21, 0x1f, 0x56, 0xe6, 0xef, 0x1b, 0xe8, + 0x49, 0xf1, 0x8d, 0x2b, 0x73, 0x6d, 0xb4, 0x9f, 0x96, 0x51, 0x15, 0xb9, 0x38, 0x4b, 0x5f, 0x18, + 0x88, 0x42, 0x3b, 0x7b, 0x0d, 0x40, 0xcf, 0xf2, 0x7d, 0x14, 0xdc, 0x3b, 0xec, 0x6e, 0x59, 0x75, + 0x15, 0x16, 0xdd, 0xf0, 0xee, 0xeb, 0x67, 0x37, 0x7a, 0xd4, 0x8d, 0x7e, 0x5d, 0xb0, 0x0f, 0x4a, + 0xeb, 0xa7, 0x71, 0x7f, 0x85, 0x55, 0xed, 0x54, 0x42, 0x05, 0x68, 0xe5, 0xdd, 0x04, 0xec, 0xcf, + 0x4a, 0xe9, 0xa6, 0xb1, 0xa6, 0xb0, 0xa6, 0xd3, 0x0d, 0x2a, 0x70, 0x90, 0xcb, 0xdf, 0xf6, 0xe7, + 0xe5, 0x94, 0xd3, 0x70, 0x14, 0xda, 0x59, 0x36, 0x28, 0xea, 0x63, 0x01, 0x73, 0x15, 0xf5, 0xb1, + 0x88, 0x64, 0x9c, 0x25, 0xe4, 0x01, 0xdc, 0x91, 0x01, 0xda, 0x2f, 0x6c, 0x88, 0xce, 0x21, 0x76, + 0xe7, 0x71, 0xc5, 0x34, 0xc4, 0x0c, 0xde, 0xcb, 0x9c, 0x96, 0xa8, 0x00, 0x9a, 0xfc, 0x4b, 0x82, + 0xfd, 0xac, 0xa4, 0x76, 0xa6, 0x28, 0xc9, 0x2f, 0x0f, 0x14, 0xa5, 0x93, 0xd7, 0x03, 0x45, 0x65, + 0xa8, 0xca, 0x59, 0x42, 0x3e, 0xac, 0xb9, 0x71, 0x28, 0x43, 0x27, 0x2c, 0x81, 0x0a, 0xac, 0xef, + 0xf3, 0x93, 0xfd, 0xa4, 0x84, 0xe6, 0xdd, 0xf7, 0xfd, 0x1c, 0x7e, 0x6a, 0x2a, 0xd5, 0xcb, 0x3a, + 0xff, 0xc7, 0xf9, 0xd5, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x30, 0x80, 0xed, 0x18, 0x42, 0x0f, + 0x00, 0x00, } diff --git a/pkg/releaseutil/manifest.go b/pkg/releaseutil/manifest.go index b6bb87b0a..aad1641d7 100644 --- a/pkg/releaseutil/manifest.go +++ b/pkg/releaseutil/manifest.go @@ -18,7 +18,7 @@ package releaseutil import ( "fmt" - "strings" + "regexp" ) // SimpleHead defines what the structure of the head of a manifest file @@ -31,17 +31,18 @@ type SimpleHead struct { } `json:"metadata,omitempty"` } +var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") + // SplitManifests takes a string of manifest and returns a map contains individual manifests func SplitManifests(bigfile string) map[string]string { - // This is not the best way of doing things, but it's how k8s itself does it. // Basically, we're quickly splitting a stream of YAML documents into an // array of YAML docs. In the current implementation, the file name is just // a place holder, and doesn't have any further meaning. - sep := "\n---\n" tpl := "manifest-%d" res := map[string]string{} - tmp := strings.Split(bigfile, sep) - for i, d := range tmp { + // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. + docs := sep.Split(bigfile, -1) + for i, d := range docs { res[fmt.Sprintf(tpl, i)] = d } return res diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 4a0751f57..601d36ead 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -161,7 +161,6 @@ func (r *ChartRepository) DownloadIndexFile(cachePath string) error { if !filepath.IsAbs(cp) { cp = filepath.Join(cachePath, cp) } - println("Writing to", cp) return ioutil.WriteFile(cp, index, 0644) } diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 148caac4a..1ba5500d6 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -243,6 +243,7 @@ func loadIndex(data []byte) (*IndexFile, error) { if err := yaml.Unmarshal(data, i); err != nil { return i, err } + i.SortEntries() if i.APIVersion == "" { // When we leave Beta, we should remove legacy support and just // return this error: diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index ccf6cfd3c..9eca22996 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -28,8 +28,9 @@ import ( ) const ( - testfile = "testdata/local-index.yaml" - testRepo = "test-repo" + testfile = "testdata/local-index.yaml" + unorderedTestfile = "testdata/local-index-unordered.yaml" + testRepo = "test-repo" ) func TestIndexFile(t *testing.T) { @@ -82,6 +83,18 @@ func TestLoadIndexFile(t *testing.T) { verifyLocalIndex(t, i) } +func TestLoadUnorderedIndex(t *testing.T) { + b, err := ioutil.ReadFile(unorderedTestfile) + if err != nil { + t.Fatal(err) + } + i, err := loadIndex(b) + if err != nil { + t.Fatal(err) + } + verifyLocalIndex(t, i) +} + func TestMerge(t *testing.T) { ind1 := NewIndexFile() ind1.Add(&chart.Metadata{ diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go index c0b0e6da7..6aee41faf 100644 --- a/pkg/repo/repo_test.go +++ b/pkg/repo/repo_test.go @@ -17,6 +17,8 @@ limitations under the License. package repo import "testing" +import "io/ioutil" +import "os" const testRepositoriesFile = "testdata/repositories.yaml" @@ -116,3 +118,100 @@ func TestNewPreV1RepositoriesFile(t *testing.T) { t.Errorf("expected the best charts ever. Got %#v", r.Repositories) } } + +func TestRemoveRepository(t *testing.T) { + sampleRepository := NewRepoFile() + sampleRepository.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + + removeRepository := "stable" + found := sampleRepository.Remove(removeRepository) + if !found { + t.Errorf("expected repository %s not found", removeRepository) + } + + found = sampleRepository.Has(removeRepository) + if found { + t.Errorf("repository %s not deleted", removeRepository) + } +} + +func TestUpdateRepository(t *testing.T) { + sampleRepository := NewRepoFile() + sampleRepository.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + newRepoName := "sample" + sampleRepository.Update(&Entry{Name: newRepoName, + URL: "https://example.com/sample", + Cache: "sample-index.yaml", + }) + + if !sampleRepository.Has(newRepoName) { + t.Errorf("expected repository %s not found", newRepoName) + } + repoCount := len(sampleRepository.Repositories) + + sampleRepository.Update(&Entry{Name: newRepoName, + URL: "https://example.com/sample", + Cache: "sample-index.yaml", + }) + + if repoCount != len(sampleRepository.Repositories) { + t.Errorf("invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount) + } +} + +func TestWriteFile(t *testing.T) { + sampleRepository := NewRepoFile() + sampleRepository.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + + repoFile, err := ioutil.TempFile("", "helm-repo") + if err != nil { + t.Errorf("failed to create test-file (%v)", err) + } + defer os.Remove(repoFile.Name()) + if err := sampleRepository.WriteFile(repoFile.Name(), 744); err != nil { + t.Errorf("failed to write file (%v)", err) + } + + repos, err := LoadRepositoriesFile(repoFile.Name()) + if err != nil { + t.Errorf("failed to load file (%v)", err) + } + for _, repo := range sampleRepository.Repositories { + if !repos.Has(repo.Name) { + t.Errorf("expected repository %s not found", repo.Name) + } + } +} diff --git a/pkg/repo/testdata/local-index-unordered.yaml b/pkg/repo/testdata/local-index-unordered.yaml new file mode 100644 index 000000000..ec529f110 --- /dev/null +++ b/pkg/repo/testdata/local-index-unordered.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +entries: + nginx: + - urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-0.1.0.tgz + name: nginx + description: string + version: 0.1.0 + home: https://github.com/something + digest: "sha256:1234567890abcdef" + keywords: + - popular + - web server + - proxy + - urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz + name: nginx + description: string + version: 0.2.0 + home: https://github.com/something/else + digest: "sha256:1234567890abcdef" + keywords: + - popular + - web server + - proxy + alpine: + - urls: + - https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz + - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz + name: alpine + description: string + version: 1.0.0 + home: https://github.com/something + keywords: + - linux + - alpine + - small + - sumtin + digest: "sha256:1234567890abcdef" diff --git a/pkg/resolver/resolver.go b/pkg/resolver/resolver.go index 858ca272d..ec8ea2cce 100644 --- a/pkg/resolver/resolver.go +++ b/pkg/resolver/resolver.go @@ -47,25 +47,15 @@ func New(chartpath string, helmhome helmpath.Home) *Resolver { } // Resolve resolves dependencies and returns a lock file with the resolution. -func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]string) (*chartutil.RequirementsLock, error) { - d, err := HashReq(reqs) - if err != nil { - return nil, err - } +func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]string, d string) (*chartutil.RequirementsLock, error) { // Now we clone the dependencies, locking as we go. locked := make([]*chartutil.Dependency, len(reqs.Dependencies)) missing := []string{} for i, d := range reqs.Dependencies { if strings.HasPrefix(d.Repository, "file://") { - depPath, err := filepath.Abs(strings.TrimPrefix(d.Repository, "file://")) - if err != nil { - return nil, err - } - if _, err = os.Stat(depPath); os.IsNotExist(err) { - return nil, fmt.Errorf("directory %s not found", depPath) - } else if err != nil { + if _, err := GetLocalPath(d.Repository, r.chartpath); err != nil { return nil, err } @@ -136,3 +126,28 @@ func HashReq(req *chartutil.Requirements) (string, error) { s, err := provenance.Digest(bytes.NewBuffer(data)) return "sha256:" + s, err } + +// GetLocalPath generates absolute local path when use +// "file://" in repository of requirements +func GetLocalPath(repo string, chartpath string) (string, error) { + var depPath string + var err error + p := strings.TrimPrefix(repo, "file://") + + // root path is absolute + if strings.HasPrefix(p, "/") { + if depPath, err = filepath.Abs(p); err != nil { + return "", err + } + } else { + depPath = filepath.Join(chartpath, p) + } + + if _, err = os.Stat(depPath); os.IsNotExist(err) { + return "", fmt.Errorf("directory %s not found", depPath) + } else if err != nil { + return "", err + } + + return depPath, nil +} diff --git a/pkg/resolver/resolver_test.go b/pkg/resolver/resolver_test.go index 4a4f853b7..ef5a3bee0 100644 --- a/pkg/resolver/resolver_test.go +++ b/pkg/resolver/resolver_test.go @@ -81,12 +81,12 @@ func TestResolve(t *testing.T) { name: "repo from valid local path", req: &chartutil.Requirements{ Dependencies: []*chartutil.Dependency{ - {Name: "signtest", Repository: "file://../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, + {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, }, }, expect: &chartutil.RequirementsLock{ Dependencies: []*chartutil.Dependency{ - {Name: "signtest", Repository: "file://../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, + {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, }, }, }, @@ -104,7 +104,12 @@ func TestResolve(t *testing.T) { repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"} r := New("testdata/chartpath", "testdata/helmhome") for _, tt := range tests { - l, err := r.Resolve(tt.req, repoNames) + hash, err := HashReq(tt.req) + if err != nil { + t.Fatal(err) + } + + l, err := r.Resolve(tt.req, repoNames, hash) if err != nil { if tt.err { continue @@ -141,7 +146,7 @@ func TestResolve(t *testing.T) { } func TestHashReq(t *testing.T) { - expect := "sha256:c8250374210bd909cef274be64f871bd4e376d4ecd34a1589b5abf90b68866ba" + expect := "sha256:1feffe2016ca113f64159d91c1f77d6a83bcd23510b171d9264741bf9d63f741" req := &chartutil.Requirements{ Dependencies: []*chartutil.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, diff --git a/pkg/tiller/hook_sorter.go b/pkg/tiller/hook_sorter.go new file mode 100644 index 000000000..42d546620 --- /dev/null +++ b/pkg/tiller/hook_sorter.go @@ -0,0 +1,53 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "sort" + + "k8s.io/helm/pkg/proto/hapi/release" +) + +// sortByHookWeight does an in-place sort of hooks by their supplied weight. +func sortByHookWeight(hooks []*release.Hook) []*release.Hook { + hs := newHookWeightSorter(hooks) + sort.Sort(hs) + return hs.hooks +} + +type hookWeightSorter struct { + hooks []*release.Hook +} + +func newHookWeightSorter(h []*release.Hook) *hookWeightSorter { + return &hookWeightSorter{ + hooks: h, + } +} + +func (hs *hookWeightSorter) Len() int { return len(hs.hooks) } + +func (hs *hookWeightSorter) Swap(i, j int) { + hs.hooks[i], hs.hooks[j] = hs.hooks[j], hs.hooks[i] +} + +func (hs *hookWeightSorter) Less(i, j int) bool { + if hs.hooks[i].Weight == hs.hooks[j].Weight { + return hs.hooks[i].Name < hs.hooks[j].Name + } + return hs.hooks[i].Weight < hs.hooks[j].Weight +} diff --git a/pkg/tiller/hook_sorter_test.go b/pkg/tiller/hook_sorter_test.go new file mode 100644 index 000000000..ac5b9bf8d --- /dev/null +++ b/pkg/tiller/hook_sorter_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestHookSorter(t *testing.T) { + hooks := []*release.Hook{ + { + Name: "g", + Kind: "pre-install", + Weight: 99, + }, + { + Name: "f", + Kind: "pre-install", + Weight: 3, + }, + { + Name: "b", + Kind: "pre-install", + Weight: -3, + }, + { + Name: "e", + Kind: "pre-install", + Weight: 3, + }, + { + Name: "a", + Kind: "pre-install", + Weight: -10, + }, + { + Name: "c", + Kind: "pre-install", + Weight: 0, + }, + { + Name: "d", + Kind: "pre-install", + Weight: 3, + }, + } + + res := sortByHookWeight(hooks) + got := "" + expect := "abcdefg" + for _, r := range res { + got += r.Name + } + if got != expect { + t.Errorf("Expected %q, got %q", expect, got) + } +} diff --git a/pkg/tiller/hooks.go b/pkg/tiller/hooks.go index 0b5510b8c..b26243479 100644 --- a/pkg/tiller/hooks.go +++ b/pkg/tiller/hooks.go @@ -20,6 +20,7 @@ import ( "fmt" "log" "path" + "strconv" "strings" "github.com/ghodss/yaml" @@ -109,12 +110,20 @@ func sortManifests(files map[string]string, apis chartutil.VersionSet, sort Sort generic = append(generic, manifest{name: n, content: c, head: &sh}) continue } + + hws, _ := sh.Metadata.Annotations[hooks.HookWeightAnno] + hw, err := strconv.Atoi(hws) + if err != nil { + hw = 0 + } + h := &release.Hook{ Name: sh.Metadata.Name, Kind: sh.Kind, Path: n, Manifest: c, Events: []release.Hook_Event{}, + Weight: int32(hw), } isHook := false diff --git a/pkg/tiller/kind_sorter.go b/pkg/tiller/kind_sorter.go index ff991fc4a..f08d13569 100644 --- a/pkg/tiller/kind_sorter.go +++ b/pkg/tiller/kind_sorter.go @@ -23,11 +23,61 @@ import ( // SortOrder is an ordering of Kinds. type SortOrder []string -// InstallOrder is the order in which manifests should be installed (by Kind) -var InstallOrder SortOrder = []string{"Namespace", "Secret", "ConfigMap", "PersistentVolume", "PersistentVolumeClaim", "ServiceAccount", "Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "Ingress", "Job"} +// InstallOrder is the order in which manifests should be installed (by Kind). +// +// Those occurring earlier in the list get installed before those occurring later in the list. +var InstallOrder SortOrder = []string{ + "Namespace", + "ResourceQuota", + "LimitRange", + "Secret", + "ConfigMap", + "PersistentVolume", + "PersistentVolumeClaim", + "ServiceAccount", + "ClusterRole", + "ClusterRoleBinding", + "Role", + "RoleBinding", + "Service", + "DaemonSet", + "Pod", + "ReplicationController", + "ReplicaSet", + "Deployment", + "StatefulSet", + "Job", + "CronJob", + "Ingress", +} -// UninstallOrder is the order in which manifests should be uninstalled (by Kind) -var UninstallOrder SortOrder = []string{"Service", "Pod", "ReplicationController", "Deployment", "DaemonSet", "ConfigMap", "Secret", "PersistentVolumeClaim", "PersistentVolume", "ServiceAccount", "Ingress", "Job", "Namespace"} +// UninstallOrder is the order in which manifests should be uninstalled (by Kind). +// +// Those occurring earlier in the list get uninstalled before those occurring later in the list. +var UninstallOrder SortOrder = []string{ + "Ingress", + "Service", + "CronJob", + "Job", + "StatefulSet", + "Deployment", + "ReplicaSet", + "ReplicationController", + "Pod", + "DaemonSet", + "RoleBinding", + "Role", + "ClusterRoleBinding", + "ClusterRole", + "ServiceAccount", + "PersistentVolumeClaim", + "PersistentVolume", + "ConfigMap", + "Secret", + "LimitRange", + "ResourceQuota", + "Namespace", +} // sortByKind does an in-place sort of manifests by Kind. // diff --git a/pkg/tiller/kind_sorter_test.go b/pkg/tiller/kind_sorter_test.go index 212b8aefc..4fe4b09e6 100644 --- a/pkg/tiller/kind_sorter_test.go +++ b/pkg/tiller/kind_sorter_test.go @@ -17,6 +17,7 @@ limitations under the License. package tiller import ( + "bytes" "testing" util "k8s.io/helm/pkg/releaseutil" @@ -25,14 +26,34 @@ import ( func TestKindSorter(t *testing.T) { manifests := []manifest{ { - name: "m", + name: "i", content: "", - head: &util.SimpleHead{Kind: "Deployment"}, + head: &util.SimpleHead{Kind: "ClusterRole"}, }, { - name: "l", + name: "j", content: "", - head: &util.SimpleHead{Kind: "Service"}, + head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, + }, + { + name: "e", + content: "", + head: &util.SimpleHead{Kind: "ConfigMap"}, + }, + { + name: "u", + content: "", + head: &util.SimpleHead{Kind: "CronJob"}, + }, + { + name: "n", + content: "", + head: &util.SimpleHead{Kind: "DaemonSet"}, + }, + { + name: "r", + content: "", + head: &util.SimpleHead{Kind: "Deployment"}, }, { name: "!", @@ -40,35 +61,107 @@ func TestKindSorter(t *testing.T) { head: &util.SimpleHead{Kind: "HonkyTonkSet"}, }, { - name: "h", + name: "v", + content: "", + head: &util.SimpleHead{Kind: "Ingress"}, + }, + { + name: "t", + content: "", + head: &util.SimpleHead{Kind: "Job"}, + }, + { + name: "c", + content: "", + head: &util.SimpleHead{Kind: "LimitRange"}, + }, + { + name: "a", content: "", head: &util.SimpleHead{Kind: "Namespace"}, }, { - name: "e", + name: "f", content: "", - head: &util.SimpleHead{Kind: "ConfigMap"}, + head: &util.SimpleHead{Kind: "PersistentVolume"}, + }, + { + name: "g", + content: "", + head: &util.SimpleHead{Kind: "PersistentVolumeClaim"}, + }, + { + name: "o", + content: "", + head: &util.SimpleHead{Kind: "Pod"}, + }, + { + name: "q", + content: "", + head: &util.SimpleHead{Kind: "ReplicaSet"}, + }, + { + name: "p", + content: "", + head: &util.SimpleHead{Kind: "ReplicationController"}, + }, + { + name: "b", + content: "", + head: &util.SimpleHead{Kind: "ResourceQuota"}, + }, + { + name: "k", + content: "", + head: &util.SimpleHead{Kind: "Role"}, + }, + { + name: "l", + content: "", + head: &util.SimpleHead{Kind: "RoleBinding"}, + }, + { + name: "d", + content: "", + head: &util.SimpleHead{Kind: "Secret"}, + }, + { + name: "m", + content: "", + head: &util.SimpleHead{Kind: "Service"}, + }, + { + name: "h", + content: "", + head: &util.SimpleHead{Kind: "ServiceAccount"}, + }, + { + name: "s", + content: "", + head: &util.SimpleHead{Kind: "StatefulSet"}, }, } - res := sortByKind(manifests, InstallOrder) - got := "" - expect := "helm!" - for _, r := range res { - got += r.name - } - if got != expect { - t.Errorf("Expected %q, got %q", expect, got) - } - - expect = "lmeh!" - got = "" - res = sortByKind(manifests, UninstallOrder) - for _, r := range res { - got += r.name + for _, test := range []struct { + description string + order SortOrder + expected string + }{ + {"install", InstallOrder, "abcdefghijklmnopqrstuv!"}, + {"uninstall", UninstallOrder, "vmutsrqponlkjihgfedcba!"}, + } { + var buf bytes.Buffer + t.Run(test.description, func(t *testing.T) { + if got, want := len(test.expected), len(manifests); got != want { + t.Fatalf("Expected %d names in order, got %d", want, got) + } + defer buf.Reset() + for _, r := range sortByKind(manifests, test.order) { + buf.WriteString(r.name) + } + if got := buf.String(); got != test.expected { + t.Errorf("Expected %q, got %q", test.expected, got) + } + }) } - if got != expect { - t.Errorf("Expected %q, got %q", expect, got) - } - } diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index 873d2335a..ccc378421 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -354,13 +354,33 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R // // This is skipped if the req.ResetValues flag is set, in which case the // request values are not altered. -func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current *release.Release) { +func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current *release.Release) error { if req.ResetValues { // If ResetValues is set, we comletely ignore current.Config. log.Print("Reset values to the chart's original version.") - return + return nil } - // If req.Values is empty, but current. config is not, copy current into the + + // If the ReuseValues flag is set, we always copy the old values over the new config's values. + if req.ReuseValues { + log.Print("Reusing the old release's values") + + // We have to regenerate the old coalesced values: + oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) + if err != nil { + err := fmt.Errorf("failed to rebuild old values: %s", err) + log.Print(err) + return err + } + nv, err := oldVals.YAML() + if err != nil { + return err + } + req.Chart.Values = &chart.Config{Raw: nv} + return nil + } + + // If req.Values is empty, but current.Config is not, copy current into the // request. if (req.Values == nil || req.Values.Raw == "" || req.Values.Raw == "{}\n") && current.Config != nil && @@ -369,6 +389,7 @@ func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current log.Printf("Copying values from %s (v%d) to new release.", current.Name, current.Version) req.Values = current.Config } + return nil } // prepareUpdate builds an updated release for an update operation. @@ -388,7 +409,9 @@ func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*rele } // If new values were not supplied in the upgrade, re-use the existing values. - s.reuseValues(req, currentRelease) + if err := s.reuseValues(req, currentRelease); err != nil { + return nil, nil, err + } // Increment revision count. This is passed to templates, and also stored on // the release object. @@ -911,17 +934,18 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin } log.Printf("Executing %s hooks for %s", hook, name) + executingHooks := []*release.Hook{} for _, h := range hs { - found := false for _, e := range h.Events { if e == code { - found = true + executingHooks = append(executingHooks, h) } } - // If this doesn't implement the hook, skip it. - if !found { - continue - } + } + + executingHooks = sortByHookWeight(executingHooks) + + for _, h := range executingHooks { b := bytes.NewBufferString(h.Manifest) if err := kubeCli.Create(namespace, b, timeout, false); err != nil { @@ -937,6 +961,7 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin } h.LastRun = timeconv.Now() } + log.Printf("Hooks complete for %s %s", hook, name) return nil } diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index 185ef05e7..d4e47a07f 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -717,7 +717,7 @@ func TestUpdateRelease(t *testing.T) { t.Errorf("Expected description %q, got %q", edesc, got) } } -func TestUpdateReleaseResetValues(t *testing.T) { +func TestUpdateRelease_ResetValues(t *testing.T) { c := helm.NewContext() rs := rsFixture() rel := releaseStub() @@ -744,6 +744,71 @@ func TestUpdateReleaseResetValues(t *testing.T) { } } +func TestUpdateRelease_ReuseValues(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + // Since reuseValues is set, this should get ignored. + Values: &chart.Config{Raw: "foo: bar\n"}, + }, + Values: &chart.Config{Raw: "name2: val2"}, + ReuseValues: true, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + // This should have been overwritten with the old value. + expect := "name: value\n" + if res.Release.Chart.Values != nil && res.Release.Chart.Values.Raw != expect { + t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Chart.Values.Raw) + } + // This should have the newly-passed overrides. + expect = "name2: val2" + if res.Release.Config != nil && res.Release.Config.Raw != expect { + t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw) + } +} + +func TestUpdateRelease_ResetReuseValues(t *testing.T) { + // This verifies that when both reset and reuse are set, reset wins. + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + }, + ResetValues: true, + ReuseValues: true, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + // This should have been unset. Config: &chart.Config{Raw: `name: value`}, + if res.Release.Config != nil && res.Release.Config.Raw != "" { + t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw) + } +} + func TestUpdateReleaseFailure(t *testing.T) { c := helm.NewContext() rs := rsFixture() diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go new file mode 100644 index 000000000..e0e4cac0f --- /dev/null +++ b/pkg/version/version_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 version represents the current version of the project. +package version // import "k8s.io/helm/pkg/version" + +import "testing" +import "k8s.io/helm/pkg/proto/hapi/version" + +func TestGetVersionProto(t *testing.T) { + tests := []struct { + version string + buildMetadata string + gitCommit string + gitTreeState string + expected version.Version + }{ + {"", "", "", "", version.Version{SemVer: "", GitCommit: "", GitTreeState: ""}}, + {"v1.0.0", "", "", "", version.Version{SemVer: "v1.0.0", GitCommit: "", GitTreeState: ""}}, + {"v1.0.0", "79d5c5f7", "", "", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "", GitTreeState: ""}}, + {"v1.0.0", "79d5c5f7", "0d399baec2acda578a217d1aec8d7d707c71e44d", "", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "0d399baec2acda578a217d1aec8d7d707c71e44d", GitTreeState: ""}}, + {"v1.0.0", "79d5c5f7", "0d399baec2acda578a217d1aec8d7d707c71e44d", "clean", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "0d399baec2acda578a217d1aec8d7d707c71e44d", GitTreeState: "clean"}}, + } + for _, tt := range tests { + Version = tt.version + BuildMetadata = tt.buildMetadata + GitCommit = tt.gitCommit + GitTreeState = tt.gitTreeState + if versionProto := GetVersionProto(); *versionProto != tt.expected { + t.Errorf("expected Semver(%s), GitCommit(%s) and GitTreeState(%s) to be %v", tt.expected, tt.gitCommit, tt.gitTreeState, *versionProto) + } + } + +} diff --git a/scripts/get b/scripts/get index d5fb16ff2..73266f373 100755 --- a/scripts/get +++ b/scripts/get @@ -62,9 +62,8 @@ verifySupported() { fi } -# downloadFile downloads the latest binary package and also the checksum -# for that binary. -downloadFile() { +# checkLatestVersion checks the latest available version. +checkLatestVersion() { # Use the GitHub API to find the latest version for this project. local latest_url="https://api.github.com/repos/kubernetes/helm/releases/latest" if type "curl" > /dev/null; then @@ -72,7 +71,28 @@ downloadFile() { elif type "wget" > /dev/null; then TAG=$(wget -q -O - $latest_url | awk '/\"tag_name\":/{gsub( /[,\"]/,"", $2); print $2}') fi +} + +# checkHelmInstalledVersion checks which version of helm is installed and +# if it needs to be updated. +checkHelmInstalledVersion() { + if [[ -f "${HELM_INSTALL_DIR}/${PROJECT_NAME}" ]]; then + local version=$(helm version | grep '^Client' | cut -d'"' -f2) + if [[ "$version" == "$TAG" ]]; then + echo "Helm ${version} is up-to-date." + return 0 + else + echo "Helm ${TAG} is available. Upgrading 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://kubernetes-helm.storage.googleapis.com/$HELM_DIST" CHECKSUM_URL="$DOWNLOAD_URL.sha256" @@ -140,6 +160,9 @@ set -e initArch initOS verifySupported -downloadFile -installFile +checkLatestVersion +if ! checkHelmInstalledVersion; then + downloadFile + installFile +fi testVersion diff --git a/scripts/validate-go.sh b/scripts/validate-go.sh index 33a4c6633..2ecf5dfb3 100755 --- a/scripts/validate-go.sh +++ b/scripts/validate-go.sh @@ -30,7 +30,6 @@ gometalinter.v1 \ --enable deadcode \ --severity deadcode:error \ --enable gofmt \ - --enable gosimple \ --enable ineffassign \ --enable misspell \ --enable vet \ diff --git a/versioning.mk b/versioning.mk index 4825c4e72..b336a7999 100644 --- a/versioning.mk +++ b/versioning.mk @@ -1,9 +1,9 @@ MUTABLE_VERSION ?= canary -GIT_COMMIT := $(shell git rev-parse HEAD) -GIT_SHA := $(shell git rev-parse --short HEAD) -GIT_TAG := $(shell git describe --tags --abbrev=0 2>/dev/null) -GIT_DIRTY = $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean") +GIT_COMMIT ?= $(shell git rev-parse HEAD) +GIT_SHA ?= $(shell git rev-parse --short HEAD) +GIT_TAG ?= $(shell git describe --tags --abbrev=0 2>/dev/null) +GIT_DIRTY ?= $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean") ifdef VERSION DOCKER_VERSION = $(VERSION)