diff --git a/Makefile b/Makefile index dbe8e0334..2070fd4d0 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,8 @@ test: test-unit .PHONY: test-unit test-unit: + @echo + @echo "==> Running unit tests <==" HELM_HOME=/no/such/dir $(GO) test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS) .PHONY: test-style diff --git a/README.md b/README.md index 27da73961..4972e7b5b 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ Think of it like apt/yum/homebrew for Kubernetes. Binary downloads of the Helm client can be found at the following links: -- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.4.1-darwin-amd64.tar.gz) -- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.4.1-linux-amd64.tar.gz) -- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.4.1-linux-386.tar.gz) +- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.4.2-darwin-amd64.tar.gz) +- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.4.2-linux-amd64.tar.gz) +- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.4.2-linux-386.tar.gz) Unpack the `helm` binary and add it to your PATH and you are good to go! macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`. diff --git a/_proto/hapi/release/test_run.proto b/_proto/hapi/release/test_run.proto index a441e729f..60734ae03 100644 --- a/_proto/hapi/release/test_run.proto +++ b/_proto/hapi/release/test_run.proto @@ -26,6 +26,7 @@ message TestRun { UNKNOWN = 0; SUCCESS = 1; FAILURE = 2; + RUNNING = 3; } string name = 1; diff --git a/_proto/hapi/rudder/rudder.proto b/_proto/hapi/rudder/rudder.proto index bb724ca45..3a37c9e99 100644 --- a/_proto/hapi/rudder/rudder.proto +++ b/_proto/hapi/rudder/rudder.proto @@ -91,6 +91,7 @@ message UpgradeReleaseRequest{ int64 Timeout = 3; bool Wait = 4; bool Recreate = 5; + bool Force = 6; } message UpgradeReleaseResponse{ hapi.release.Release release = 1; @@ -103,6 +104,7 @@ message RollbackReleaseRequest{ int64 Timeout = 3; bool Wait = 4; bool Recreate = 5; + bool Force = 6; } message RollbackReleaseResponse{ hapi.release.Release release = 1; diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index f16d68238..1fb6a86e9 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -20,6 +20,7 @@ import "hapi/chart/chart.proto"; import "hapi/chart/config.proto"; import "hapi/release/release.proto"; import "hapi/release/info.proto"; +import "hapi/release/test_run.proto"; import "hapi/release/status.proto"; import "hapi/version/version.proto"; @@ -206,6 +207,8 @@ message UpdateReleaseRequest { // ReuseValues will cause Tiller to reuse the values from the last release. // This is ignored if reset_values is set. bool reuse_values = 10; + // Force resource update through delete/recreate if needed. + bool force = 11; } // UpdateReleaseResponse is the response to an update request. @@ -229,6 +232,8 @@ message RollbackReleaseRequest { // 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 = 7; + // Force resource update through delete/recreate if needed. + bool force = 8; } // RollbackReleaseResponse is the response to an update request. @@ -327,4 +332,6 @@ message TestReleaseRequest { // TestReleaseResponse represents a message from executing a test message TestReleaseResponse { string msg = 1; + hapi.release.TestRun.Status status = 2; + } diff --git a/cmd/helm/completion.go b/cmd/helm/completion.go index 9a6a77cbb..b1cd04140 100644 --- a/cmd/helm/completion.go +++ b/cmd/helm/completion.go @@ -53,7 +53,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { Short: "Generate autocompletions script for the specified shell (bash or zsh)", Long: completionDesc, RunE: func(cmd *cobra.Command, args []string) error { - return RunCompletion(out, cmd, args) + return runCompletion(out, cmd, args) }, ValidArgs: shells, } @@ -61,16 +61,16 @@ func newCompletionCmd(out io.Writer) *cobra.Command { return cmd } -func RunCompletion(out io.Writer, cmd *cobra.Command, args []string) error { +func runCompletion(out io.Writer, cmd *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("Shell not specified.") + return fmt.Errorf("shell not specified") } if len(args) > 1 { - return fmt.Errorf("Too many arguments. Expected only the shell type.") + return fmt.Errorf("too many arguments, expected only the shell type") } run, found := completionShells[args[0]] if !found { - return fmt.Errorf("Unsupported shell type %q.", args[0]) + return fmt.Errorf("unsupported shell type %q", args[0]) } return run(out, cmd) diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go index 899fd1e73..a639e7f02 100644 --- a/cmd/helm/fetch.go +++ b/cmd/helm/fetch.go @@ -61,6 +61,8 @@ type fetchCmd struct { keyFile string caFile string + devel bool + out io.Writer } @@ -73,8 +75,14 @@ func newFetchCmd(out io.Writer) *cobra.Command { Long: fetchDesc, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("This command needs at least one argument, url or repo/name of the chart.") + return fmt.Errorf("need at least one argument, url or repo/name of the chart") + } + + if fch.version == "" && fch.devel { + debug("setting version to >0.0.0-a") + fch.version = ">0.0.0-a" } + for i := 0; i < len(args); i++ { fch.chartRef = args[i] if err := fch.run(); err != nil { @@ -97,6 +105,7 @@ func newFetchCmd(out io.Writer) *cobra.Command { f.StringVar(&fch.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") return cmd } diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index ced336ac4..48f87d5e5 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -33,7 +33,6 @@ import ( "k8s.io/helm/pkg/helm" helm_env "k8s.io/helm/pkg/helm/environment" - "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/portforwarder" "k8s.io/helm/pkg/kube" tiller_env "k8s.io/helm/pkg/tiller/environment" @@ -84,8 +83,6 @@ Environment: ` func newRootCmd(out io.Writer) *cobra.Command { - var helmHomeTemp string - cmd := &cobra.Command{ Use: "helm", Short: "The Helm package manager for Kubernetes.", @@ -101,8 +98,7 @@ func newRootCmd(out io.Writer) *cobra.Command { }, } p := cmd.PersistentFlags() - p.StringVar(&helmHomeTemp, "home", helm_env.DefaultHelmHome(), "location of your Helm config. Overrides $HELM_HOME") - settings.Home = helmpath.Home(helmHomeTemp) + p.StringVar((*string)(&settings.Home), "home", helm_env.DefaultHelmHome(), "location of your Helm config. Overrides $HELM_HOME") p.StringVar(&settings.TillerHost, "host", helm_env.DefaultHelmHost(), "address of tiller. Overrides $HELM_HOST") p.StringVar(&kubeContext, "kube-context", "", "name of the kubeconfig context to use") p.BoolVar(&settings.Debug, "debug", false, "enable verbose output") diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 016b79d6b..cf70b3f74 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -24,6 +24,7 @@ import ( "math/rand" "os" "regexp" + "sync" "testing" "github.com/golang/protobuf/ptypes/timestamp" @@ -122,8 +123,9 @@ func releaseMock(opts *releaseOptions) *release.Release { } type fakeReleaseClient struct { - rels []*release.Release - err error + rels []*release.Release + responses map[string]release.TestRun_Status + err error } var _ helm.Interface = &fakeReleaseClient{} @@ -198,7 +200,27 @@ func (c *fakeReleaseClient) ReleaseHistory(rlsName string, opts ...helm.HistoryO } func (c *fakeReleaseClient) RunReleaseTest(rlsName string, opts ...helm.ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) { - return nil, nil + + results := make(chan *rls.TestReleaseResponse) + errc := make(chan error, 1) + + go func() { + var wg sync.WaitGroup + for m, s := range c.responses { + wg.Add(1) + + go func(msg string, status release.TestRun_Status) { + defer wg.Done() + results <- &rls.TestReleaseResponse{Msg: msg, Status: status} + }(m, s) + } + + wg.Wait() + close(results) + close(errc) + }() + + return results, errc } func (c *fakeReleaseClient) Option(opt ...helm.Option) helm.Interface { diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 4dcfe0c30..c51786f90 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -117,6 +117,7 @@ type installCmd struct { timeout int64 wait bool repoURL string + devel bool certFile string keyFile string @@ -155,6 +156,13 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { if err := checkArgsLength(len(args), "chart name"); err != nil { return err } + + debug("Original chart version: %q", inst.version) + if inst.version == "" && inst.devel { + debug("setting version to >0.0.0-a") + inst.version = ">0.0.0-a" + } + cp, err := locateChartPath(inst.repoURL, args[0], inst.version, inst.verify, inst.keyring, inst.certFile, inst.keyFile, inst.caFile) if err != nil { @@ -184,6 +192,7 @@ func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") return cmd } @@ -223,6 +232,8 @@ func (i *installCmd) run() error { if err := checkDependencies(chartRequested, req, i.out); err != nil { return prettyError(err) } + } else if err != chartutil.ErrRequirementsNotFound { + return fmt.Errorf("cannot load requirements: %v", err) } res, err := i.client.InstallReleaseFromChart( diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index 7e749afc7..b2e47a1dd 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -138,6 +138,12 @@ func TestInstall(t *testing.T) { args: []string{"testdata/testcharts/chart-missing-deps"}, err: true, }, + // Install, chart with bad requirements.yaml in /charts + { + name: "install chart with bad requirements.yaml", + args: []string{"testdata/testcharts/chart-bad-requirements"}, + err: true, + }, } runReleaseCases(t, tests, func(c *fakeReleaseClient, out io.Writer) *cobra.Command { diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index d3c9217be..a6d4626c8 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -58,6 +58,7 @@ func Upgrade(client internalclientset.Interface, opts *Options) error { } obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage() obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy() + obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount if _, err := client.Extensions().Deployments(opts.Namespace).Update(obj); err != nil { return err } diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index be3ef151f..22af4d61e 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -329,11 +329,12 @@ func TestInstall_canary(t *testing.T) { func TestUpgrade(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" - + serviceAccount := "newServiceAccount" existingDeployment := deployment(&Options{ - Namespace: api.NamespaceDefault, - ImageSpec: "imageToReplace", - UseCanary: false, + Namespace: api.NamespaceDefault, + ImageSpec: "imageToReplace", + ServiceAccount: "serviceAccountToReplace", + UseCanary: false, }) existingService := service(api.NamespaceDefault) @@ -347,13 +348,17 @@ func TestUpgrade(t *testing.T) { if i != image { t.Errorf("expected image = '%s', got '%s'", image, i) } + sa := obj.Spec.Template.Spec.ServiceAccountName + if sa != serviceAccount { + t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa) + } return true, obj, nil }) fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { return true, existingService, nil }) - opts := &Options{Namespace: api.NamespaceDefault, ImageSpec: image} + opts := &Options{Namespace: api.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount} if err := Upgrade(fc, opts); err != nil { t.Errorf("unexpected error: %#+v", err) } diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 8b4e12e55..9ca853ad3 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -72,7 +72,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { pkg.home = settings.Home if len(args) == 0 { - return fmt.Errorf("This command needs at least one argument, the path to the chart.") + return fmt.Errorf("need at least one argument, the path to the chart") } if pkg.sign { if pkg.key == "" { @@ -130,6 +130,10 @@ func (p *packageCmd) run(cmd *cobra.Command, args []string) error { if err := checkDependencies(ch, reqs, p.out); err != nil { return err } + } else { + if err != chartutil.ErrRequirementsNotFound { + return err + } } var dest string @@ -146,7 +150,9 @@ func (p *packageCmd) run(cmd *cobra.Command, args []string) error { name, err := chartutil.Save(ch, dest) if err == nil { - debug("Saved %s to current directory\n", name) + fmt.Fprintf(p.out, "Successfully packaged chart and saved it to: %s\n", name) + } else { + return fmt.Errorf("Failed to save: %s", err) } // Save to $HELM_HOME/local directory. This is second, because we don't want @@ -156,7 +162,7 @@ func (p *packageCmd) run(cmd *cobra.Command, args []string) error { if err := repo.AddChartToLocalRepo(ch, lr); err != nil { return err } - debug("Saved %s to %s\n", name, lr) + debug("Successfully saved %s to %s\n", name, lr) } if p.sign { diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index 683858cd8..2a0f6d9f5 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -64,7 +64,7 @@ func TestPackage(t *testing.T) { name: "package without chart path", args: []string{}, flags: map[string]string{}, - expect: "This command needs at least one argument, the path to the chart.", + expect: "need at least one argument, the path to the chart", err: true, }, { diff --git a/cmd/helm/plugin.go b/cmd/helm/plugin.go index 37d9205b7..47e1b361c 100644 --- a/cmd/helm/plugin.go +++ b/cmd/helm/plugin.go @@ -41,6 +41,7 @@ func newPluginCmd(out io.Writer) *cobra.Command { newPluginInstallCmd(out), newPluginListCmd(out), newPluginRemoveCmd(out), + newPluginUpdateCmd(out), ) return cmd } diff --git a/cmd/helm/plugin_update.go b/cmd/helm/plugin_update.go new file mode 100644 index 000000000..e4fbe18df --- /dev/null +++ b/cmd/helm/plugin_update.go @@ -0,0 +1,114 @@ +/* +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 main + +import ( + "errors" + "fmt" + "io" + "path/filepath" + "strings" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin" + "k8s.io/helm/pkg/plugin/installer" + + "github.com/spf13/cobra" +) + +type pluginUpdateCmd struct { + names []string + home helmpath.Home + out io.Writer +} + +func newPluginUpdateCmd(out io.Writer) *cobra.Command { + pcmd := &pluginUpdateCmd{out: out} + cmd := &cobra.Command{ + Use: "update ...", + Short: "update one or more Helm plugins", + PreRunE: func(cmd *cobra.Command, args []string) error { + return pcmd.complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return pcmd.run() + }, + } + return cmd +} + +func (pcmd *pluginUpdateCmd) complete(args []string) error { + if len(args) == 0 { + return errors.New("please provide plugin name to update") + } + pcmd.names = args + pcmd.home = settings.Home + return nil +} + +func (pcmd *pluginUpdateCmd) run() error { + installer.Debug = settings.Debug + plugdirs := pluginDirs(pcmd.home) + debug("loading installed plugins from %s", plugdirs) + plugins, err := findPlugins(plugdirs) + if err != nil { + return err + } + var errorPlugins []string + + for _, name := range pcmd.names { + if found := findPlugin(plugins, name); found != nil { + if err := updatePlugin(found, pcmd.home); err != nil { + errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err)) + } else { + fmt.Fprintf(pcmd.out, "Updated plugin: %s\n", name) + } + } else { + errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name)) + } + } + if len(errorPlugins) > 0 { + return fmt.Errorf(strings.Join(errorPlugins, "\n")) + } + return nil +} + +func updatePlugin(p *plugin.Plugin, home helmpath.Home) error { + exactLocation, err := filepath.EvalSymlinks(p.Dir) + if err != nil { + return err + } + absExactLocation, err := filepath.Abs(exactLocation) + if err != nil { + return err + } + + i, err := installer.FindSource(absExactLocation, home) + if err != nil { + return err + } + if err := installer.Update(i); err != nil { + return err + } + + debug("loading plugin from %s", i.Path()) + updatedPlugin, err := plugin.LoadDir(i.Path()) + if err != nil { + return err + } + + return runHook(updatedPlugin, plugin.Update, home) +} diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index 76a8f40e3..a196f7809 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" ) const releaseTestDesc = ` @@ -75,16 +76,35 @@ func (t *releaseTestCmd) run() (err error) { helm.ReleaseTestTimeout(t.timeout), helm.ReleaseTestCleanup(t.cleanup), ) + testErr := &testErr{} for { select { case err := <-errc: + if prettyError(err) == nil && testErr.failed > 0 { + return testErr.Error() + } return prettyError(err) case res, ok := <-c: if !ok { break } + + if res.Status == release.TestRun_FAILURE { + testErr.failed++ + } + fmt.Fprintf(t.out, res.Msg+"\n") + } } + +} + +type testErr struct { + failed int +} + +func (err *testErr) Error() error { + return fmt.Errorf("%v test(s) failed", err.failed) } diff --git a/cmd/helm/release_testing_test.go b/cmd/helm/release_testing_test.go new file mode 100644 index 000000000..2c7a5867a --- /dev/null +++ b/cmd/helm/release_testing_test.go @@ -0,0 +1,105 @@ +/* +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 main + +import ( + "bytes" + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestReleaseTesting(t *testing.T) { + tests := []struct { + name string + args []string + flags []string + responses map[string]release.TestRun_Status + fail bool + }{ + { + name: "basic test", + args: []string{"example-release"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"PASSED: green lights everywhere": release.TestRun_SUCCESS}, + fail: false, + }, + { + name: "test failure", + args: []string{"example-fail"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"FAILURE: red lights everywhere": release.TestRun_FAILURE}, + fail: true, + }, + { + name: "test unknown", + args: []string{"example-unknown"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"UNKNOWN: yellow lights everywhere": release.TestRun_UNKNOWN}, + fail: false, + }, + { + name: "test error", + args: []string{"example-error"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"ERROR: yellow lights everywhere": release.TestRun_FAILURE}, + fail: true, + }, + { + name: "test running", + args: []string{"example-running"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"RUNNING: things are happpeningggg": release.TestRun_RUNNING}, + fail: false, + }, + { + name: "multiple tests example", + args: []string{"example-suite"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{ + "RUNNING: things are happpeningggg": release.TestRun_RUNNING, + "PASSED: party time": release.TestRun_SUCCESS, + "RUNNING: things are happening again": release.TestRun_RUNNING, + "FAILURE: good thing u checked :)": release.TestRun_FAILURE, + "RUNNING: things are happpeningggg yet again": release.TestRun_RUNNING, + "PASSED: feel free to party again": release.TestRun_SUCCESS}, + fail: true, + }, + } + + for _, tt := range tests { + c := &fakeReleaseClient{responses: tt.responses} + + buf := bytes.NewBuffer(nil) + cmd := newReleaseTestCmd(c, buf) + cmd.ParseFlags(tt.flags) + + err := cmd.RunE(cmd, tt.args) + if err == nil && tt.fail { + t.Errorf("%q did not fail but should have failed", tt.name) + } + + if err != nil { + if tt.fail { + continue + } else { + t.Errorf("%q reported error: %s", tt.name, err) + } + } + + } +} diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 314a31ae2..1bdbfddb1 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -85,7 +85,7 @@ func addRepository(name, url string, home helmpath.Home, certFile, keyFile, caFi } if noUpdate && f.Has(name) { - return fmt.Errorf("The repository name you provided (%s) already exists. Please specify a different name.", name) + return fmt.Errorf("repository name (%s) already exists, please specify a different name", name) } cif := home.CacheIndex(name) diff --git a/cmd/helm/reset.go b/cmd/helm/reset.go index c37e3d687..56a216735 100644 --- a/cmd/helm/reset.go +++ b/cmd/helm/reset.go @@ -54,10 +54,15 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command { } cmd := &cobra.Command{ - Use: "reset", - Short: "uninstalls Tiller from a cluster", - Long: resetDesc, - PersistentPreRunE: setupConnection, + Use: "reset", + Short: "uninstalls Tiller from a cluster", + Long: resetDesc, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := setupConnection(cmd, args); !d.force && err != nil { + return err + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 0 { return errors.New("This command does not accept arguments") @@ -72,7 +77,7 @@ func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command { } f := cmd.Flags() - f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed") + f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if tiller is not in ready state") f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME") return cmd @@ -91,12 +96,12 @@ func (d *resetCmd) run() error { res, err := d.client.ListReleases( helm.ReleaseListStatuses([]release.Status_Code{release.Status_DEPLOYED}), ) - if err != nil { + if !d.force && err != nil { return prettyError(err) } - if len(res.Releases) > 0 && !d.force { - return fmt.Errorf("There are still %d deployed releases (Tip: use --force).", len(res.Releases)) + if !d.force && res != nil && len(res.Releases) > 0 { + return fmt.Errorf("there are still %d deployed releases (Tip: use --force)", len(res.Releases)) } if err := installer.Uninstall(d.kubeClient, &installer.Options{Namespace: d.namespace}); err != nil { diff --git a/cmd/helm/reset_test.go b/cmd/helm/reset_test.go index 855b4c5bd..5df8f2234 100644 --- a/cmd/helm/reset_test.go +++ b/cmd/helm/reset_test.go @@ -120,7 +120,7 @@ func TestReset_deployedReleases(t *testing.T) { namespace: api.NamespaceDefault, } err = cmd.run() - expected := "There are still 1 deployed releases (Tip: use --force)" + expected := "there are still 1 deployed releases (Tip: use --force)" if !strings.Contains(err.Error(), expected) { t.Errorf("unexpected error: %v", err) } diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index bcc780c1d..b6c031c56 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -39,6 +39,7 @@ type rollbackCmd struct { revision int32 dryRun bool recreate bool + force bool disableHooks bool out io.Writer client helm.Interface @@ -78,6 +79,7 @@ func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") + f.BoolVar(&rollback.force, "force", false, "force resource update through delete/recreate if needed") f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)") f.BoolVar(&rollback.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") @@ -90,6 +92,7 @@ func (r *rollbackCmd) run() error { r.name, helm.RollbackDryRun(r.dryRun), helm.RollbackRecreate(r.recreate), + helm.RollbackForce(r.force), helm.RollbackDisableHooks(r.disableHooks), helm.RollbackVersion(r.revision), helm.RollbackTimeout(r.timeout), diff --git a/cmd/helm/serve.go b/cmd/helm/serve.go index 028af9751..1c9a772fe 100644 --- a/cmd/helm/serve.go +++ b/cmd/helm/serve.go @@ -55,19 +55,29 @@ func newServeCmd(out io.Writer) *cobra.Command { Use: "serve", Short: "start a local http web server", Long: serveDesc, + PreRunE: func(cmd *cobra.Command, args []string) error { + return srv.complete(args) + }, RunE: func(cmd *cobra.Command, args []string) error { return srv.run() }, } f := cmd.Flags() - f.StringVar(&srv.repoPath, "repo-path", settings.Home.LocalRepository(), "local directory path from which to serve charts") + f.StringVar(&srv.repoPath, "repo-path", "", "local directory path from which to serve charts") f.StringVar(&srv.address, "address", "127.0.0.1:8879", "address to listen on") f.StringVar(&srv.url, "url", "", "external URL of chart repository") return cmd } +func (s *serveCmd) complete(args []string) error { + if s.repoPath == "" { + s.repoPath = settings.Home.LocalRepository() + } + return nil +} + func (s *serveCmd) run() error { repoPath, err := filepath.Abs(s.repoPath) if err != nil { diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/.helmignore b/cmd/helm/testdata/testcharts/chart-bad-requirements/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-bad-requirements/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/Chart.yaml b/cmd/helm/testdata/testcharts/chart-bad-requirements/Chart.yaml new file mode 100644 index 000000000..02be4c013 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-bad-requirements/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: chart-missing-deps +version: 0.1.0 diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore b/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml b/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml new file mode 100644 index 000000000..c3813bc8c --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: reqsubchart +version: 0.1.0 diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml b/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml new file mode 100644 index 000000000..0f0b63f2a --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqsubchart. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/requirements.yaml b/cmd/helm/testdata/testcharts/chart-bad-requirements/requirements.yaml new file mode 100644 index 000000000..10c4d6dcb --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-bad-requirements/requirements.yaml @@ -0,0 +1,4 @@ +dependencies: + - name: reqsubchart + version: 0.1.0 + repository: "https://example.com/charts" diff --git a/cmd/helm/testdata/testcharts/chart-bad-requirements/values.yaml b/cmd/helm/testdata/testcharts/chart-bad-requirements/values.yaml new file mode 100644 index 000000000..d57f76b07 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-bad-requirements/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqtest. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 886589265..3c0a780f1 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -62,6 +62,7 @@ type upgradeCmd struct { client helm.Interface dryRun bool recreate bool + force bool disableHooks bool valueFiles valueFiles values []string @@ -75,6 +76,7 @@ type upgradeCmd struct { reuseValues bool wait bool repoURL string + devel bool certFile string keyFile string @@ -98,6 +100,11 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { return err } + if upgrade.version == "" && upgrade.devel { + debug("setting version to >0.0.0-a") + upgrade.version = ">0.0.0-a" + } + upgrade.release = args[0] upgrade.chart = args[1] upgrade.client = ensureHelmClient(upgrade.client) @@ -110,6 +117,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") + f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed") f.StringArrayVar(&upgrade.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks") f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks") @@ -126,6 +134,7 @@ func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored.") f.MarkDeprecated("disable-hooks", "use --no-hooks instead") @@ -178,7 +187,11 @@ func (u *upgradeCmd) run() error { if err := checkDependencies(ch, req, u.out); err != nil { return err } + } else if err != chartutil.ErrRequirementsNotFound { + return fmt.Errorf("cannot load requirements: %v", err) } + } else { + return prettyError(err) } resp, err := u.client.UpdateRelease( @@ -187,6 +200,7 @@ func (u *upgradeCmd) run() error { helm.UpdateValueOverrides(rawVals), helm.UpgradeDryRun(u.dryRun), helm.UpgradeRecreate(u.recreate), + helm.UpgradeForce(u.force), helm.UpgradeDisableHooks(u.disableHooks), helm.UpgradeTimeout(u.timeout), helm.ResetValues(u.resetValues), diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 9ee1ae585..d9bff45bb 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -82,6 +82,7 @@ func TestUpgradeCmd(t *testing.T) { originalDepsPath := filepath.Join("testdata/testcharts/reqtest") missingDepsPath := filepath.Join("testdata/testcharts/chart-missing-deps") + badDepsPath := filepath.Join("testdata/testcharts/chart-bad-requirements") var ch3 *chart.Chart ch3, err = chartutil.Load(originalDepsPath) if err != nil { @@ -143,6 +144,12 @@ func TestUpgradeCmd(t *testing.T) { resp: releaseMock(&releaseOptions{name: "bonkers-bunny", version: 1, chart: ch3}), err: true, }, + { + name: "upgrade a release with bad dependencies", + args: []string{"bonkers-bunny", badDepsPath}, + resp: releaseMock(&releaseOptions{name: "bonkers-bunny", version: 1, chart: ch3}), + err: true, + }, } cmd := func(c *fakeReleaseClient, out io.Writer) *cobra.Command { diff --git a/cmd/rudder/rudder.go b/cmd/rudder/rudder.go index 0a5a97bff..e1ba4736a 100644 --- a/cmd/rudder/rudder.go +++ b/cmd/rudder/rudder.go @@ -112,7 +112,7 @@ func (r *ReleaseModuleServiceServer) RollbackRelease(ctx context.Context, in *ru grpclog.Print("rollback") c := bytes.NewBufferString(in.Current.Manifest) t := bytes.NewBufferString(in.Target.Manifest) - err := kubeClient.Update(in.Target.Namespace, c, t, in.Recreate, in.Timeout, in.Wait) + err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait) return &rudderAPI.RollbackReleaseResponse{}, err } @@ -121,11 +121,12 @@ func (r *ReleaseModuleServiceServer) UpgradeRelease(ctx context.Context, in *rud grpclog.Print("upgrade") c := bytes.NewBufferString(in.Current.Manifest) t := bytes.NewBufferString(in.Target.Manifest) - err := kubeClient.Update(in.Target.Namespace, c, t, in.Recreate, in.Timeout, in.Wait) + err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait) // upgrade response object should be changed to include status return &rudderAPI.UpgradeReleaseResponse{}, err } +// ReleaseStatus retrieves release status func (r *ReleaseModuleServiceServer) ReleaseStatus(ctx context.Context, in *rudderAPI.ReleaseStatusRequest) (*rudderAPI.ReleaseStatusResponse, error) { grpclog.Print("status") diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index e5f3a33f3..6abd35ec9 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -29,6 +29,7 @@ import ( goprom "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/spf13/cobra" + "github.com/spf13/pflag" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -69,6 +70,8 @@ var rootServer *grpc.Server // Any changes to env should be done before rootServer.Serve() is called. var env = environment.New() +var logger *log.Logger + var ( grpcAddr = ":44134" probeAddr = ":44135" @@ -93,64 +96,83 @@ Tiller is the server for Helm. It provides in-cluster resource management. By default, Tiller listens for gRPC connections on port 44134. ` -var rootCommand = &cobra.Command{ - Use: "tiller", - Short: "The Kubernetes Helm server.", - Long: globalUsage, - Run: start, +func addFlags(flags *pflag.FlagSet) { + flags.StringVarP(&grpcAddr, "listen", "l", ":44134", "address:port to listen on") + flags.StringVar(&store, "storage", storageConfigMap, "storage driver to use. One of 'configmap' or 'memory'") + flags.BoolVar(&enableTracing, "trace", false, "enable rpc tracing") + flags.BoolVar(&remoteReleaseModules, "experimental-release", false, "enable experimental release modules") + + flags.BoolVar(&tlsEnable, "tls", tlsEnableEnvVarDefault(), "enable TLS") + flags.BoolVar(&tlsVerify, "tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate") + flags.StringVar(&keyFile, "tls-key", tlsDefaultsFromEnv("tls-key"), "path to TLS private key file") + flags.StringVar(&certFile, "tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file") + flags.StringVar(&caCertFile, "tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA") } -func init() { - log.SetFlags(log.Flags() | log.Lshortfile) +func initLog() { + if enableTracing { + log.SetFlags(log.Lshortfile) + } + logger = newLogger("main") } func main() { - p := rootCommand.PersistentFlags() - p.StringVarP(&grpcAddr, "listen", "l", ":44134", "address:port to listen on") - p.StringVar(&store, "storage", storageConfigMap, "storage driver to use. One of 'configmap' or 'memory'") - p.BoolVar(&enableTracing, "trace", false, "enable rpc tracing") - p.BoolVar(&remoteReleaseModules, "experimental-release", false, "enable experimental release modules") - - p.BoolVar(&tlsEnable, "tls", tlsEnableEnvVarDefault(), "enable TLS") - p.BoolVar(&tlsVerify, "tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate") - p.StringVar(&keyFile, "tls-key", tlsDefaultsFromEnv("tls-key"), "path to TLS private key file") - p.StringVar(&certFile, "tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file") - p.StringVar(&caCertFile, "tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA") - - if err := rootCommand.Execute(); err != nil { - fmt.Fprint(os.Stderr, err) - os.Exit(1) + root := &cobra.Command{ + Use: "tiller", + Short: "The Kubernetes Helm server.", + Long: globalUsage, + Run: start, + PreRun: func(_ *cobra.Command, _ []string) { + initLog() + }, + } + addFlags(root.Flags()) + + if err := root.Execute(); err != nil { + logger.Fatal(err) } } +func newLogger(prefix string) *log.Logger { + if len(prefix) > 0 { + prefix = fmt.Sprintf("[%s] ", prefix) + } + return log.New(os.Stderr, prefix, log.Flags()) +} + func start(c *cobra.Command, args []string) { clientset, err := kube.New(nil).ClientSet() if err != nil { - fmt.Fprintf(os.Stderr, "Cannot initialize Kubernetes connection: %s\n", err) - os.Exit(1) + logger.Fatalf("Cannot initialize Kubernetes connection: %s", err) } switch store { case storageMemory: env.Releases = storage.Init(driver.NewMemory()) case storageConfigMap: - env.Releases = storage.Init(driver.NewConfigMaps(clientset.Core().ConfigMaps(namespace()))) + cfgmaps := driver.NewConfigMaps(clientset.Core().ConfigMaps(namespace())) + cfgmaps.Log = newLogger("storage/driver").Printf + + env.Releases = storage.Init(cfgmaps) + env.Releases.Log = newLogger("storage").Printf } + kubeClient := kube.New(nil) + kubeClient.Log = newLogger("kube").Printf + env.KubeClient = kubeClient + if tlsEnable || tlsVerify { opts := tlsutil.Options{CertFile: certFile, KeyFile: keyFile} if tlsVerify { opts.CaCertFile = caCertFile } - } var opts []grpc.ServerOption if tlsEnable || tlsVerify { cfg, err := tlsutil.ServerConfig(tlsOptions()) if err != nil { - fmt.Fprintf(os.Stderr, "Could not create server TLS configuration: %v\n", err) - os.Exit(1) + logger.Fatalf("Could not create server TLS configuration: %v", err) } opts = append(opts, grpc.Creds(credentials.NewTLS(cfg))) } @@ -159,14 +181,13 @@ func start(c *cobra.Command, args []string) { lstn, err := net.Listen("tcp", grpcAddr) if err != nil { - fmt.Fprintf(os.Stderr, "Server died: %s\n", err) - os.Exit(1) + logger.Fatalf("Server died: %s", err) } - fmt.Printf("Starting Tiller %s (tls=%t)\n", version.GetVersion(), tlsEnable || tlsVerify) - fmt.Printf("GRPC listening on %s\n", grpcAddr) - fmt.Printf("Probes listening on %s\n", probeAddr) - fmt.Printf("Storage driver is %s\n", env.Releases.Name()) + logger.Printf("Starting Tiller %s (tls=%t)", version.GetVersion(), tlsEnable || tlsVerify) + logger.Printf("GRPC listening on %s", grpcAddr) + logger.Printf("Probes listening on %s", probeAddr) + logger.Printf("Storage driver is %s", env.Releases.Name()) if enableTracing { startTracing(traceAddr) @@ -176,6 +197,7 @@ func start(c *cobra.Command, args []string) { probeErrCh := make(chan error) go func() { svc := tiller.NewReleaseServer(env, clientset, remoteReleaseModules) + svc.Log = newLogger("tiller").Printf services.RegisterReleaseServiceServer(rootServer, svc) if err := rootServer.Serve(lstn); err != nil { srvErrCh <- err @@ -196,10 +218,9 @@ func start(c *cobra.Command, args []string) { select { case err := <-srvErrCh: - fmt.Fprintf(os.Stderr, "Server died: %s\n", err) - os.Exit(1) + logger.Fatalf("Server died: %s", err) case err := <-probeErrCh: - fmt.Fprintf(os.Stderr, "Probes server died: %s\n", err) + logger.Printf("Probes server died: %s", err) } } diff --git a/cmd/tiller/trace.go b/cmd/tiller/trace.go index b9e0583f2..71d7e8f72 100644 --- a/cmd/tiller/trace.go +++ b/cmd/tiller/trace.go @@ -17,8 +17,6 @@ limitations under the License. package main // import "k8s.io/helm/cmd/tiller" import ( - "fmt" - "log" "net/http" _ "net/http/pprof" @@ -27,7 +25,7 @@ import ( ) func startTracing(addr string) { - fmt.Printf("Tracing server is listening on %s\n", addr) + logger.Printf("Tracing server is listening on %s\n", addr) grpc.EnableTracing = true http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { @@ -41,7 +39,7 @@ func startTracing(addr string) { go func() { if err := http.ListenAndServe(addr, nil); err != nil { - log.Printf("tracing error: %s", err) + logger.Printf("tracing error: %s", err) } }() } diff --git a/docs/chart_repository_faq.md b/docs/chart_repository_faq.md new file mode 100644 index 000000000..e11194b23 --- /dev/null +++ b/docs/chart_repository_faq.md @@ -0,0 +1,15 @@ +# Chart Repositories: Frequently Asked Questions + +This section tracks some of the more frequently encountered issues with using chart repositories. + +**We'd love your help** making this document better. To add, correct, or remove +information, [file an issue](https://github.com/kubernetes/helm/issues) or +send us a pull request. + +## Fetching + +**Q: Why do I get a `unsupported protocol scheme ""` error when trying to fetch a chart from my custom repo?** + +A: This is likely caused by you creating your chart repo index without specifying the `--url` flag. +Try recreating your `index.yaml` file with a command like `heml repo index --url http://my-repo/charts .`, +and then re-uploading it to your custom charts repo. diff --git a/docs/chart_template_guide/control_structures.md b/docs/chart_template_guide/control_structures.md index 8723aacef..7575ebc35 100644 --- a/docs/chart_template_guide/control_structures.md +++ b/docs/chart_template_guide/control_structures.md @@ -170,7 +170,7 @@ data: {{- end}} ``` -Just for the same of making this point clear, let's adjust the above, and substitute an `*` for each whitespace that will be deleted following this rule. an `*` at the end of the line indicates a newline character that would be removed +Just for the sake of making this point clear, let's adjust the above, and substitute an `*` for each whitespace that will be deleted following this rule. an `*` at the end of the line indicates a newline character that would be removed ```yaml apiVersion: v1 diff --git a/docs/chart_tests.md b/docs/chart_tests.md index cb25d5dfc..4311ffaeb 100644 --- a/docs/chart_tests.md +++ b/docs/chart_tests.md @@ -1,8 +1,8 @@ # Chart Tests -A chart contains a number of Kubernetes resources and components that work together. As a chart author, you may want to write some tests that validate that your charts works as expected when it is installed. These tests also help the chart consumer understand what your chart is supposed to do. +A chart contains a number of Kubernetes resources and components that work together. As a chart author, you may want to write some tests that validate that your chart works as expected when it is installed. These tests also help the chart consumer understand what your chart is supposed to do. -A **test** in a helm chart lives under the `templates/` directory and is a pod definition that specifies a container with a given command to run. The container should exist successfully (exit 0) for a test to be considered a success. The pod definiton must contain one of the helm test hook annotations: `helm.sh/hooks: test-success` or `helm.sh/hooks: test-failure`. +A **test** in a helm chart lives under the `templates/` directory and is a pod definition that specifies a container with a given command to run. The container should exit successfully (exit 0) for a test to be considered a success. The pod definiton must contain one of the helm test hook annotations: `helm.sh/hooks: test-success` or `helm.sh/hooks: test-failure`. Example tests: - Validate that your configuration from the values.yaml file was properly injected. @@ -17,12 +17,12 @@ You can run the pre-defined tests in Helm on a release using the command `helm t In Helm, there are two test hooks: `test-success` and `test-failure` -`test-success` indiciates that test pod should complete successfully. In other words, the containers in the pod should exit 0. -`test-failure` is a way to assert that a test pod should not complete successfully. If the containers in the pod do not exit 0, that indiciates success. +`test-success` indicates that test pod should complete successfully. In other words, the containers in the pod should exit 0. +`test-failure` is a way to assert that a test pod should not complete successfully. If the containers in the pod do not exit 0, that indicates success. ## Example Test -Here is an example of a helm test pod definition in an example maraidb chart: +Here is an example of a helm test pod definition in an example mariadb chart: ``` mariadb/ diff --git a/docs/charts.md b/docs/charts.md index b1aa10ad4..d63df1720 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -229,6 +229,38 @@ Managing charts with `requirements.yaml` is a good way to easily keep charts updated, and also share requirements information throughout a team. +#### Alias field in requirements.yaml + +In addition to the other fields above, each requirements entry may contain +the optional field `alias`. + +Adding an alias for a dependency chart would add another copy +of the chart as a new depdendency using alias as name of new dependency. + +One can use `alias` in cases where they need multiple copies of same chart +as dependencies all independent of one another. + +```` +# parentchart/requirements.yaml +dependencies: + - name: subchart + repository: http://localhost:10191 + version: 0.1.0 + alias: + - one-more-subchart + - another-subchart +```` + +In the above example we will get 3 depenendencies in all for `parentchart` +``` +subchart +one-more-subchart +another-subchart +``` + +Manual way of achieving this is copy/pasting same chart in +`charts/` directory multiple times with different name. + #### Tags and Condition fields in requirements.yaml In addition to the other fields above, each requirements entry may contain @@ -687,17 +719,6 @@ parent chart. Also, global variables of parent charts take precedence over the global variables from subcharts. -_Global sections are restricted to only simple key/value pairs. They do -not support nesting._ - -For example, the following is **illegal** and will not work: - -```yaml -global: - foo: # It is illegal to nest an object inside of global. - bar: baz -``` - ### References When it comes to writing templates and values files, there are several diff --git a/docs/developers.md b/docs/developers.md index 163ba1b9d..e0aeb374a 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -174,6 +174,7 @@ Common commit types: - feat: Add a new feature - docs: Change documentation - test: Improve testing +- ref: refactor existing code Common scopes: diff --git a/docs/examples/alpine/README.md b/docs/examples/alpine/README.md index 3c32de5db..eb4fb9571 100644 --- a/docs/examples/alpine/README.md +++ b/docs/examples/alpine/README.md @@ -1,4 +1,4 @@ -#Alpine: A simple Helm chart +# Alpine: A simple Helm chart Run a single pod of Alpine Linux. diff --git a/docs/helm/helm.md b/docs/helm/helm.md index 135d5f995..4cc3289d3 100644 --- a/docs/helm/helm.md +++ b/docs/helm/helm.md @@ -66,4 +66,4 @@ Environment: * [helm verify](helm_verify.md) - verify that a chart at the given path has been signed and is valid * [helm version](helm_version.md) - print the client/server version information -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_completion.md b/docs/helm/helm_completion.md index 463f6bc47..7b63a91d8 100644 --- a/docs/helm/helm_completion.md +++ b/docs/helm/helm_completion.md @@ -34,4 +34,4 @@ helm completion SHELL ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_create.md b/docs/helm/helm_create.md index 442becc87..79704c3d1 100644 --- a/docs/helm/helm_create.md +++ b/docs/helm/helm_create.md @@ -53,4 +53,4 @@ helm create NAME ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_delete.md b/docs/helm/helm_delete.md index 97620edaa..cee3bcb87 100644 --- a/docs/helm/helm_delete.md +++ b/docs/helm/helm_delete.md @@ -44,4 +44,4 @@ helm delete [flags] RELEASE_NAME [...] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_dependency.md b/docs/helm/helm_dependency.md index 69417bc24..c824c73b3 100644 --- a/docs/helm/helm_dependency.md +++ b/docs/helm/helm_dependency.md @@ -70,4 +70,4 @@ for this case. * [helm dependency list](helm_dependency_list.md) - list the dependencies for the given chart * [helm dependency update](helm_dependency_update.md) - update charts/ based on the contents of requirements.yaml -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_dependency_build.md b/docs/helm/helm_dependency_build.md index 4cd149444..2bd2cb3ed 100644 --- a/docs/helm/helm_dependency_build.md +++ b/docs/helm/helm_dependency_build.md @@ -40,4 +40,4 @@ helm dependency build [flags] CHART ### SEE ALSO * [helm dependency](helm_dependency.md) - manage a chart's dependencies -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_dependency_list.md b/docs/helm/helm_dependency_list.md index e0223c07e..57f89c20c 100644 --- a/docs/helm/helm_dependency_list.md +++ b/docs/helm/helm_dependency_list.md @@ -32,4 +32,4 @@ helm dependency list [flags] CHART ### SEE ALSO * [helm dependency](helm_dependency.md) - manage a chart's dependencies -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_dependency_update.md b/docs/helm/helm_dependency_update.md index 553d1f831..6c07aaf54 100644 --- a/docs/helm/helm_dependency_update.md +++ b/docs/helm/helm_dependency_update.md @@ -45,4 +45,4 @@ helm dependency update [flags] CHART ### SEE ALSO * [helm dependency](helm_dependency.md) - manage a chart's dependencies -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_fetch.md b/docs/helm/helm_fetch.md index e49909e78..b6a424569 100644 --- a/docs/helm/helm_fetch.md +++ b/docs/helm/helm_fetch.md @@ -30,6 +30,7 @@ helm fetch [flags] [chart URL | repo/chartname] [...] --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --cert-file string identify HTTPS client using this SSL certificate file -d, --destination string location to write the chart. If this and tardir are specified, tardir is appended to this (default ".") + --devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. --key-file string identify HTTPS client using this SSL key file --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") --prov fetch the provenance file, but don't perform verification @@ -53,4 +54,4 @@ helm fetch [flags] [chart URL | repo/chartname] [...] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 24-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_get.md b/docs/helm/helm_get.md index d67af8388..a8c3699a1 100644 --- a/docs/helm/helm_get.md +++ b/docs/helm/helm_get.md @@ -49,4 +49,4 @@ helm get [flags] RELEASE_NAME * [helm get manifest](helm_get_manifest.md) - download the manifest for a named release * [helm get values](helm_get_values.md) - download the values file for a named release -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_get_hooks.md b/docs/helm/helm_get_hooks.md index 9e4cbbaca..9c15ab3dc 100644 --- a/docs/helm/helm_get_hooks.md +++ b/docs/helm/helm_get_hooks.md @@ -34,4 +34,4 @@ helm get hooks [flags] RELEASE_NAME ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_get_manifest.md b/docs/helm/helm_get_manifest.md index aa68c4de8..f4346ea9e 100644 --- a/docs/helm/helm_get_manifest.md +++ b/docs/helm/helm_get_manifest.md @@ -36,4 +36,4 @@ helm get manifest [flags] RELEASE_NAME ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_get_values.md b/docs/helm/helm_get_values.md index 67568c6a8..f86b7e574 100644 --- a/docs/helm/helm_get_values.md +++ b/docs/helm/helm_get_values.md @@ -33,4 +33,4 @@ helm get values [flags] RELEASE_NAME ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_history.md b/docs/helm/helm_history.md index 1852c6616..e735a1f1c 100644 --- a/docs/helm/helm_history.md +++ b/docs/helm/helm_history.md @@ -49,4 +49,4 @@ helm history [flags] RELEASE_NAME ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_home.md b/docs/helm/helm_home.md index d86f3ce80..b2b37b0b4 100644 --- a/docs/helm/helm_home.md +++ b/docs/helm/helm_home.md @@ -27,4 +27,4 @@ helm home ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_init.md b/docs/helm/helm_init.md index e21121dcd..5329fde3b 100644 --- a/docs/helm/helm_init.md +++ b/docs/helm/helm_init.md @@ -63,4 +63,4 @@ helm init ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 1-May-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_inspect.md b/docs/helm/helm_inspect.md index c6ad0dcbd..9b72f646d 100644 --- a/docs/helm/helm_inspect.md +++ b/docs/helm/helm_inspect.md @@ -43,4 +43,4 @@ helm inspect [CHART] * [helm inspect chart](helm_inspect_chart.md) - shows inspect chart * [helm inspect values](helm_inspect_values.md) - shows inspect values -###### Auto generated by spf13/cobra on 24-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_inspect_chart.md b/docs/helm/helm_inspect_chart.md index 89094d1cd..178702d7b 100644 --- a/docs/helm/helm_inspect_chart.md +++ b/docs/helm/helm_inspect_chart.md @@ -39,4 +39,4 @@ helm inspect chart [CHART] ### SEE ALSO * [helm inspect](helm_inspect.md) - inspect a chart -###### Auto generated by spf13/cobra on 24-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_inspect_values.md b/docs/helm/helm_inspect_values.md index 86b2c74c1..e388e3c7a 100644 --- a/docs/helm/helm_inspect_values.md +++ b/docs/helm/helm_inspect_values.md @@ -39,4 +39,4 @@ helm inspect values [CHART] ### SEE ALSO * [helm inspect](helm_inspect.md) - inspect a chart -###### Auto generated by spf13/cobra on 24-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_install.md b/docs/helm/helm_install.md index edbdcb6a9..859df2ef1 100644 --- a/docs/helm/helm_install.md +++ b/docs/helm/helm_install.md @@ -70,6 +70,7 @@ helm install [CHART] ``` --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --cert-file string identify HTTPS client using this SSL certificate file + --devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. --dry-run simulate an install --key-file string identify HTTPS client using this SSL key file --keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg") @@ -105,4 +106,4 @@ helm install [CHART] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 24-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_lint.md b/docs/helm/helm_lint.md index 00f287569..a7fad58d1 100644 --- a/docs/helm/helm_lint.md +++ b/docs/helm/helm_lint.md @@ -37,4 +37,4 @@ helm lint [flags] PATH ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_list.md b/docs/helm/helm_list.md index 26e4ffcd1..5ccfdfdca 100644 --- a/docs/helm/helm_list.md +++ b/docs/helm/helm_list.md @@ -70,4 +70,4 @@ helm list [flags] [FILTER] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_package.md b/docs/helm/helm_package.md index d7afd118b..a7b69f6b9 100644 --- a/docs/helm/helm_package.md +++ b/docs/helm/helm_package.md @@ -44,4 +44,4 @@ helm package [flags] [CHART_PATH] [...] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_plugin.md b/docs/helm/helm_plugin.md index 96d474dea..d4d0ee08b 100644 --- a/docs/helm/helm_plugin.md +++ b/docs/helm/helm_plugin.md @@ -24,5 +24,6 @@ Manage client-side Helm plugins. * [helm plugin install](helm_plugin_install.md) - install one or more Helm plugins * [helm plugin list](helm_plugin_list.md) - list installed Helm plugins * [helm plugin remove](helm_plugin_remove.md) - remove one or more Helm plugins +* [helm plugin update](helm_plugin_update.md) - update one or more Helm plugins -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_plugin_install.md b/docs/helm/helm_plugin_install.md index 8480eb2ed..13581c055 100644 --- a/docs/helm/helm_plugin_install.md +++ b/docs/helm/helm_plugin_install.md @@ -30,4 +30,4 @@ helm plugin install [options] ... ### SEE ALSO * [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_plugin_list.md b/docs/helm/helm_plugin_list.md index cfa321706..4448a5a04 100644 --- a/docs/helm/helm_plugin_list.md +++ b/docs/helm/helm_plugin_list.md @@ -24,4 +24,4 @@ helm plugin list ### SEE ALSO * [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_plugin_remove.md b/docs/helm/helm_plugin_remove.md index 47c16f6e8..4ae04b286 100644 --- a/docs/helm/helm_plugin_remove.md +++ b/docs/helm/helm_plugin_remove.md @@ -24,4 +24,4 @@ helm plugin remove ... ### SEE ALSO * [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_plugin_update.md b/docs/helm/helm_plugin_update.md new file mode 100644 index 000000000..6f59cd4b2 --- /dev/null +++ b/docs/helm/helm_plugin_update.md @@ -0,0 +1,27 @@ +## helm plugin update + +update one or more Helm plugins + +### Synopsis + + +update one or more Helm plugins + +``` +helm plugin update ... +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-namespace string namespace of tiller (default "kube-system") +``` + +### SEE ALSO +* [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins + +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_repo.md b/docs/helm/helm_repo.md index 8e5d0da57..d0d015569 100644 --- a/docs/helm/helm_repo.md +++ b/docs/helm/helm_repo.md @@ -31,4 +31,4 @@ Example usage: * [helm repo remove](helm_repo_remove.md) - remove a chart repository * [helm repo update](helm_repo_update.md) - update information on available charts in the chart repositories -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_repo_add.md b/docs/helm/helm_repo_add.md index 5567fe415..c7b2a41ef 100644 --- a/docs/helm/helm_repo_add.md +++ b/docs/helm/helm_repo_add.md @@ -33,4 +33,4 @@ helm repo add [flags] [NAME] [URL] ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_repo_index.md b/docs/helm/helm_repo_index.md index a98ecbffa..7a33052a3 100644 --- a/docs/helm/helm_repo_index.md +++ b/docs/helm/helm_repo_index.md @@ -40,4 +40,4 @@ helm repo index [flags] [DIR] ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_repo_list.md b/docs/helm/helm_repo_list.md index e5138458c..7966592cb 100644 --- a/docs/helm/helm_repo_list.md +++ b/docs/helm/helm_repo_list.md @@ -24,4 +24,4 @@ helm repo list [flags] ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_repo_remove.md b/docs/helm/helm_repo_remove.md index 0d1b1d06f..705fd8e89 100644 --- a/docs/helm/helm_repo_remove.md +++ b/docs/helm/helm_repo_remove.md @@ -24,4 +24,4 @@ helm repo remove [flags] [NAME] ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_repo_update.md b/docs/helm/helm_repo_update.md index e3787cd56..b54ad3dda 100644 --- a/docs/helm/helm_repo_update.md +++ b/docs/helm/helm_repo_update.md @@ -30,4 +30,4 @@ helm repo update ### SEE ALSO * [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_reset.md b/docs/helm/helm_reset.md index e07f3a707..1d6fea9c9 100644 --- a/docs/helm/helm_reset.md +++ b/docs/helm/helm_reset.md @@ -18,7 +18,7 @@ helm reset ### Options ``` - -f, --force forces Tiller uninstall even if there are releases installed + -f, --force forces Tiller uninstall even if there are releases installed, or if tiller is not in ready state --remove-helm-home if set deletes $HELM_HOME --tls enable TLS for request --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") @@ -40,4 +40,4 @@ helm reset ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_rollback.md b/docs/helm/helm_rollback.md index a5500008c..7e59a67c8 100644 --- a/docs/helm/helm_rollback.md +++ b/docs/helm/helm_rollback.md @@ -21,6 +21,7 @@ helm rollback [flags] [RELEASE] [REVISION] ``` --dry-run simulate a rollback + --force force resource update through delete/recreate if needed --no-hooks prevent hooks from running during rollback --recreate-pods performs pods restart for the resource if applicable --timeout int time in seconds to wait for any individual kubernetes operation (like Jobs for hooks) (default 300) @@ -45,4 +46,4 @@ helm rollback [flags] [RELEASE] [REVISION] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_search.md b/docs/helm/helm_search.md index 6f1f8a645..68f2f16be 100644 --- a/docs/helm/helm_search.md +++ b/docs/helm/helm_search.md @@ -37,4 +37,4 @@ helm search [keyword] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 18-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_serve.md b/docs/helm/helm_serve.md index 7c928c9a0..71aaf26c5 100644 --- a/docs/helm/helm_serve.md +++ b/docs/helm/helm_serve.md @@ -28,7 +28,7 @@ helm serve ``` --address string address to listen on (default "127.0.0.1:8879") - --repo-path string local directory path from which to serve charts (default "~/.helm/repository/local") + --repo-path string local directory path from which to serve charts --url string external URL of chart repository ``` @@ -45,4 +45,4 @@ helm serve ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_status.md b/docs/helm/helm_status.md index 893c3325a..62a99796c 100644 --- a/docs/helm/helm_status.md +++ b/docs/helm/helm_status.md @@ -44,4 +44,4 @@ helm status [flags] RELEASE_NAME ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_test.md b/docs/helm/helm_test.md index 795a81c66..578737578 100644 --- a/docs/helm/helm_test.md +++ b/docs/helm/helm_test.md @@ -41,4 +41,4 @@ helm test [RELEASE] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_upgrade.md b/docs/helm/helm_upgrade.md index 2ac1fd32a..c36c3ae5a 100644 --- a/docs/helm/helm_upgrade.md +++ b/docs/helm/helm_upgrade.md @@ -38,7 +38,9 @@ helm upgrade [RELEASE] [CHART] ``` --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle --cert-file string identify HTTPS client using this SSL certificate file + --devel use development versions, too. Equivalent to version '>0.0.0-a'. If --version is set, this is ignored. --dry-run simulate an upgrade + --force force resource update through delete/recreate if needed -i, --install if a release by this name doesn't already exist, run an install --key-file string identify HTTPS client using this SSL key file --keyring string path to the keyring that contains public signing keys (default "~/.gnupg/pubring.gpg") @@ -74,4 +76,4 @@ helm upgrade [RELEASE] [CHART] ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 24-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_verify.md b/docs/helm/helm_verify.md index 57ff2f191..83c33844b 100644 --- a/docs/helm/helm_verify.md +++ b/docs/helm/helm_verify.md @@ -39,4 +39,4 @@ helm verify [flags] PATH ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/helm/helm_version.md b/docs/helm/helm_version.md index de65607b1..f0aa4f8ca 100644 --- a/docs/helm/helm_version.md +++ b/docs/helm/helm_version.md @@ -53,4 +53,4 @@ helm version ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 16-Apr-2017 +###### Auto generated by spf13/cobra on 26-May-2017 diff --git a/docs/man/man1/helm.1 b/docs/man/man1/helm.1 index 026ca69e4..4128830f4 100644 --- a/docs/man/man1/helm.1 +++ b/docs/man/man1/helm.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -63,7 +63,7 @@ Environment: location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -82,4 +82,4 @@ Environment: .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_completion.1 b/docs/man/man1/helm_completion.1 index 95e267e2d..83217073f 100644 --- a/docs/man/man1/helm_completion.1 +++ b/docs/man/man1/helm_completion.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -52,7 +52,7 @@ $ source <(helm completion bash) location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -71,4 +71,4 @@ $ source <(helm completion bash) .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_create.1 b/docs/man/man1/helm_create.1 index 4261d753f..3a1f4b26f 100644 --- a/docs/man/man1/helm_create.1 +++ b/docs/man/man1/helm_create.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -64,7 +64,7 @@ will be overwritten, but other files will be left alone. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -83,4 +83,4 @@ will be overwritten, but other files will be left alone. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_delete.1 b/docs/man/man1/helm_delete.1 index 3df4e0ded..ecce68398 100644 --- a/docs/man/man1/helm_delete.1 +++ b/docs/man/man1/helm_delete.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -71,7 +71,7 @@ deleting them. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -90,4 +90,4 @@ deleting them. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_dependency.1 b/docs/man/man1/helm_dependency.1 index 9287dcb1f..fd4fc195e 100644 --- a/docs/man/man1/helm_dependency.1 +++ b/docs/man/man1/helm_dependency.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -95,7 +95,7 @@ for this case. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -114,4 +114,4 @@ for this case. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_dependency_build.1 b/docs/man/man1/helm_dependency_build.1 index a42f44a58..adc225a81 100644 --- a/docs/man/man1/helm_dependency_build.1 +++ b/docs/man/man1/helm_dependency_build.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -47,7 +47,7 @@ of 'helm dependency update'. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -66,4 +66,4 @@ of 'helm dependency update'. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_dependency_list.1 b/docs/man/man1/helm_dependency_list.1 index 9061d3ebe..30a686bb4 100644 --- a/docs/man/man1/helm_dependency_list.1 +++ b/docs/man/man1/helm_dependency_list.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -36,7 +36,7 @@ if it cannot find a requirements.yaml. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -55,4 +55,4 @@ if it cannot find a requirements.yaml. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_dependency_update.1 b/docs/man/man1/helm_dependency_update.1 index eebee95d7..02d9ec94b 100644 --- a/docs/man/man1/helm_dependency_update.1 +++ b/docs/man/man1/helm_dependency_update.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -56,7 +56,7 @@ in the requirements.yaml file, but (b) at the wrong version. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -75,4 +75,4 @@ in the requirements.yaml file, but (b) at the wrong version. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_fetch.1 b/docs/man/man1/helm_fetch.1 index 336922468..9a8ad185c 100644 --- a/docs/man/man1/helm_fetch.1 +++ b/docs/man/man1/helm_fetch.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -45,6 +45,10 @@ result in an error, and the chart will not be saved locally. \fB\-d\fP, \fB\-\-destination\fP="." location to write the chart. If this and tardir are specified, tardir is appended to this +.PP +\fB\-\-devel\fP[=false] + use development versions, too. Equivalent to version '>0.0.0\-a'. If \-\-version is set, this is ignored. + .PP \fB\-\-key\-file\fP="" identify HTTPS client using this SSL key file @@ -88,7 +92,7 @@ result in an error, and the chart will not be saved locally. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -107,4 +111,4 @@ result in an error, and the chart will not be saved locally. .SH HISTORY .PP -24\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_get.1 b/docs/man/man1/helm_get.1 index 6a5f96a20..e680f49dc 100644 --- a/docs/man/man1/helm_get.1 +++ b/docs/man/man1/helm_get.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -67,7 +67,7 @@ chart, the supplied values, and the generated manifest file. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -86,4 +86,4 @@ chart, the supplied values, and the generated manifest file. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_get_hooks.1 b/docs/man/man1/helm_get_hooks.1 index 8ba348e54..34e460d19 100644 --- a/docs/man/man1/helm_get_hooks.1 +++ b/docs/man/man1/helm_get_hooks.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -37,7 +37,7 @@ Hooks are formatted in YAML and separated by the YAML '\-\-\-\\n' separator. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -56,4 +56,4 @@ Hooks are formatted in YAML and separated by the YAML '\-\-\-\\n' separator. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_get_manifest.1 b/docs/man/man1/helm_get_manifest.1 index 21fa6363c..7132db38e 100644 --- a/docs/man/man1/helm_get_manifest.1 +++ b/docs/man/man1/helm_get_manifest.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -39,7 +39,7 @@ charts, those resources will also be included in the manifest. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -58,4 +58,4 @@ charts, those resources will also be included in the manifest. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_get_values.1 b/docs/man/man1/helm_get_values.1 index c897e7378..349f97c14 100644 --- a/docs/man/man1/helm_get_values.1 +++ b/docs/man/man1/helm_get_values.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -38,7 +38,7 @@ This command downloads a values file for a given release. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -57,4 +57,4 @@ This command downloads a values file for a given release. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_history.1 b/docs/man/man1/helm_history.1 index cde92fa82..40789ef92 100644 --- a/docs/man/man1/helm_history.1 +++ b/docs/man/man1/helm_history.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -75,7 +75,7 @@ REVISION UPDATED STATUS CHART DESCRIPTIO location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -94,4 +94,4 @@ REVISION UPDATED STATUS CHART DESCRIPTIO .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_home.1 b/docs/man/man1/helm_home.1 index a0887aac4..77024d53e 100644 --- a/docs/man/man1/helm_home.1 +++ b/docs/man/man1/helm_home.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -29,7 +29,7 @@ any helm configuration files live. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -48,4 +48,4 @@ any helm configuration files live. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_init.1 b/docs/man/man1/helm_init.1 index e92c51f76..74871ebe8 100644 --- a/docs/man/man1/helm_init.1 +++ b/docs/man/man1/helm_init.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -113,7 +113,7 @@ To dump a manifest containing the Tiller deployment YAML, combine the location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -132,4 +132,4 @@ To dump a manifest containing the Tiller deployment YAML, combine the .SH HISTORY .PP -1\-May\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_inspect.1 b/docs/man/man1/helm_inspect.1 index 1f16017ad..0783476c7 100644 --- a/docs/man/man1/helm_inspect.1 +++ b/docs/man/man1/helm_inspect.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -62,7 +62,7 @@ Inspect prints the contents of the Chart.yaml file and the values.yaml file. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -81,4 +81,4 @@ Inspect prints the contents of the Chart.yaml file and the values.yaml file. .SH HISTORY .PP -24\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_inspect_chart.1 b/docs/man/man1/helm_inspect_chart.1 index 04047c102..f728df410 100644 --- a/docs/man/man1/helm_inspect_chart.1 +++ b/docs/man/man1/helm_inspect_chart.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -59,7 +59,7 @@ of the Charts.yaml file location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -78,4 +78,4 @@ of the Charts.yaml file .SH HISTORY .PP -24\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_inspect_values.1 b/docs/man/man1/helm_inspect_values.1 index f7fbe6f6f..c87dd9c60 100644 --- a/docs/man/man1/helm_inspect_values.1 +++ b/docs/man/man1/helm_inspect_values.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -59,7 +59,7 @@ of the values.yaml file location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -78,4 +78,4 @@ of the values.yaml file .SH HISTORY .PP -24\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_install.1 b/docs/man/man1/helm_install.1 index 3a6db6769..8fe99acb3 100644 --- a/docs/man/man1/helm_install.1 +++ b/docs/man/man1/helm_install.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -122,6 +122,10 @@ charts in a repository, use 'helm search'. \fB\-\-cert\-file\fP="" identify HTTPS client using this SSL certificate file +.PP +\fB\-\-devel\fP[=false] + use development versions, too. Equivalent to version '>0.0.0\-a'. If \-\-version is set, this is ignored. + .PP \fB\-\-dry\-run\fP[=false] simulate an install @@ -213,7 +217,7 @@ charts in a repository, use 'helm search'. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -232,4 +236,4 @@ charts in a repository, use 'helm search'. .SH HISTORY .PP -24\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_lint.1 b/docs/man/man1/helm_lint.1 index 0ade88c5a..e0d522685 100644 --- a/docs/man/man1/helm_lint.1 +++ b/docs/man/man1/helm_lint.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -40,7 +40,7 @@ or recommendation, it will emit [WARNING] messages. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -59,4 +59,4 @@ or recommendation, it will emit [WARNING] messages. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_list.1 b/docs/man/man1/helm_list.1 index aef144aa7..d4fccf960 100644 --- a/docs/man/man1/helm_list.1 +++ b/docs/man/man1/helm_list.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -129,7 +129,7 @@ flag with the '\-\-offset' flag allows you to page through results. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -148,4 +148,4 @@ flag with the '\-\-offset' flag allows you to page through results. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_package.1 b/docs/man/man1/helm_package.1 index b50ef10d6..07185a4c2 100644 --- a/docs/man/man1/helm_package.1 +++ b/docs/man/man1/helm_package.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -63,7 +63,7 @@ Versioned chart archives are used by Helm package repositories. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -82,4 +82,4 @@ Versioned chart archives are used by Helm package repositories. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_plugin.1 b/docs/man/man1/helm_plugin.1 index 36bae074f..7af4f39fc 100644 --- a/docs/man/man1/helm_plugin.1 +++ b/docs/man/man1/helm_plugin.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -28,7 +28,7 @@ Manage client\-side Helm plugins. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -42,9 +42,9 @@ Manage client\-side Helm plugins. .SH SEE ALSO .PP -\fBhelm(1)\fP, \fBhelm\-plugin\-install(1)\fP, \fBhelm\-plugin\-list(1)\fP, \fBhelm\-plugin\-remove(1)\fP +\fBhelm(1)\fP, \fBhelm\-plugin\-install(1)\fP, \fBhelm\-plugin\-list(1)\fP, \fBhelm\-plugin\-remove(1)\fP, \fBhelm\-plugin\-update(1)\fP .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_plugin_install.1 b/docs/man/man1/helm_plugin_install.1 index fc4378048..d2a8d1326 100644 --- a/docs/man/man1/helm_plugin_install.1 +++ b/docs/man/man1/helm_plugin_install.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -34,7 +34,7 @@ install one or more Helm plugins location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -53,4 +53,4 @@ install one or more Helm plugins .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_plugin_list.1 b/docs/man/man1/helm_plugin_list.1 index 0db846a2c..5fcebd748 100644 --- a/docs/man/man1/helm_plugin_list.1 +++ b/docs/man/man1/helm_plugin_list.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -28,7 +28,7 @@ list installed Helm plugins location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -47,4 +47,4 @@ list installed Helm plugins .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_plugin_remove.1 b/docs/man/man1/helm_plugin_remove.1 index 9d3a20057..de64220dd 100644 --- a/docs/man/man1/helm_plugin_remove.1 +++ b/docs/man/man1/helm_plugin_remove.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -28,7 +28,7 @@ remove one or more Helm plugins location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -47,4 +47,4 @@ remove one or more Helm plugins .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_plugin_update.1 b/docs/man/man1/helm_plugin_update.1 new file mode 100644 index 000000000..ad7d70a17 --- /dev/null +++ b/docs/man/man1/helm_plugin_update.1 @@ -0,0 +1,50 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-plugin\-update \- update one or more Helm plugins + + +.SH SYNOPSIS +.PP +\fBhelm plugin update \&...\fP + + +.SH DESCRIPTION +.PP +update one or more Helm plugins + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-plugin(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_repo.1 b/docs/man/man1/helm_repo.1 index e073dabcf..19b2da8a3 100644 --- a/docs/man/man1/helm_repo.1 +++ b/docs/man/man1/helm_repo.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -33,7 +33,7 @@ Example usage: location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -52,4 +52,4 @@ Example usage: .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_repo_add.1 b/docs/man/man1/helm_repo_add.1 index 19e2606ac..3cd9f790b 100644 --- a/docs/man/man1/helm_repo_add.1 +++ b/docs/man/man1/helm_repo_add.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -46,7 +46,7 @@ add a chart repository location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -65,4 +65,4 @@ add a chart repository .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_repo_index.1 b/docs/man/man1/helm_repo_index.1 index 260fa97f3..edfcda6f5 100644 --- a/docs/man/man1/helm_repo_index.1 +++ b/docs/man/man1/helm_repo_index.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -47,7 +47,7 @@ into the existing index, with local charts taking priority over existing charts. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -66,4 +66,4 @@ into the existing index, with local charts taking priority over existing charts. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_repo_list.1 b/docs/man/man1/helm_repo_list.1 index 33a97d816..7623f73fe 100644 --- a/docs/man/man1/helm_repo_list.1 +++ b/docs/man/man1/helm_repo_list.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -28,7 +28,7 @@ list chart repositories location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -47,4 +47,4 @@ list chart repositories .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_repo_remove.1 b/docs/man/man1/helm_repo_remove.1 index b6d48a50d..cfbf217a4 100644 --- a/docs/man/man1/helm_repo_remove.1 +++ b/docs/man/man1/helm_repo_remove.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -28,7 +28,7 @@ remove a chart repository location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -47,4 +47,4 @@ remove a chart repository .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_repo_update.1 b/docs/man/man1/helm_repo_update.1 index 1a46691e9..2c0b9d338 100644 --- a/docs/man/man1/helm_repo_update.1 +++ b/docs/man/man1/helm_repo_update.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -33,7 +33,7 @@ future releases. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -52,4 +52,4 @@ future releases. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_reset.1 b/docs/man/man1/helm_reset.1 index 6d600d1d2..bf735591d 100644 --- a/docs/man/man1/helm_reset.1 +++ b/docs/man/man1/helm_reset.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -60,7 +60,7 @@ $HELM\_HOME (default \~/.helm/) location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -79,4 +79,4 @@ $HELM\_HOME (default \~/.helm/) .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_rollback.1 b/docs/man/man1/helm_rollback.1 index 909573969..d91ab881d 100644 --- a/docs/man/man1/helm_rollback.1 +++ b/docs/man/man1/helm_rollback.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -28,6 +28,10 @@ second is a revision (version) number. To see revision numbers, run \fB\-\-dry\-run\fP[=false] simulate a rollback +.PP +\fB\-\-force\fP[=false] + force resource update through delete/recreate if needed + .PP \fB\-\-no\-hooks\fP[=false] prevent hooks from running during rollback @@ -75,7 +79,7 @@ second is a revision (version) number. To see revision numbers, run location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -94,4 +98,4 @@ second is a revision (version) number. To see revision numbers, run .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_search.1 b/docs/man/man1/helm_search.1 index 7afc38a12..ac2467bf2 100644 --- a/docs/man/man1/helm_search.1 +++ b/docs/man/man1/helm_search.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -46,7 +46,7 @@ Repositories are managed with 'helm repo' commands. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -65,4 +65,4 @@ Repositories are managed with 'helm repo' commands. .SH HISTORY .PP -18\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_serve.1 b/docs/man/man1/helm_serve.1 index a01385ac2..a4a9c51da 100644 --- a/docs/man/man1/helm_serve.1 +++ b/docs/man/man1/helm_serve.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -39,7 +39,7 @@ for more information on hosting chart repositories in a production setting. address to listen on .PP -\fB\-\-repo\-path\fP="~/.helm/repository/local" +\fB\-\-repo\-path\fP="" local directory path from which to serve charts .PP @@ -57,7 +57,7 @@ for more information on hosting chart repositories in a production setting. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -76,4 +76,4 @@ for more information on hosting chart repositories in a production setting. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_status.1 b/docs/man/man1/helm_status.1 index 380ef03a8..8f2366808 100644 --- a/docs/man/man1/helm_status.1 +++ b/docs/man/man1/helm_status.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -61,7 +61,7 @@ The status consists of: location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -80,4 +80,4 @@ The status consists of: .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_test.1 b/docs/man/man1/helm_test.1 index 5936f81ee..6da36b33b 100644 --- a/docs/man/man1/helm_test.1 +++ b/docs/man/man1/helm_test.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -62,7 +62,7 @@ The tests to be run are defined in the chart that was installed. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -81,4 +81,4 @@ The tests to be run are defined in the chart that was installed. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_upgrade.1 b/docs/man/man1/helm_upgrade.1 index dce13815b..5d5e919f6 100644 --- a/docs/man/man1/helm_upgrade.1 +++ b/docs/man/man1/helm_upgrade.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -65,10 +65,18 @@ $ helm upgrade \-\-set foo=bar \-\-set foo=newbar redis ./redis \fB\-\-cert\-file\fP="" identify HTTPS client using this SSL certificate file +.PP +\fB\-\-devel\fP[=false] + use development versions, too. Equivalent to version '>0.0.0\-a'. If \-\-version is set, this is ignored. + .PP \fB\-\-dry\-run\fP[=false] simulate an upgrade +.PP +\fB\-\-force\fP[=false] + force resource update through delete/recreate if needed + .PP \fB\-i\fP, \fB\-\-install\fP[=false] if a release by this name doesn't already exist, run an install @@ -160,7 +168,7 @@ $ helm upgrade \-\-set foo=bar \-\-set foo=newbar redis ./redis location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -179,4 +187,4 @@ $ helm upgrade \-\-set foo=bar \-\-set foo=newbar redis ./redis .SH HISTORY .PP -24\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_verify.1 b/docs/man/man1/helm_verify.1 index dcd18e7fd..5297924ae 100644 --- a/docs/man/man1/helm_verify.1 +++ b/docs/man/man1/helm_verify.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -43,7 +43,7 @@ the 'helm package \-\-sign' command. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -62,4 +62,4 @@ the 'helm package \-\-sign' command. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/man/man1/helm_version.1 b/docs/man/man1/helm_version.1 index f97acf6da..1f1bf600d 100644 --- a/docs/man/man1/helm_version.1 +++ b/docs/man/man1/helm_version.1 @@ -1,4 +1,4 @@ -.TH "HELM" "1" "Apr 2017" "Auto generated by spf13/cobra" "" +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" .nh .ad l @@ -81,7 +81,7 @@ use '\-\-server'. location of your Helm config. Overrides $HELM\_HOME .PP -\fB\-\-host\fP="" +\fB\-\-host\fP="localhost:44134" address of tiller. Overrides $HELM\_HOST .PP @@ -100,4 +100,4 @@ use '\-\-server'. .SH HISTORY .PP -16\-Apr\-2017 Auto generated by spf13/cobra +19\-May\-2017 Auto generated by spf13/cobra diff --git a/docs/using_helm.md b/docs/using_helm.md index ef7753101..e27cd56d0 100755 --- a/docs/using_helm.md +++ b/docs/using_helm.md @@ -215,12 +215,13 @@ You can then override any of these settings in a YAML formatted file, and then pass that file during installation. ```console -$ echo 'mariadbUser: user0' > config.yaml +$ echo '{mariadbUser: user0, mariadbDatabase: user0db}' > config.yaml $ helm install -f config.yaml stable/mariadb ``` -The above will set the default MariaDB user to `user0`, but accept all -the rest of the defaults for that chart. +The above will create a default MariaDB user with the name `user0`, and +grant this user access to a newly created `user0db` database, but will +accept all the rest of the defaults for that chart. There are two ways to pass configuration data during install: @@ -355,7 +356,7 @@ is not a full list of cli flags. To see a description of all flags, just run This defaults to 300 (5 minutes) - `--wait`: Waits until all Pods are in a ready state, PVCs are bound, Deployments have minimum (`Desired` minus `maxUnavailable`) Pods in ready state and - Services have and IP address (and Ingress if a `LoadBalancer`) before + Services have an IP address (and Ingress if a `LoadBalancer`) before marking the release as successful. It will wait for as long as the `--timeout` value. If timeout is reached, the release will be marked as `FAILED`. diff --git a/glide.lock b/glide.lock index 624341837..efefa3de1 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: e323e66f9aba77578f7dcc0886e3117ebc373bc391374b2d5ddd293c276c8966 -updated: 2017-05-02T22:27:03.674351365-04:00 +hash: f705b8873fe2e7fb0cf436e2485edf218f558ae726015e6d59490d53464ae536 +updated: 2017-05-17T12:47:01.838888143-06:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 @@ -49,7 +49,7 @@ imports: - name: github.com/dgrijalva/jwt-go version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 - name: github.com/docker/distribution - version: cd27f179f2c10c5d300e6d09025b538c475b0d51 + version: 03efb43768979f4d2ea5187bef995656441829e5 subpackages: - digest - reference @@ -102,6 +102,8 @@ imports: version: ba18e35c5c1b36ef6334cad706eb681153d2d379 - name: github.com/exponent-io/jsonpath version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5 +- name: github.com/facebookgo/atomicfile + version: 2de1f203e7d5e386a6833233882782932729f27e - name: github.com/facebookgo/symwalk version: 42004b9f322246749dd73ad71008b1f3160c0052 - name: github.com/ghodss/yaml @@ -152,6 +154,8 @@ imports: version: 34abd90a014618f61222a1b0a7b7eb834a2d0dc3 - name: github.com/howeyc/gopass version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d +- name: github.com/huandu/xstrings + version: 3959339b333561bf62a38b424fd41517c2c90f40 - name: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - name: github.com/inconshreveable/mousetrap @@ -169,7 +173,7 @@ imports: - name: github.com/Masterminds/semver version: 3f0ab6d4ab4bed1c61caf056b63a6e62190c7801 - name: github.com/Masterminds/sprig - version: a48f46e0125cf60347eda24fccf1b09f1a0d681f + version: 9526be0327b26ad31aa70296a7b10704883976d5 - name: github.com/Masterminds/vcs version: 3084677c2c188840777bff30054f2b553729d329 - name: github.com/mattn/go-runewidth @@ -469,7 +473,7 @@ imports: - util/integer - util/jsonpath - name: k8s.io/kubernetes - version: 477efc3cbe6a7effca06bd1452fa356e2201e1ee + version: 0480917b552be33e2dba47386e51decb1a211df6 subpackages: - federation/apis/federation - federation/apis/federation/install @@ -621,6 +625,8 @@ imports: - pkg/watch/json - name: vbom.ml/util version: db5cfe13f5cc80a4990d98e2e1b0707a4d1a5394 + repo: https://github.com/fvbommel/util.git + vcs: git subpackages: - sortorder testImports: diff --git a/glide.yaml b/glide.yaml index 0e4c3c3d0..7b298a29e 100644 --- a/glide.yaml +++ b/glide.yaml @@ -15,7 +15,7 @@ import: - package: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - package: github.com/Masterminds/sprig - version: ^2.11 + version: ^2.12 - package: github.com/ghodss/yaml - package: github.com/Masterminds/semver version: ~1.2.3 @@ -41,6 +41,7 @@ import: - package: github.com/gobwas/glob version: ^0.2.1 - package: github.com/evanphx/json-patch +- package: github.com/facebookgo/atomicfile - package: github.com/facebookgo/symwalk - package: github.com/BurntSushi/toml version: ~0.3.0 @@ -49,6 +50,11 @@ import: - package: github.com/chai2010/gettext-go - package: github.com/prometheus/client_golang version: v0.8.0 +- package: vbom.ml/util + repo: https://github.com/fvbommel/util.git + vcs: git +- package: github.com/docker/distribution + version: ~v2.4.0 testImports: - package: github.com/stretchr/testify version: ^1.1.4 diff --git a/pkg/chartutil/files.go b/pkg/chartutil/files.go index 043357dd1..470ed711e 100644 --- a/pkg/chartutil/files.go +++ b/pkg/chartutil/files.go @@ -22,7 +22,7 @@ import ( "path" "strings" - yaml "gopkg.in/yaml.v2" + "github.com/ghodss/yaml" "github.com/BurntSushi/toml" "github.com/gobwas/glob" diff --git a/pkg/chartutil/requirements.go b/pkg/chartutil/requirements.go index 5f9fa3b3d..5a7f5fe78 100644 --- a/pkg/chartutil/requirements.go +++ b/pkg/chartutil/requirements.go @@ -65,6 +65,8 @@ type Dependency struct { // 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"` + // Alias usable alias to be used for the chart + Alias []string `json:"alias"` } // ErrNoRequirementsFile to detect error condition @@ -216,6 +218,28 @@ func ProcessRequirementsTags(reqs *Requirements, cvals Values) { } +func copyChartAsAlias(charts []*chart.Chart, dependentChart, aliasChart string) *chart.Chart { + var chartFound chart.Chart + for _, existingChart := range charts { + if existingChart == nil { + continue + } + if existingChart.Metadata == nil { + continue + } + if existingChart.Metadata.Name != dependentChart { + continue + } + + chartFound = *existingChart + newMetadata := *existingChart.Metadata + newMetadata.Name = aliasChart + chartFound.Metadata = &newMetadata + return &chartFound + } + return nil +} + // ProcessRequirementsEnabled removes disabled charts from dependencies func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error { reqs, err := LoadRequirements(c) @@ -228,6 +252,21 @@ func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error { // no requirements to process return nil } + + for _, req := range reqs.Dependencies { + for _, alias := range req.Alias { + aliasDependency := copyChartAsAlias(c.Dependencies, req.Name, alias) + if aliasDependency == nil { + break + } + c.Dependencies = append(c.Dependencies, aliasDependency) + origReqName := req.Name + req.Name = alias + reqs.Dependencies = append(reqs.Dependencies, req) + req.Name = origReqName + } + } + // set all to true for _, lr := range reqs.Dependencies { lr.Enabled = true diff --git a/pkg/chartutil/requirements_test.go b/pkg/chartutil/requirements_test.go index b5dbe35dc..f67bea46e 100644 --- a/pkg/chartutil/requirements_test.go +++ b/pkg/chartutil/requirements_test.go @@ -320,3 +320,59 @@ func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v *chart.Confi } } + +func TestCopyChartAsAlias(t *testing.T) { + c, err := Load("testdata/frobnitz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + if aliasChart := copyChartAsAlias(c.Dependencies, "mariners", "another-mariner"); aliasChart != nil { + t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) + } + + aliasChart := copyChartAsAlias(c.Dependencies, "mariner", "another-mariner") + if aliasChart == nil { + t.Fatal("Failed to find dependent chart") + } + if aliasChart.Metadata.Name != "another-mariner" { + t.Fatal(`Failed to update chart-name for alias "dependent chart`) + } +} + +func TestDependentChartAliases(t *testing.T) { + c, err := Load("testdata/dependent-chart-alias") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + if len(c.Dependencies) == 0 { + t.Fatal("There are no dependencies to run this test") + } + + origLength := len(c.Dependencies) + if err := ProcessRequirementsEnabled(c, c.Values); err != nil { + t.Fatalf("Expected no errors but got %q", err) + } + + if len(c.Dependencies) == origLength { + t.Fatal("Expected alias dependencies to be added, but did not got that") + } + + reqmts, err := LoadRequirements(c) + if err != nil { + t.Fatalf("Cannot load requirements for test chart, %v", err) + } + + var expectedDependencyCharts int + for _, reqmt := range reqmts.Dependencies { + expectedDependencyCharts++ + if len(reqmt.Alias) >= 0 { + expectedDependencyCharts += len(reqmt.Alias) + } + } + if len(c.Dependencies) != expectedDependencyCharts { + t.Fatalf("Expected number of chart dependencies %d, but got %d", expectedDependencyCharts, len(c.Dependencies)) + } + +} diff --git a/pkg/chartutil/testdata/dependent-chart-alias/.helmignore b/pkg/chartutil/testdata/dependent-chart-alias/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml new file mode 100644 index 000000000..7c071c27b --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png diff --git a/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/LICENSE b/pkg/chartutil/testdata/dependent-chart-alias/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/README.md b/pkg/chartutil/testdata/dependent-chart-alias/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 000000000..ced5a4a6a Binary files /dev/null and b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz differ diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz new file mode 100644 index 000000000..3af333e76 Binary files /dev/null and b/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz differ diff --git a/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md b/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/icon.svg b/pkg/chartutil/testdata/dependent-chart-alias/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/chartutil/testdata/dependent-chart-alias/requirements.lock b/pkg/chartutil/testdata/dependent-chart-alias/requirements.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/requirements.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/pkg/chartutil/testdata/dependent-chart-alias/requirements.yaml b/pkg/chartutil/testdata/dependent-chart-alias/requirements.yaml new file mode 100644 index 000000000..2e41105c7 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/requirements.yaml @@ -0,0 +1,10 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts + alias: + - mariners1 + - mariners2 diff --git a/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-alias/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/pkg/chartutil/testdata/dependent-chart-alias/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index e1dadcb15..122d0b125 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -202,7 +202,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, ge cv, err := i.Get(chartName, version) if err != nil { - return u, r.Client, fmt.Errorf("chart %q not found in %s index. (try 'helm repo update'). %s", chartName, r.Config.Name, err) + return u, r.Client, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'). %s", chartName, version, r.Config.Name, err) } if len(cv.URLs) == 0 { diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 508122376..aea489edf 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -572,5 +572,5 @@ func tarFromLocalDir(chartpath string, name string, repo string, version string) return ch.Metadata.Version, err } - return "", fmt.Errorf("Can't get a valid version for dependency %s.", name) + return "", fmt.Errorf("can't get a valid version for dependency %s", name) } diff --git a/pkg/helm/client.go b/pkg/helm/client.go index 5b12047c8..a921a548d 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -159,6 +159,7 @@ func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts req.Name = rlsName req.DisableHooks = h.opts.disableHooks req.Recreate = h.opts.recreate + req.Force = h.opts.force req.ResetValues = h.opts.resetValues req.ReuseValues = h.opts.reuseValues ctx := NewContext() @@ -202,6 +203,8 @@ func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.R opt(&h.opts) } req := &h.opts.rollbackReq + req.Recreate = h.opts.recreate + req.Force = h.opts.force req.DisableHooks = h.opts.disableHooks req.DryRun = h.opts.dryRun req.Name = rlsName diff --git a/pkg/helm/option.go b/pkg/helm/option.go index 0604e244a..2b30cd3c5 100644 --- a/pkg/helm/option.go +++ b/pkg/helm/option.go @@ -46,6 +46,8 @@ type options struct { reuseName bool // if set, performs pod restart during upgrade/rollback recreate bool + // if set, force resource update through delete/recreate if needed + force bool // if set, skip running hooks disableHooks bool // name of release @@ -311,6 +313,13 @@ func RollbackRecreate(recreate bool) RollbackOption { } } +// RollbackForce will (if true) force resource update through delete/recreate if needed +func RollbackForce(force bool) RollbackOption { + return func(opts *options) { + opts.force = force + } +} + // RollbackVersion sets the version of the release to deploy. func RollbackVersion(ver int32) RollbackOption { return func(opts *options) { @@ -353,6 +362,13 @@ func UpgradeRecreate(recreate bool) UpdateOption { } } +// UpgradeForce will (if true) force resource update through delete/recreate if needed +func UpgradeForce(force bool) UpdateOption { + return func(opts *options) { + opts.force = force + } +} + // ContentOption allows setting optional attributes when // performing a GetReleaseContent tiller rpc. type ContentOption func(*options) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index a68c80c34..8302c2889 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -59,6 +59,8 @@ type Client struct { cmdutil.Factory // SchemaCacheDir is the path for loading cached schema. SchemaCacheDir string + + Log func(string, ...interface{}) } // New create a new Client @@ -66,6 +68,7 @@ func New(config clientcmd.ClientConfig) *Client { return &Client{ Factory: cmdutil.NewFactory(config), SchemaCacheDir: clientcmd.RecommendedSchemaFile, + Log: func(_ string, _ ...interface{}) {}, } } @@ -83,10 +86,12 @@ func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shoul if err := ensureNamespace(client, namespace); err != nil { return err } + c.Log("building resources from manifest") infos, buildErr := c.BuildUnstructured(namespace, reader) if buildErr != nil { return buildErr } + c.Log("creating %d resource(s)", len(infos)) if err := perform(infos, createResource); err != nil { return err } @@ -99,7 +104,7 @@ func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shoul func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { schema, err := c.Validator(true, c.SchemaCacheDir) if err != nil { - log.Printf("warning: failed to load schema: %s", err) + c.Log("warning: failed to load schema: %s", err) } return c.NewBuilder(). ContinueOnError(). @@ -115,12 +120,12 @@ func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { schema, err := c.Validator(true, c.SchemaCacheDir) if err != nil { - log.Printf("warning: failed to load schema: %s", err) + c.Log("warning: failed to load schema: %s", err) } mapper, typer, err := c.UnstructuredObject() if err != nil { - log.Printf("failed to load mapper: %s", err) + c.Log("failed to load mapper: %s", err) return nil, err } var result Result @@ -158,9 +163,9 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { missing := []string{} err = perform(infos, func(info *resource.Info) error { - log.Printf("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) + c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) if err := info.Get(); err != nil { - log.Printf("WARNING: Failed Get for resource %q: %s", info.Name, err) + c.Log("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 } @@ -202,7 +207,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: %q :\n %v", t, o, err) + c.Log("failed to print object type %s, object: %q :\n %v", t, o, err) return "", err } } @@ -225,12 +230,13 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { // not present in the target configuration // // Namespace will set the namespaces -func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { +func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { original, err := c.BuildUnstructured(namespace, originalReader) if err != nil { return fmt.Errorf("failed decoding reader into objects: %s", err) } + c.Log("building resources from updated manifest") target, err := c.BuildUnstructured(namespace, targetReader) if err != nil { return fmt.Errorf("failed decoding reader into objects: %s", err) @@ -238,6 +244,7 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader updateErrors := []string{} + c.Log("checking %d resources for changes", len(target)) err = target.Visit(func(info *resource.Info, err error) error { if err != nil { return err @@ -246,7 +253,7 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader helper := resource.NewHelper(info.Client, info.Mapping) if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil { if !errors.IsNotFound(err) { - return fmt.Errorf("Could not get information about the resource: err: %s", err) + return fmt.Errorf("Could not get information about the resource: %s", err) } // Since the resource does not exist, create it. @@ -255,7 +262,7 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader } kind := info.Mapping.GroupVersionKind.Kind - log.Printf("Created a new %s called %q\n", kind, info.Name) + c.Log("Created a new %s called %q\n", kind, info.Name) return nil } @@ -264,8 +271,8 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader 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 %q:\n\t %v", info.Name, err) + if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil { + c.Log("error updating the resource %q:\n\t %v", info.Name, err) updateErrors = append(updateErrors, err.Error()) } @@ -280,9 +287,9 @@ func (c *Client) Update(namespace string, originalReader, targetReader io.Reader } for _, info := range original.Difference(target) { - log.Printf("Deleting %q in %s...", info.Name, info.Namespace) + c.Log("Deleting %q in %s...", info.Name, info.Namespace) if err := deleteResource(c, info); err != nil { - log.Printf("Failed to delete %q, err: %s", info.Name, err) + c.Log("Failed to delete %q, err: %s", info.Name, err) } } if shouldWait { @@ -300,23 +307,23 @@ func (c *Client) Delete(namespace string, reader io.Reader) error { return err } return perform(infos, func(info *resource.Info) error { - log.Printf("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) + c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) err := deleteResource(c, info) - return skipIfNotFound(err) + return c.skipIfNotFound(err) }) } -func skipIfNotFound(err error) error { +func (c *Client) skipIfNotFound(err error) error { if errors.IsNotFound(err) { - log.Printf("%v", err) + c.Log("%v", err) return nil } return err } -func watchTimeout(t time.Duration) ResourceActorFunc { +func (c *Client) watchTimeout(t time.Duration) ResourceActorFunc { return func(info *resource.Info) error { - return watchUntilReady(t, info) + return c.watchUntilReady(t, info) } } @@ -339,7 +346,7 @@ func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int } // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 - return perform(infos, watchTimeout(time.Duration(timeout)*time.Second)) + return perform(infos, c.watchTimeout(time.Duration(timeout)*time.Second)) } func perform(infos Result, fn ResourceActorFunc) error { @@ -372,7 +379,7 @@ func deleteResource(c *Client, info *resource.Info) error { } return err } - log.Printf("Using reaper for deleting %q", info.Name) + c.Log("Using reaper for deleting %q", info.Name) return reaper.Stop(info.Namespace, info.Name, 0, nil) } @@ -400,19 +407,18 @@ func createPatch(mapping *meta.RESTMapping, target, current runtime.Object) ([]b case err != nil: return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err) default: - log.Printf("generating strategic merge patch for %T", target) patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject) return patch, types.StrategicMergePatchType, err } } -func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, recreate bool) error { +func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error { patch, patchType, err := createPatch(target.Mapping, target.Object, currentObj) if err != nil { return fmt.Errorf("failed to create patch: %s", err) } if patch == nil { - log.Printf("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) + c.Log("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 { @@ -423,12 +429,36 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, // send patch to server helper := resource.NewHelper(target.Client, target.Mapping) + obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch) if err != nil { - return err - } + kind := target.Mapping.GroupVersionKind.Kind + log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err) - target.Refresh(obj, true) + if force { + // Attempt to delete... + if err := deleteResource(c, target); err != nil { + return err + } + log.Printf("Deleted %s: %q", kind, target.Name) + + // ... and recreate + if err := createResource(target); err != nil { + return fmt.Errorf("Failed to recreate resource: %s", err) + } + log.Printf("Created a new %s called %q\n", kind, target.Name) + + // No need to refresh the target, as we recreated the resource based + // on it. In addition, it might not exist yet and a call to `Refresh` + // may fail. + } else { + log.Print("Use --force to force recreation of the resource") + return err + } + } else { + // When patch succeeds without needing to recreate, refresh target. + target.Refresh(obj, true) + } if !recreate { return nil @@ -446,7 +476,11 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, if err != nil { return nil } - client, _ := c.ClientSet() + + client, err := c.ClientSet() + if err != nil { + return err + } pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{ FieldSelector: fields.Everything().String(), @@ -458,7 +492,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, // Restart pods for _, pod := range pods.Items { - log.Printf("Restarting pod: %v/%v", pod.Namespace, pod.Name) + c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name) // Delete each pod for get them restarted with changed spec. if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { @@ -487,14 +521,14 @@ func getSelectorFromObject(obj runtime.Object) (map[string]string, error) { } } -func watchUntilReady(timeout time.Duration, info *resource.Info) error { +func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error { w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) if err != nil { return err } kind := info.Mapping.GroupVersionKind.Kind - log.Printf("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) + c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) // What we watch for depends on the Kind. // - For a Job, we watch for completion. @@ -509,17 +543,17 @@ func watchUntilReady(timeout time.Duration, info *resource.Info) error { // we get. We care mostly about jobs, where what we want to see is // the status go into a good state. For other types, like ReplicaSet // we don't really do anything to support these as hooks. - log.Printf("Add/Modify event for %s: %v", info.Name, e.Type) + c.Log("Add/Modify event for %s: %v", info.Name, e.Type) if kind == "Job" { - return waitForJob(e, info.Name) + return c.waitForJob(e, info.Name) } return true, nil case watch.Deleted: - log.Printf("Deleted event for %s", info.Name) + c.Log("Deleted event for %s", info.Name) return true, nil case watch.Error: // Handle error and return with an error. - log.Printf("Error event for %s", info.Name) + c.Log("Error event for %s", info.Name) return true, fmt.Errorf("Failed to deploy %s", info.Name) default: return false, nil @@ -542,7 +576,7 @@ func (c *Client) AsVersionedObject(obj runtime.Object) (runtime.Object, error) { // waitForJob is a helper that waits for a job to complete. // // This operates on an event returned from a watcher. -func waitForJob(e watch.Event, name string) (bool, error) { +func (c *Client) waitForJob(e watch.Event, name string) (bool, error) { o, ok := e.Object.(*batchinternal.Job) if !ok { return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object) @@ -556,7 +590,7 @@ func waitForJob(e watch.Event, name string) (bool, error) { } } - log.Printf("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) + c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) return false, nil } @@ -587,7 +621,7 @@ func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, return api.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name) } - if err := watchPodUntilComplete(timeout, info); err != nil { + if err := c.watchPodUntilComplete(timeout, info); err != nil { return api.PodUnknown, err } @@ -599,13 +633,13 @@ func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, return status, nil } -func watchPodUntilComplete(timeout time.Duration, info *resource.Info) error { +func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error { w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) if err != nil { return err } - log.Printf("Watching pod %s for completion with timeout of %v", info.Name, timeout) + c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout) _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { return conditions.PodCompleted(e) }) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 1f2032bb2..647a50652 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -138,6 +138,12 @@ func encodeAndMarshalEvent(e *watch.Event) ([]byte, error) { return json.Marshal(encodedEvent) } +func newTestClient(f cmdutil.Factory) *Client { + c := New(nil) + c.Factory = f + return c +} + func TestUpdate(t *testing.T) { listA := newPodList("starfish", "otter", "squid") listB := newPodList("starfish", "otter", "dolphin") @@ -186,8 +192,8 @@ func TestUpdate(t *testing.T) { reaper := &fakeReaper{} rf := &fakeReaperFactory{Factory: f, reaper: reaper} - c := &Client{Factory: rf} - if err := c.Update(api.NamespaceDefault, objBody(codec, &listA), objBody(codec, &listB), false, 0, false); err != nil { + c := newTestClient(rf) + if err := c.Update(api.NamespaceDefault, objBody(codec, &listA), objBody(codec, &listB), false, false, 0, false); err != nil { t.Fatal(err) } // TODO: Find a way to test methods that use Client Set @@ -251,7 +257,7 @@ func TestBuild(t *testing.T) { for _, tt := range tests { f, tf, _, _ := cmdtesting.NewAPIFactory() - c := &Client{Factory: f} + c := newTestClient(f) if tt.swaggerFile != "" { data, err := ioutil.ReadFile(tt.swaggerFile) if err != nil { @@ -320,7 +326,7 @@ func TestGet(t *testing.T) { } }), } - c := &Client{Factory: f} + c := newTestClient(f) // Test Success data := strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: otter") @@ -380,7 +386,7 @@ func TestPerform(t *testing.T) { } f, tf, _, _ := cmdtesting.NewAPIFactory() - c := &Client{Factory: f} + c := newTestClient(f) if tt.swaggerFile != "" { data, err := ioutil.ReadFile(tt.swaggerFile) if err != nil { @@ -464,7 +470,7 @@ func TestWaitAndGetCompletedPodPhase(t *testing.T) { }), } - c := &Client{Factory: f} + c := newTestClient(f) phase, err := c.WaitAndGetCompletedPodPhase("test", objBody(codec, &testPodList), 1*time.Second) if (err != nil) != tt.err { diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index e10f4997f..d917d79ed 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -17,7 +17,6 @@ limitations under the License. package kube // import "k8s.io/helm/pkg/kube" import ( - "log" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -44,15 +43,17 @@ type deployment struct { // waitForResources polls to get the current status of all pods, PVCs, and Services // until all are ready or a timeout is reached func (c *Client) waitForResources(timeout time.Duration, created Result) error { - log.Printf("beginning wait for resources with timeout of %v", timeout) + c.Log("beginning wait for %d resources with timeout of %v", len(created), timeout) - cs, _ := c.ClientSet() + cs, err := c.ClientSet() + if err != nil { + return err + } client := versionedClientsetForDeployment(cs) return wait.Poll(2*time.Second, timeout, func() (bool, error) { pods := []v1.Pod{} services := []v1.Service{} pvc := []v1.PersistentVolumeClaim{} - replicaSets := []*extensions.ReplicaSet{} deployments := []deployment{} for _, v := range created { obj, err := c.AsVersionedObject(v.Object) @@ -73,25 +74,12 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { } pods = append(pods, *pod) case (*extensions.Deployment): - // Get the RS children first - rs, err := client.Extensions().ReplicaSets(value.Namespace).List(metav1.ListOptions{ - FieldSelector: fields.Everything().String(), - LabelSelector: labels.Set(value.Spec.Selector.MatchLabels).AsSelector().String(), - }) - if err != nil { - return false, err - } - - for _, i := range rs.Items { - replicaSets = append(replicaSets, &i) - } - currentDeployment, err := client.Extensions().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) if err != nil { return false, err } // Find RS associated with deployment - newReplicaSet, err := deploymentutil.FindNewReplicaSet(currentDeployment, replicaSets) + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, client) if err != nil || newReplicaSet == nil { return false, err } @@ -132,7 +120,9 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { services = append(services, *svc) } } - return podsReady(pods) && servicesReady(services) && volumesReady(pvc) && deploymentsReady(deployments), nil + isReady := podsReady(pods) && servicesReady(services) && volumesReady(pvc) && deploymentsReady(deployments) + c.Log("resources ready: %v", isReady) + return isReady, nil }) } @@ -147,6 +137,11 @@ func podsReady(pods []v1.Pod) bool { func servicesReady(svc []v1.Service) bool { for _, s := range svc { + // ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set) + if s.Spec.Type == v1.ServiceTypeExternalName { + continue + } + // Make sure the service is not explicitly set to "None" before checking the IP if s.Spec.ClusterIP != v1.ClusterIPNone && !v1.IsServiceIPSet(&s) { return false diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 5b98d4886..73206b80c 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -21,12 +21,15 @@ import ( "fmt" "os" "path/filepath" + "runtime" "github.com/ghodss/yaml" + "k8s.io/apimachinery/pkg/version" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/engine" "k8s.io/helm/pkg/lint/support" "k8s.io/helm/pkg/timeconv" + tversion "k8s.io/helm/pkg/version" ) // Templates lints the templates in the Linter. @@ -51,7 +54,17 @@ func Templates(linter *support.Linter) { } options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: "testNamespace"} - caps := &chartutil.Capabilities{APIVersions: chartutil.DefaultVersionSet} + caps := &chartutil.Capabilities{ + APIVersions: chartutil.DefaultVersionSet, + KubeVersion: &version.Info{ + Major: "1", + Minor: "6", + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + }, + TillerVersion: tversion.GetVersionProto(), + } valuesToRender, err := chartutil.ToRenderValuesCaps(chart, chart.Values, options, caps) if err != nil { // FIXME: This seems to generate a duplicate, but I can't find where the first diff --git a/pkg/lint/rules/testdata/albatross/templates/svc.yaml b/pkg/lint/rules/testdata/albatross/templates/svc.yaml index 2c44ea2c6..167148112 100644 --- a/pkg/lint/rules/testdata/albatross/templates/svc.yaml +++ b/pkg/lint/rules/testdata/albatross/templates/svc.yaml @@ -8,6 +8,8 @@ metadata: heritage: {{ .Release.Service | quote }} release: {{ .Release.Name | quote }} chart: "{{.Chart.Name}}-{{.Chart.Version}}" + kubeVersion: {{ .Capabilities.KubeVersion.Major }} + tillerVersion: {{ .Capabilities.TillerVersion }} spec: ports: - port: {{default 80 .Values.httpPort | quote}} diff --git a/pkg/plugin/hooks.go b/pkg/plugin/hooks.go index 1f435f9f8..b5ca032ac 100644 --- a/pkg/plugin/hooks.go +++ b/pkg/plugin/hooks.go @@ -21,6 +21,8 @@ const ( Install = "install" // Delete is executed after the plugin is removed. Delete = "delete" + // Update is executed after the plugin is updated. + Update = "update" ) // Hooks is a map of events to commands. diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go index 8da6e24e5..6ecec980a 100644 --- a/pkg/plugin/installer/installer.go +++ b/pkg/plugin/installer/installer.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "os" + "path" "path/filepath" "k8s.io/helm/pkg/helm/helmpath" @@ -36,10 +37,16 @@ type Installer interface { Install() error // Path is the directory of the installed plugin. Path() string + // Update updates a plugin to $HELM_HOME. + Update() error } // Install installs a plugin to $HELM_HOME. func Install(i Installer) error { + if _, pathErr := os.Stat(path.Dir(i.Path())); os.IsNotExist(pathErr) { + return errors.New(`plugin home "$HELM_HOME/plugins" does not exist`) + } + if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { return errors.New("plugin already exists") } @@ -47,6 +54,15 @@ func Install(i Installer) error { return i.Install() } +// Update updates a plugin in $HELM_HOME. +func Update(i Installer) error { + if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { + return errors.New("plugin does not exist") + } + + return i.Update() +} + // NewForSource determines the correct Installer for the given source. func NewForSource(source, version string, home helmpath.Home) (Installer, error) { // Check if source is a local directory @@ -56,6 +72,15 @@ func NewForSource(source, version string, home helmpath.Home) (Installer, error) return NewVCSInstaller(source, version, home) } +// FindSource determines the correct Installer for the given source. +func FindSource(location string, home helmpath.Home) (Installer, error) { + installer, err := existingVCSRepo(location, home) + if err != nil && err.Error() == "Cannot detect VCS" { + return installer, errors.New("cannot get information about plugin source") + } + return installer, err +} + // isLocalReference checks if the source exists on the filesystem. func isLocalReference(source string) bool { _, err := os.Stat(source) diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go index 7ab588d60..18011f8de 100644 --- a/pkg/plugin/installer/local_installer.go +++ b/pkg/plugin/installer/local_installer.go @@ -47,3 +47,9 @@ func (i *LocalInstaller) Install() error { } return i.link(src) } + +// Update updates a local repository +func (i *LocalInstaller) Update() error { + debug("local repository is auto-updated") + return nil +} diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go index f9f181662..d2ba3aa31 100644 --- a/pkg/plugin/installer/vcs_installer.go +++ b/pkg/plugin/installer/vcs_installer.go @@ -16,6 +16,7 @@ limitations under the License. package installer // import "k8s.io/helm/pkg/plugin/installer" import ( + "errors" "fmt" "os" "sort" @@ -34,6 +35,18 @@ type VCSInstaller struct { base } +func existingVCSRepo(location string, home helmpath.Home) (Installer, error) { + repo, err := vcs.NewRepo("", location) + if err != nil { + return nil, err + } + i := &VCSInstaller{ + Repo: repo, + base: newBase(repo.Remote(), home), + } + return i, err +} + // NewVCSInstaller creates a new VCSInstaller. func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) { key, err := cache.Key(source) @@ -77,6 +90,21 @@ func (i *VCSInstaller) Install() error { return i.link(i.Repo.LocalPath()) } +// Update updates a remote repository +func (i *VCSInstaller) Update() error { + debug("updating %s", i.Repo.Remote()) + if i.Repo.IsDirty() { + return errors.New("plugin repo was modified") + } + if err := i.Repo.Update(); err != nil { + return err + } + if !isPlugin(i.Repo.LocalPath()) { + return ErrMissingMetadata + } + return nil +} + func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) { if i.Version == "" { return "", nil diff --git a/pkg/plugin/installer/vcs_installer_test.go b/pkg/plugin/installer/vcs_installer_test.go index 081598c7c..4abfc4c09 100644 --- a/pkg/plugin/installer/vcs_installer_test.go +++ b/pkg/plugin/installer/vcs_installer_test.go @@ -97,6 +97,13 @@ func TestVCSInstaller(t *testing.T) { } else if err.Error() != "plugin already exists" { t.Errorf("expected error for plugin exists, got (%v)", err) } + + //Testing FindSource method, expect error because plugin code is not a cloned repository + if _, err := FindSource(i.Path(), home); err == nil { + t.Error("expected error for inability to find plugin source, got none") + } else if err.Error() != "cannot get information about plugin source" { + t.Errorf("expected error for inability to find plugin source, got (%v)", err) + } } func TestVCSInstallerNonExistentVersion(t *testing.T) { @@ -131,3 +138,66 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { t.Errorf("expected error for version does not exists, got (%v)", err) } } +func TestVCSInstallerUpdate(t *testing.T) { + + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + source := "https://github.com/adamreese/helm-env" + + i, err := NewForSource(source, "", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + // ensure a VCSInstaller was returned + _, ok := i.(*VCSInstaller) + if !ok { + t.Error("expected a VCSInstaller") + } + + if err := Update(i); err == nil { + t.Error("expected error for plugin does not exist, got none") + } else if err.Error() != "plugin does not exist" { + t.Errorf("expected error for plugin does not exist, got (%v)", err) + } + + // Install plugin before update + if err := Install(i); err != nil { + t.Error(err) + } + + // Test FindSource method for positive result + pluginInfo, err := FindSource(i.Path(), home) + if err != nil { + t.Error(err) + } + + repoRemote := pluginInfo.(*VCSInstaller).Repo.Remote() + if repoRemote != source { + t.Errorf("invalid source found, expected %q got %q", source, repoRemote) + } + + // Update plugin + if err := Update(i); err != nil { + t.Error(err) + } + + // Test update failure + os.Remove(filepath.Join(i.Path(), "plugin.yaml")) + // Testing update for error + if err := Update(i); err == nil { + t.Error("expected error for plugin modified, got none") + } else if err.Error() != "plugin repo was modified" { + t.Errorf("expected error for plugin modified, got (%v)", err) + } + +} diff --git a/pkg/proto/hapi/release/test_run.pb.go b/pkg/proto/hapi/release/test_run.pb.go index 79fce4ab4..bd3c0ab2b 100644 --- a/pkg/proto/hapi/release/test_run.pb.go +++ b/pkg/proto/hapi/release/test_run.pb.go @@ -20,17 +20,20 @@ const ( TestRun_UNKNOWN TestRun_Status = 0 TestRun_SUCCESS TestRun_Status = 1 TestRun_FAILURE TestRun_Status = 2 + TestRun_RUNNING TestRun_Status = 3 ) var TestRun_Status_name = map[int32]string{ 0: "UNKNOWN", 1: "SUCCESS", 2: "FAILURE", + 3: "RUNNING", } var TestRun_Status_value = map[string]int32{ "UNKNOWN": 0, "SUCCESS": 1, "FAILURE": 2, + "RUNNING": 3, } func (x TestRun_Status) String() string { @@ -94,22 +97,23 @@ func init() { func init() { proto.RegisterFile("hapi/release/test_run.proto", fileDescriptor4) } var fileDescriptor4 = []byte{ - // 265 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0x41, 0x4b, 0xfb, 0x40, - 0x14, 0xc4, 0xff, 0xc9, 0xbf, 0x26, 0x64, 0x53, 0x24, 0xec, 0x29, 0x54, 0xc1, 0xd0, 0x53, 0x4e, - 0xbb, 0x50, 0xbd, 0x78, 0xf0, 0x10, 0x4b, 0x05, 0x51, 0x22, 0x6c, 0x1a, 0x04, 0x2f, 0x65, 0xab, - 0xaf, 0x35, 0x90, 0x64, 0x43, 0xf6, 0xe5, 0x8b, 0xf8, 0x89, 0x65, 0x93, 0xad, 0x78, 0xf3, 0xf6, - 0x86, 0xf9, 0xcd, 0x30, 0x8f, 0x5c, 0x7c, 0xca, 0xae, 0xe2, 0x3d, 0xd4, 0x20, 0x35, 0x70, 0x04, - 0x8d, 0xbb, 0x7e, 0x68, 0x59, 0xd7, 0x2b, 0x54, 0x74, 0x6e, 0x4c, 0x66, 0xcd, 0xc5, 0xd5, 0x51, - 0xa9, 0x63, 0x0d, 0x7c, 0xf4, 0xf6, 0xc3, 0x81, 0x63, 0xd5, 0x80, 0x46, 0xd9, 0x74, 0x13, 0xbe, - 0xfc, 0x72, 0x89, 0xbf, 0x05, 0x8d, 0x62, 0x68, 0x29, 0x25, 0xb3, 0x56, 0x36, 0x10, 0x3b, 0x89, - 0x93, 0x06, 0x62, 0xbc, 0xe9, 0x0d, 0xf1, 0x34, 0x4a, 0x1c, 0x74, 0xec, 0x26, 0x4e, 0x7a, 0xbe, - 0xba, 0x64, 0xbf, 0xfb, 0x99, 0x8d, 0xb2, 0x62, 0x64, 0x84, 0x65, 0x4d, 0x53, 0xd5, 0x1e, 0x54, - 0xfc, 0x7f, 0x6a, 0x32, 0x37, 0xbd, 0x25, 0x44, 0xa3, 0xec, 0x11, 0x3e, 0x76, 0x12, 0xe3, 0x59, - 0xe2, 0xa4, 0xe1, 0x6a, 0xc1, 0xa6, 0x7d, 0xec, 0xb4, 0x8f, 0x6d, 0x4f, 0xfb, 0x44, 0x60, 0xe9, - 0x0c, 0xe9, 0x1d, 0x99, 0xbf, 0xab, 0xa6, 0xab, 0xc1, 0x86, 0xcf, 0xfe, 0x0c, 0x87, 0x3f, 0x7c, - 0x86, 0x4b, 0x4e, 0xbc, 0x69, 0x1f, 0x0d, 0x89, 0x5f, 0xe6, 0x4f, 0xf9, 0xcb, 0x6b, 0x1e, 0xfd, - 0x33, 0xa2, 0x28, 0xd7, 0xeb, 0x4d, 0x51, 0x44, 0x8e, 0x11, 0x0f, 0xd9, 0xe3, 0x73, 0x29, 0x36, - 0x91, 0x7b, 0x1f, 0xbc, 0xf9, 0xf6, 0xc1, 0xbd, 0x37, 0x96, 0x5f, 0x7f, 0x07, 0x00, 0x00, 0xff, - 0xff, 0x8d, 0xb9, 0xce, 0x57, 0x74, 0x01, 0x00, 0x00, + // 274 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4b, 0xfb, 0x30, + 0x1c, 0xc5, 0x7f, 0xe9, 0xf6, 0x6b, 0x69, 0x3a, 0xa4, 0xe4, 0x54, 0xa6, 0x60, 0xd9, 0xa9, 0xa7, + 0x14, 0xa6, 0x17, 0x41, 0x0f, 0x75, 0x4c, 0x19, 0x4a, 0x84, 0x74, 0x45, 0xf0, 0x32, 0x32, 0xcd, + 0x66, 0xa1, 0x6d, 0x4a, 0xf3, 0xed, 0xdf, 0xe3, 0xbf, 0x2a, 0x69, 0x33, 0xf1, 0xe6, 0xed, 0xfb, + 0x78, 0x9f, 0xf7, 0xf2, 0x82, 0xcf, 0x3f, 0x45, 0x5b, 0xa6, 0x9d, 0xac, 0xa4, 0xd0, 0x32, 0x05, + 0xa9, 0x61, 0xd7, 0xf5, 0x0d, 0x6d, 0x3b, 0x05, 0x8a, 0xcc, 0x8c, 0x49, 0xad, 0x39, 0xbf, 0x3c, + 0x2a, 0x75, 0xac, 0x64, 0x3a, 0x78, 0xfb, 0xfe, 0x90, 0x42, 0x59, 0x4b, 0x0d, 0xa2, 0x6e, 0x47, + 0x7c, 0xf1, 0xe5, 0x60, 0x6f, 0x2b, 0x35, 0xf0, 0xbe, 0x21, 0x04, 0x4f, 0x1b, 0x51, 0xcb, 0x08, + 0xc5, 0x28, 0xf1, 0xf9, 0x70, 0x93, 0x6b, 0xec, 0x6a, 0x10, 0xd0, 0xeb, 0xc8, 0x89, 0x51, 0x72, + 0xb6, 0xbc, 0xa0, 0xbf, 0xfb, 0xa9, 0x8d, 0xd2, 0x7c, 0x60, 0xb8, 0x65, 0x4d, 0x53, 0xd9, 0x1c, + 0x54, 0x34, 0x19, 0x9b, 0xcc, 0x4d, 0x6e, 0x30, 0xd6, 0x20, 0x3a, 0x90, 0x1f, 0x3b, 0x01, 0xd1, + 0x34, 0x46, 0x49, 0xb0, 0x9c, 0xd3, 0x71, 0x1f, 0x3d, 0xed, 0xa3, 0xdb, 0xd3, 0x3e, 0xee, 0x5b, + 0x3a, 0x03, 0x72, 0x87, 0x67, 0xef, 0xaa, 0x6e, 0x2b, 0x69, 0xc3, 0xff, 0xff, 0x0c, 0x07, 0x3f, + 0x7c, 0x06, 0x8b, 0x5b, 0xec, 0x8e, 0xfb, 0x48, 0x80, 0xbd, 0x82, 0x3d, 0xb1, 0x97, 0x57, 0x16, + 0xfe, 0x33, 0x22, 0x2f, 0x56, 0xab, 0x75, 0x9e, 0x87, 0xc8, 0x88, 0x87, 0x6c, 0xf3, 0x5c, 0xf0, + 0x75, 0xe8, 0x18, 0xc1, 0x0b, 0xc6, 0x36, 0xec, 0x31, 0x9c, 0xdc, 0xfb, 0x6f, 0x9e, 0xfd, 0xed, + 0xde, 0x1d, 0x5e, 0xba, 0xfa, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x31, 0x86, 0x46, 0xdb, 0x81, 0x01, + 0x00, 0x00, } diff --git a/pkg/proto/hapi/rudder/rudder.pb.go b/pkg/proto/hapi/rudder/rudder.pb.go index af5577678..3f64ba713 100644 --- a/pkg/proto/hapi/rudder/rudder.pb.go +++ b/pkg/proto/hapi/rudder/rudder.pb.go @@ -220,6 +220,7 @@ type UpgradeReleaseRequest struct { Timeout int64 `protobuf:"varint,3,opt,name=Timeout" json:"Timeout,omitempty"` Wait bool `protobuf:"varint,4,opt,name=Wait" json:"Wait,omitempty"` Recreate bool `protobuf:"varint,5,opt,name=Recreate" json:"Recreate,omitempty"` + Force bool `protobuf:"varint,6,opt,name=Force" json:"Force,omitempty"` } func (m *UpgradeReleaseRequest) Reset() { *m = UpgradeReleaseRequest{} } @@ -262,6 +263,13 @@ func (m *UpgradeReleaseRequest) GetRecreate() bool { return false } +func (m *UpgradeReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + type UpgradeReleaseResponse struct { Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"` @@ -292,6 +300,7 @@ type RollbackReleaseRequest struct { Timeout int64 `protobuf:"varint,3,opt,name=Timeout" json:"Timeout,omitempty"` Wait bool `protobuf:"varint,4,opt,name=Wait" json:"Wait,omitempty"` Recreate bool `protobuf:"varint,5,opt,name=Recreate" json:"Recreate,omitempty"` + Force bool `protobuf:"varint,6,opt,name=Force" json:"Force,omitempty"` } func (m *RollbackReleaseRequest) Reset() { *m = RollbackReleaseRequest{} } @@ -334,6 +343,13 @@ func (m *RollbackReleaseRequest) GetRecreate() bool { return false } +func (m *RollbackReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + type RollbackReleaseResponse struct { Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"` @@ -665,42 +681,43 @@ var _ReleaseModuleService_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("hapi/rudder/rudder.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 584 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0xd1, 0x8e, 0xd2, 0x40, - 0x14, 0xa5, 0xcb, 0x52, 0xe0, 0x92, 0x55, 0x32, 0xd9, 0x42, 0xd3, 0xf8, 0x40, 0xfa, 0x60, 0x88, - 0xeb, 0x96, 0x04, 0x7d, 0xf4, 0x45, 0x59, 0xdc, 0xdd, 0x18, 0xd9, 0x64, 0x2a, 0x6e, 0xe2, 0x5b, - 0x17, 0x2e, 0x58, 0x2d, 0x6d, 0x9d, 0x4e, 0xf7, 0x51, 0xfd, 0x1a, 0xff, 0x43, 0xbf, 0xcc, 0xb4, - 0xd3, 0x12, 0x5b, 0xa7, 0x11, 0xd7, 0x84, 0xc4, 0xa7, 0xce, 0xf4, 0x1e, 0xee, 0x9c, 0x73, 0x7a, - 0xe7, 0x04, 0xd0, 0xdf, 0x3b, 0xa1, 0x3b, 0x62, 0xf1, 0x72, 0x89, 0x2c, 0x7b, 0x58, 0x21, 0x0b, - 0x78, 0x40, 0x8e, 0x93, 0x8a, 0x15, 0x21, 0xbb, 0x75, 0x17, 0x18, 0x59, 0xa2, 0x66, 0xf4, 0x05, - 0x1e, 0x3d, 0x74, 0x22, 0x1c, 0xb9, 0xfe, 0x2a, 0x10, 0x70, 0xc3, 0x28, 0x14, 0xb2, 0xa7, 0xa8, - 0x99, 0x1e, 0xa8, 0x14, 0xa3, 0xd8, 0xe3, 0x84, 0xc0, 0x61, 0xf2, 0x1b, 0x5d, 0x19, 0x28, 0xc3, - 0x36, 0x4d, 0xd7, 0xa4, 0x0b, 0x75, 0x2f, 0x58, 0xeb, 0x07, 0x83, 0xfa, 0xb0, 0x4d, 0x93, 0xa5, - 0xf9, 0x0c, 0x54, 0x9b, 0x3b, 0x3c, 0x8e, 0x48, 0x07, 0x9a, 0xf3, 0xd9, 0xab, 0xd9, 0xd5, 0xf5, - 0xac, 0x5b, 0x4b, 0x36, 0xf6, 0x7c, 0x32, 0x99, 0xda, 0x76, 0x57, 0x21, 0x47, 0xd0, 0x9e, 0xcf, - 0x26, 0x17, 0xcf, 0x67, 0xe7, 0xd3, 0xb3, 0xee, 0x01, 0x69, 0x43, 0x63, 0x4a, 0xe9, 0x15, 0xed, - 0xd6, 0xcd, 0x3e, 0x68, 0x6f, 0x91, 0x45, 0x6e, 0xe0, 0x53, 0xc1, 0x82, 0xe2, 0xa7, 0x18, 0x23, - 0x6e, 0xbe, 0x84, 0x5e, 0xb9, 0x10, 0x85, 0x81, 0x1f, 0x61, 0x42, 0xcb, 0x77, 0x36, 0x98, 0xd3, - 0x4a, 0xd6, 0x44, 0x87, 0xe6, 0xad, 0x40, 0xeb, 0x07, 0xe9, 0xeb, 0x7c, 0x6b, 0x5e, 0x80, 0x76, - 0xe9, 0x47, 0xdc, 0xf1, 0xbc, 0xe2, 0x01, 0x64, 0x04, 0xcd, 0x4c, 0x78, 0xda, 0xa9, 0x33, 0xd6, - 0xac, 0xd4, 0xc4, 0xdc, 0x8d, 0x1c, 0x9e, 0xa3, 0xcc, 0x2f, 0xd0, 0x2b, 0x77, 0xca, 0x18, 0xfd, - 0x6d, 0x2b, 0xf2, 0x14, 0x54, 0x96, 0x7a, 0x9c, 0xb2, 0xed, 0x8c, 0x1f, 0x58, 0xb2, 0xef, 0x67, - 0x89, 0xef, 0x40, 0x33, 0xac, 0x79, 0x0e, 0xc7, 0x67, 0xe8, 0x21, 0xc7, 0x7f, 0x55, 0xf2, 0x19, - 0xb4, 0x52, 0xa3, 0xfd, 0x0a, 0xf9, 0xae, 0x80, 0x36, 0x0f, 0xd7, 0xcc, 0x59, 0x4a, 0xa4, 0x2c, - 0x62, 0xc6, 0xd0, 0xe7, 0x7f, 0x20, 0x90, 0xa1, 0xc8, 0x29, 0xa8, 0xdc, 0x61, 0x6b, 0xcc, 0x09, - 0x54, 0xe0, 0x33, 0x50, 0x32, 0x27, 0x6f, 0xdc, 0x0d, 0x06, 0x31, 0xd7, 0xeb, 0x03, 0x65, 0x58, - 0xa7, 0xf9, 0x36, 0x99, 0xaa, 0x6b, 0xc7, 0xe5, 0xfa, 0xe1, 0x40, 0x19, 0xb6, 0x68, 0xba, 0x26, - 0x06, 0xb4, 0x28, 0x2e, 0x18, 0x3a, 0x1c, 0xf5, 0x46, 0xfa, 0x7e, 0xbb, 0x4f, 0xa6, 0xa1, 0x2c, - 0x61, 0xbf, 0x26, 0xfe, 0x50, 0xa0, 0x47, 0x03, 0xcf, 0xbb, 0x71, 0x16, 0x1f, 0xff, 0x5b, 0x17, - 0xbf, 0x2a, 0xd0, 0xff, 0x4d, 0xc4, 0xde, 0x6f, 0x55, 0xd6, 0x49, 0xc4, 0xd8, 0x9d, 0x6f, 0x55, - 0x08, 0x5a, 0xa9, 0xd1, 0x5d, 0x85, 0x3c, 0xcc, 0x82, 0x57, 0xc8, 0x20, 0x45, 0xf4, 0xa5, 0xbf, - 0x0a, 0x44, 0x18, 0x8f, 0xbf, 0x35, 0xb6, 0xdc, 0x5f, 0x07, 0xcb, 0xd8, 0x43, 0x5b, 0x48, 0x25, - 0x2b, 0x68, 0x66, 0xe1, 0x49, 0x4e, 0xe4, 0x26, 0x48, 0x43, 0xd7, 0x78, 0xbc, 0x1b, 0x58, 0xe8, - 0x32, 0x6b, 0x64, 0x03, 0xf7, 0x8a, 0x91, 0x58, 0x75, 0x9c, 0x34, 0x82, 0xab, 0x8e, 0x93, 0xa7, - 0xac, 0x59, 0x23, 0x1f, 0xe0, 0xa8, 0x90, 0x5b, 0xe4, 0x91, 0xbc, 0x81, 0x2c, 0x25, 0x8d, 0x93, - 0x9d, 0xb0, 0xdb, 0xb3, 0x42, 0xb8, 0x5f, 0x1a, 0x4c, 0x52, 0x41, 0x57, 0x7e, 0x09, 0x8d, 0xd3, - 0x1d, 0xd1, 0xbf, 0x9a, 0x59, 0x4c, 0x94, 0x2a, 0x33, 0xa5, 0xd1, 0x59, 0x65, 0xa6, 0x3c, 0xa4, - 0x84, 0x99, 0x85, 0x71, 0xad, 0x32, 0x53, 0x76, 0x39, 0xaa, 0xcc, 0x94, 0xce, 0xbf, 0x59, 0x7b, - 0xd1, 0x7a, 0xa7, 0x0a, 0xc4, 0x8d, 0x9a, 0xfe, 0xc9, 0x78, 0xf2, 0x33, 0x00, 0x00, 0xff, 0xff, - 0x52, 0x0c, 0xa8, 0x50, 0xcb, 0x08, 0x00, 0x00, + // 597 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x56, 0x5f, 0x8f, 0xd2, 0x4e, + 0x14, 0xa5, 0xb0, 0x14, 0xb8, 0x64, 0x7f, 0x3f, 0x32, 0xa1, 0xd0, 0x34, 0x3e, 0x90, 0x3e, 0x18, + 0xe2, 0xba, 0x25, 0x41, 0x1f, 0x7d, 0x51, 0x96, 0xfd, 0x13, 0x23, 0x9b, 0x0c, 0xe2, 0x26, 0xbe, + 0x75, 0xe1, 0x82, 0xd5, 0xd2, 0xd6, 0xe9, 0x74, 0x1f, 0xd5, 0x4f, 0xe3, 0x57, 0xd2, 0x8f, 0x63, + 0xda, 0x69, 0x89, 0xad, 0xd3, 0x88, 0x6b, 0xc2, 0x83, 0x4f, 0x9d, 0xe9, 0x3d, 0xdc, 0x39, 0xe7, + 0xf4, 0xce, 0x09, 0xa0, 0xbf, 0xb3, 0x03, 0x67, 0xc4, 0xa2, 0xd5, 0x0a, 0x59, 0xfa, 0xb0, 0x02, + 0xe6, 0x73, 0x9f, 0x74, 0xe3, 0x8a, 0x15, 0x22, 0xbb, 0x73, 0x96, 0x18, 0x5a, 0xa2, 0x66, 0xf4, + 0x05, 0x1e, 0x5d, 0xb4, 0x43, 0x1c, 0x39, 0xde, 0xda, 0x17, 0x70, 0xc3, 0xc8, 0x15, 0xd2, 0xa7, + 0xa8, 0x99, 0x2e, 0xa8, 0x14, 0xc3, 0xc8, 0xe5, 0x84, 0xc0, 0x51, 0xfc, 0x1b, 0x5d, 0x19, 0x28, + 0xc3, 0x16, 0x4d, 0xd6, 0xa4, 0x03, 0x35, 0xd7, 0xdf, 0xe8, 0xd5, 0x41, 0x6d, 0xd8, 0xa2, 0xf1, + 0xd2, 0x7c, 0x06, 0xea, 0x9c, 0xdb, 0x3c, 0x0a, 0x49, 0x1b, 0x1a, 0x8b, 0xd9, 0xcb, 0xd9, 0xf5, + 0xcd, 0xac, 0x53, 0x89, 0x37, 0xf3, 0xc5, 0x64, 0x32, 0x9d, 0xcf, 0x3b, 0x0a, 0x39, 0x86, 0xd6, + 0x62, 0x36, 0xb9, 0x7c, 0x3e, 0xbb, 0x98, 0x9e, 0x75, 0xaa, 0xa4, 0x05, 0xf5, 0x29, 0xa5, 0xd7, + 0xb4, 0x53, 0x33, 0xfb, 0xa0, 0xbd, 0x41, 0x16, 0x3a, 0xbe, 0x47, 0x05, 0x0b, 0x8a, 0x1f, 0x23, + 0x0c, 0xb9, 0x79, 0x0e, 0xbd, 0x62, 0x21, 0x0c, 0x7c, 0x2f, 0xc4, 0x98, 0x96, 0x67, 0x6f, 0x31, + 0xa3, 0x15, 0xaf, 0x89, 0x0e, 0x8d, 0x3b, 0x81, 0xd6, 0xab, 0xc9, 0xeb, 0x6c, 0x6b, 0x5e, 0x82, + 0x76, 0xe5, 0x85, 0xdc, 0x76, 0xdd, 0xfc, 0x01, 0x64, 0x04, 0x8d, 0x54, 0x78, 0xd2, 0xa9, 0x3d, + 0xd6, 0xac, 0xc4, 0xc4, 0xcc, 0x8d, 0x0c, 0x9e, 0xa1, 0xcc, 0xcf, 0xd0, 0x2b, 0x76, 0x4a, 0x19, + 0xfd, 0x69, 0x2b, 0xf2, 0x14, 0x54, 0x96, 0x78, 0x9c, 0xb0, 0x6d, 0x8f, 0x1f, 0x58, 0xb2, 0xef, + 0x67, 0x89, 0xef, 0x40, 0x53, 0xac, 0x79, 0x01, 0xdd, 0x33, 0x74, 0x91, 0xe3, 0xdf, 0x2a, 0xf9, + 0x04, 0x5a, 0xa1, 0xd1, 0x61, 0x85, 0x7c, 0x53, 0x40, 0x5b, 0x04, 0x1b, 0x66, 0xaf, 0x24, 0x52, + 0x96, 0x11, 0x63, 0xe8, 0xf1, 0xdf, 0x10, 0x48, 0x51, 0xe4, 0x14, 0x54, 0x6e, 0xb3, 0x0d, 0x66, + 0x04, 0x4a, 0xf0, 0x29, 0x28, 0x9e, 0x93, 0xd7, 0xce, 0x16, 0xfd, 0x88, 0xeb, 0xb5, 0x81, 0x32, + 0xac, 0xd1, 0x6c, 0x1b, 0x4f, 0xd5, 0x8d, 0xed, 0x70, 0xfd, 0x68, 0xa0, 0x0c, 0x9b, 0x34, 0x59, + 0x13, 0x03, 0x9a, 0x14, 0x97, 0x0c, 0x6d, 0x8e, 0x7a, 0x3d, 0x79, 0xbf, 0xdb, 0x93, 0x2e, 0xd4, + 0xcf, 0x7d, 0xb6, 0x44, 0x5d, 0x4d, 0x0a, 0x62, 0x13, 0xcf, 0x48, 0x51, 0xd8, 0x61, 0xad, 0xfd, + 0xae, 0x40, 0x8f, 0xfa, 0xae, 0x7b, 0x6b, 0x2f, 0x3f, 0xfc, 0x63, 0xde, 0x7e, 0x51, 0xa0, 0xff, + 0x8b, 0xb4, 0x83, 0xdf, 0xc0, 0xb4, 0x93, 0x88, 0xbc, 0x7b, 0xdf, 0xc0, 0x00, 0xb4, 0x42, 0xa3, + 0xfb, 0x0a, 0x79, 0x98, 0x86, 0xb4, 0x90, 0x41, 0xf2, 0xe8, 0x2b, 0x6f, 0xed, 0x8b, 0xe0, 0x1e, + 0x7f, 0xad, 0xef, 0xb8, 0xbf, 0xf2, 0x57, 0x91, 0x8b, 0x73, 0x21, 0x95, 0xac, 0xa1, 0x91, 0x06, + 0x2d, 0x39, 0x91, 0x9b, 0x20, 0x0d, 0x68, 0xe3, 0xf1, 0x7e, 0x60, 0xa1, 0xcb, 0xac, 0x90, 0x2d, + 0xfc, 0x97, 0x8f, 0xcf, 0xb2, 0xe3, 0xa4, 0x71, 0x5d, 0x76, 0x9c, 0x3c, 0x91, 0xcd, 0x0a, 0x79, + 0x0f, 0xc7, 0xb9, 0x8c, 0x23, 0x8f, 0xe4, 0x0d, 0x64, 0x89, 0x6a, 0x9c, 0xec, 0x85, 0xdd, 0x9d, + 0x15, 0xc0, 0xff, 0x85, 0xc1, 0x24, 0x25, 0x74, 0xe5, 0x57, 0xd3, 0x38, 0xdd, 0x13, 0xfd, 0xb3, + 0x99, 0xf9, 0x9c, 0x29, 0x33, 0x53, 0x1a, 0xb3, 0x65, 0x66, 0xca, 0xa3, 0x4b, 0x98, 0x99, 0x1b, + 0xd7, 0x32, 0x33, 0x65, 0x97, 0xa3, 0xcc, 0x4c, 0xe9, 0xfc, 0x9b, 0x95, 0x17, 0xcd, 0xb7, 0xaa, + 0x40, 0xdc, 0xaa, 0xc9, 0x1f, 0x92, 0x27, 0x3f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb6, 0xa5, 0x37, + 0x75, 0xf7, 0x08, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index d3f8676fc..d629320c5 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -40,6 +40,7 @@ import hapi_chart3 "k8s.io/helm/pkg/proto/hapi/chart" import hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" import hapi_release5 "k8s.io/helm/pkg/proto/hapi/release" import hapi_release4 "k8s.io/helm/pkg/proto/hapi/release" +import hapi_release1 "k8s.io/helm/pkg/proto/hapi/release" import hapi_release3 "k8s.io/helm/pkg/proto/hapi/release" import hapi_version "k8s.io/helm/pkg/proto/hapi/version" @@ -374,6 +375,8 @@ type UpdateReleaseRequest struct { // 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"` + // Force resource update through delete/recreate if needed. + Force bool `protobuf:"varint,11,opt,name=force" json:"force,omitempty"` } func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } @@ -451,6 +454,13 @@ func (m *UpdateReleaseRequest) GetReuseValues() bool { return false } +func (m *UpdateReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + // UpdateReleaseResponse is the response to an update request. type UpdateReleaseResponse struct { Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` @@ -484,6 +494,8 @@ type RollbackReleaseRequest 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,7,opt,name=wait" json:"wait,omitempty"` + // Force resource update through delete/recreate if needed. + Force bool `protobuf:"varint,8,opt,name=force" json:"force,omitempty"` } func (m *RollbackReleaseRequest) Reset() { *m = RollbackReleaseRequest{} } @@ -540,6 +552,13 @@ func (m *RollbackReleaseRequest) GetWait() bool { return false } +func (m *RollbackReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + // RollbackReleaseResponse is the response to an update request. type RollbackReleaseResponse struct { Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` @@ -848,7 +867,8 @@ func (m *TestReleaseRequest) GetCleanup() bool { // TestReleaseResponse represents a message from executing a test type TestReleaseResponse struct { - Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` + Status hapi_release1.TestRun_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestRun_Status" json:"status,omitempty"` } func (m *TestReleaseResponse) Reset() { *m = TestReleaseResponse{} } @@ -863,6 +883,13 @@ func (m *TestReleaseResponse) GetMsg() string { return "" } +func (m *TestReleaseResponse) GetStatus() hapi_release1.TestRun_Status { + if m != nil { + return m.Status + } + return hapi_release1.TestRun_UNKNOWN +} + func init() { proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest") proto.RegisterType((*ListSort)(nil), "hapi.services.tiller.ListSort") @@ -1342,79 +1369,82 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 1170 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 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, + // 1217 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0xc4, + 0x17, 0xaf, 0xf3, 0x9d, 0x93, 0x36, 0xff, 0x74, 0x9a, 0xb6, 0xae, 0xff, 0x0b, 0x2a, 0x46, 0xb0, + 0xd9, 0x85, 0x4d, 0x21, 0x70, 0x83, 0x84, 0x90, 0xba, 0xdd, 0xa8, 0x2d, 0x94, 0xae, 0xe4, 0x6c, + 0x17, 0x09, 0x01, 0x91, 0x9b, 0x4c, 0x5a, 0xb3, 0x8e, 0x27, 0x78, 0xc6, 0x65, 0x7b, 0xcb, 0x1d, + 0x8f, 0xc2, 0x5b, 0xf0, 0x1e, 0x5c, 0xc2, 0x83, 0x20, 0xcf, 0x87, 0xeb, 0x49, 0xed, 0xd6, 0xf4, + 0x26, 0x9e, 0x99, 0xf3, 0xfd, 0x3b, 0x67, 0xce, 0x9c, 0x80, 0x75, 0xe9, 0x2e, 0xbc, 0x3d, 0x8a, + 0xc3, 0x2b, 0x6f, 0x82, 0xe9, 0x1e, 0xf3, 0x7c, 0x1f, 0x87, 0xfd, 0x45, 0x48, 0x18, 0x41, 0xdd, + 0x98, 0xd6, 0x57, 0xb4, 0xbe, 0xa0, 0x59, 0x5b, 0x5c, 0x62, 0x72, 0xe9, 0x86, 0x4c, 0xfc, 0x0a, + 0x6e, 0x6b, 0x3b, 0x7d, 0x4e, 0x82, 0x99, 0x77, 0x21, 0x09, 0xc2, 0x44, 0x88, 0x7d, 0xec, 0x52, + 0xac, 0xbe, 0x9a, 0x90, 0xa2, 0x79, 0xc1, 0x8c, 0x48, 0xc2, 0xff, 0x35, 0x02, 0xc3, 0x94, 0x8d, + 0xc3, 0x28, 0x90, 0xc4, 0x1d, 0x8d, 0x48, 0x99, 0xcb, 0x22, 0xaa, 0x19, 0xbb, 0xc2, 0x21, 0xf5, + 0x48, 0xa0, 0xbe, 0x82, 0x66, 0xff, 0x59, 0x82, 0x8d, 0x13, 0x8f, 0x32, 0x47, 0x08, 0x52, 0x07, + 0xff, 0x12, 0x61, 0xca, 0x50, 0x17, 0xaa, 0xbe, 0x37, 0xf7, 0x98, 0x69, 0xec, 0x1a, 0xbd, 0xb2, + 0x23, 0x36, 0x68, 0x0b, 0x6a, 0x64, 0x36, 0xa3, 0x98, 0x99, 0xa5, 0x5d, 0xa3, 0xd7, 0x74, 0xe4, + 0x0e, 0x7d, 0x05, 0x75, 0x4a, 0x42, 0x36, 0x3e, 0xbf, 0x36, 0xcb, 0xbb, 0x46, 0xaf, 0x3d, 0xf8, + 0xa0, 0x9f, 0x85, 0x53, 0x3f, 0xb6, 0x34, 0x22, 0x21, 0xeb, 0xc7, 0x3f, 0xcf, 0xaf, 0x9d, 0x1a, + 0xe5, 0xdf, 0x58, 0xef, 0xcc, 0xf3, 0x19, 0x0e, 0xcd, 0x8a, 0xd0, 0x2b, 0x76, 0xe8, 0x10, 0x80, + 0xeb, 0x25, 0xe1, 0x14, 0x87, 0x66, 0x95, 0xab, 0xee, 0x15, 0x50, 0xfd, 0x32, 0xe6, 0x77, 0x9a, + 0x54, 0x2d, 0xd1, 0x97, 0xb0, 0x2a, 0x20, 0x19, 0x4f, 0xc8, 0x14, 0x53, 0xb3, 0xb6, 0x5b, 0xee, + 0xb5, 0x07, 0x3b, 0x42, 0x95, 0x82, 0x7f, 0x24, 0x40, 0x3b, 0x20, 0x53, 0xec, 0xb4, 0x04, 0x7b, + 0xbc, 0xa6, 0xe8, 0x11, 0x34, 0x03, 0x77, 0x8e, 0xe9, 0xc2, 0x9d, 0x60, 0xb3, 0xce, 0x3d, 0xbc, + 0x39, 0xb0, 0x7f, 0x82, 0x86, 0x32, 0x6e, 0x0f, 0xa0, 0x26, 0x42, 0x43, 0x2d, 0xa8, 0x9f, 0x9d, + 0x7e, 0x73, 0xfa, 0xf2, 0xbb, 0xd3, 0xce, 0x0a, 0x6a, 0x40, 0xe5, 0x74, 0xff, 0xdb, 0x61, 0xc7, + 0x40, 0xeb, 0xb0, 0x76, 0xb2, 0x3f, 0x7a, 0x35, 0x76, 0x86, 0x27, 0xc3, 0xfd, 0xd1, 0xf0, 0x45, + 0xa7, 0x64, 0xbf, 0x0b, 0xcd, 0xc4, 0x67, 0x54, 0x87, 0xf2, 0xfe, 0xe8, 0x40, 0x88, 0xbc, 0x18, + 0x8e, 0x0e, 0x3a, 0x86, 0xfd, 0xbb, 0x01, 0x5d, 0x3d, 0x45, 0x74, 0x41, 0x02, 0x8a, 0xe3, 0x1c, + 0x4d, 0x48, 0x14, 0x24, 0x39, 0xe2, 0x1b, 0x84, 0xa0, 0x12, 0xe0, 0xb7, 0x2a, 0x43, 0x7c, 0x1d, + 0x73, 0x32, 0xc2, 0x5c, 0x9f, 0x67, 0xa7, 0xec, 0x88, 0x0d, 0xfa, 0x14, 0x1a, 0x32, 0x74, 0x6a, + 0x56, 0x76, 0xcb, 0xbd, 0xd6, 0x60, 0x53, 0x07, 0x44, 0x5a, 0x74, 0x12, 0x36, 0xfb, 0x10, 0xb6, + 0x0f, 0xb1, 0xf2, 0x44, 0xe0, 0xa5, 0x2a, 0x26, 0xb6, 0xeb, 0xce, 0x31, 0x77, 0x26, 0xb6, 0xeb, + 0xce, 0x31, 0x32, 0xa1, 0x2e, 0xcb, 0x8d, 0xbb, 0x53, 0x75, 0xd4, 0xd6, 0x66, 0x60, 0xde, 0x56, + 0x24, 0xe3, 0xca, 0xd2, 0xf4, 0x21, 0x54, 0xe2, 0x9b, 0xc0, 0xd5, 0xb4, 0x06, 0x48, 0xf7, 0xf3, + 0x38, 0x98, 0x11, 0x87, 0xd3, 0xf5, 0x54, 0x95, 0x97, 0x53, 0x75, 0x94, 0xb6, 0x7a, 0x40, 0x02, + 0x86, 0x03, 0xf6, 0x30, 0xff, 0x4f, 0x60, 0x27, 0x43, 0x93, 0x0c, 0x60, 0x0f, 0xea, 0xd2, 0x35, + 0xae, 0x2d, 0x17, 0x57, 0xc5, 0x65, 0xff, 0x5d, 0x82, 0xee, 0xd9, 0x62, 0xea, 0x32, 0xac, 0x48, + 0x77, 0x38, 0xf5, 0x18, 0xaa, 0xbc, 0xa3, 0x48, 0x2c, 0xd6, 0x85, 0x6e, 0xd1, 0x76, 0x0e, 0xe2, + 0x5f, 0x47, 0xd0, 0xd1, 0x53, 0xa8, 0x5d, 0xb9, 0x7e, 0x84, 0x29, 0x07, 0x22, 0x41, 0x4d, 0x72, + 0xf2, 0x76, 0xe4, 0x48, 0x0e, 0xb4, 0x0d, 0xf5, 0x69, 0x78, 0x1d, 0xf7, 0x13, 0x7e, 0x05, 0x1b, + 0x4e, 0x6d, 0x1a, 0x5e, 0x3b, 0x51, 0x80, 0xde, 0x87, 0xb5, 0xa9, 0x47, 0xdd, 0x73, 0x1f, 0x8f, + 0x2f, 0x09, 0x79, 0x43, 0xf9, 0x2d, 0x6c, 0x38, 0xab, 0xf2, 0xf0, 0x28, 0x3e, 0x43, 0x56, 0x5c, + 0x49, 0x93, 0x10, 0xbb, 0x0c, 0x9b, 0x35, 0x4e, 0x4f, 0xf6, 0x31, 0x86, 0xcc, 0x9b, 0x63, 0x12, + 0x31, 0x7e, 0x75, 0xca, 0x8e, 0xda, 0xa2, 0xf7, 0x60, 0x35, 0xc4, 0x14, 0xb3, 0xb1, 0xf4, 0xb2, + 0xc1, 0x25, 0x5b, 0xfc, 0xec, 0xb5, 0x70, 0x0b, 0x41, 0xe5, 0x57, 0xd7, 0x63, 0x66, 0x93, 0x93, + 0xf8, 0x5a, 0x88, 0x45, 0x14, 0x2b, 0x31, 0x50, 0x62, 0x11, 0xc5, 0x52, 0xac, 0x0b, 0xd5, 0x19, + 0x09, 0x27, 0xd8, 0x6c, 0x71, 0x9a, 0xd8, 0xd8, 0x47, 0xb0, 0xb9, 0x04, 0xf2, 0x43, 0xf3, 0xf5, + 0x8f, 0x01, 0x5b, 0x0e, 0xf1, 0xfd, 0x73, 0x77, 0xf2, 0xa6, 0x40, 0xc6, 0x52, 0xe0, 0x96, 0xee, + 0x06, 0xb7, 0x9c, 0x01, 0x6e, 0xaa, 0x08, 0x2b, 0x5a, 0x11, 0x6a, 0xb0, 0x57, 0xf3, 0x61, 0xaf, + 0xe9, 0xb0, 0x2b, 0x4c, 0xeb, 0x29, 0x4c, 0x13, 0xc0, 0x1a, 0x69, 0xc0, 0xbe, 0x86, 0xed, 0x5b, + 0x51, 0x3e, 0x14, 0xb2, 0x3f, 0x4a, 0xb0, 0x79, 0x1c, 0x50, 0xe6, 0xfa, 0xfe, 0x12, 0x62, 0x49, + 0x3d, 0x1b, 0x85, 0xeb, 0xb9, 0xf4, 0x5f, 0xea, 0xb9, 0xac, 0x41, 0xae, 0xf2, 0x53, 0x49, 0xe5, + 0xa7, 0x50, 0x8d, 0x6b, 0x9d, 0xa5, 0xb6, 0xd4, 0x59, 0xd0, 0x3b, 0x00, 0xa2, 0x28, 0xb9, 0x72, + 0x01, 0x6d, 0x93, 0x9f, 0x9c, 0xca, 0x46, 0xa2, 0xb2, 0xd1, 0xc8, 0xce, 0x46, 0xaa, 0xc2, 0xed, + 0x63, 0xd8, 0x5a, 0x86, 0xea, 0xa1, 0xb0, 0xff, 0x66, 0xc0, 0xf6, 0x59, 0xe0, 0x65, 0x02, 0x9f, + 0x55, 0xaa, 0xb7, 0xa0, 0x28, 0x65, 0x40, 0xd1, 0x85, 0xea, 0x22, 0x0a, 0x2f, 0xb0, 0x84, 0x56, + 0x6c, 0xd2, 0x31, 0x56, 0xb4, 0x18, 0xed, 0x31, 0x98, 0xb7, 0x7d, 0x78, 0x60, 0x44, 0xb1, 0xd7, + 0xc9, 0x4b, 0xd0, 0x14, 0x5d, 0xdf, 0xde, 0x80, 0xf5, 0x43, 0xcc, 0x5e, 0x8b, 0x6b, 0x21, 0xc3, + 0xb3, 0x87, 0x80, 0xd2, 0x87, 0x37, 0xf6, 0xe4, 0x91, 0x6e, 0x4f, 0x8d, 0x45, 0x8a, 0x5f, 0x71, + 0xd9, 0x5f, 0x70, 0xdd, 0x47, 0x1e, 0x65, 0x24, 0xbc, 0xbe, 0x0b, 0xba, 0x0e, 0x94, 0xe7, 0xee, + 0x5b, 0xf9, 0x50, 0xc4, 0x4b, 0xfb, 0x90, 0x7b, 0x90, 0x88, 0x4a, 0x0f, 0xd2, 0xcf, 0xae, 0x51, + 0xec, 0xd9, 0xfd, 0x01, 0xd0, 0x2b, 0x9c, 0x4c, 0x00, 0xf7, 0xbc, 0x58, 0x2a, 0x09, 0x25, 0xbd, + 0xd0, 0x4c, 0xa8, 0x4f, 0x7c, 0xec, 0x06, 0xd1, 0x42, 0xa6, 0x4d, 0x6d, 0xed, 0x1f, 0x61, 0x43, + 0xd3, 0x2e, 0xfd, 0x8c, 0xe3, 0xa1, 0x17, 0x52, 0x7b, 0xbc, 0x44, 0x9f, 0x43, 0x4d, 0x8c, 0x45, + 0x5c, 0x77, 0x7b, 0xf0, 0x48, 0xf7, 0x9b, 0x2b, 0x89, 0x02, 0x39, 0x47, 0x39, 0x92, 0x77, 0xf0, + 0x57, 0x03, 0xda, 0xea, 0xa1, 0x17, 0x43, 0x1b, 0xf2, 0x60, 0x35, 0x3d, 0xd1, 0xa0, 0x27, 0xf9, + 0x33, 0xdd, 0xd2, 0x60, 0x6a, 0x3d, 0x2d, 0xc2, 0x2a, 0x22, 0xb0, 0x57, 0x3e, 0x31, 0x10, 0x85, + 0xce, 0xf2, 0xa0, 0x81, 0x9e, 0x65, 0xeb, 0xc8, 0x99, 0x6c, 0xac, 0x7e, 0x51, 0x76, 0x65, 0x16, + 0x5d, 0xf1, 0x9a, 0xd1, 0xa7, 0x03, 0x74, 0xaf, 0x1a, 0x7d, 0x20, 0xb1, 0xf6, 0x0a, 0xf3, 0x27, + 0x76, 0x7f, 0x86, 0x35, 0xed, 0x85, 0x43, 0x39, 0x68, 0x65, 0xcd, 0x1a, 0xd6, 0x47, 0x85, 0x78, + 0x13, 0x5b, 0x73, 0x68, 0xeb, 0x4d, 0x0a, 0xe5, 0x28, 0xc8, 0xec, 0xfa, 0xd6, 0xc7, 0xc5, 0x98, + 0x13, 0x73, 0x14, 0x3a, 0xcb, 0x3d, 0x24, 0x2f, 0x8f, 0x39, 0xfd, 0x2e, 0x2f, 0x8f, 0x79, 0xad, + 0xc9, 0x5e, 0x41, 0x2e, 0xc0, 0x4d, 0x0b, 0x41, 0x8f, 0x73, 0x13, 0xa2, 0x77, 0x1e, 0xab, 0x77, + 0x3f, 0x63, 0x62, 0x62, 0x01, 0xff, 0x5b, 0x7a, 0x63, 0x51, 0x0e, 0x34, 0xd9, 0x03, 0x87, 0xf5, + 0xac, 0x20, 0xf7, 0x52, 0x50, 0xb2, 0x2b, 0xdd, 0x11, 0x94, 0xde, 0xf2, 0xee, 0x08, 0x6a, 0xa9, + 0xc1, 0xd9, 0x2b, 0xc8, 0x83, 0xb6, 0x13, 0x05, 0xd2, 0x74, 0xdc, 0x16, 0x50, 0x8e, 0xf4, 0xed, + 0xae, 0x66, 0x3d, 0x29, 0xc0, 0x79, 0x73, 0xbf, 0x9f, 0xc3, 0xf7, 0x0d, 0xc5, 0x7a, 0x5e, 0xe3, + 0xff, 0x69, 0x3f, 0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x7c, 0x9c, 0x49, 0xc1, 0x0f, 0x00, + 0x00, } diff --git a/pkg/releasetesting/environment.go b/pkg/releasetesting/environment.go index a56721333..51c1aa95a 100644 --- a/pkg/releasetesting/environment.go +++ b/pkg/releasetesting/environment.go @@ -83,31 +83,31 @@ func (env *Environment) streamResult(r *release.TestRun) error { func (env *Environment) streamRunning(name string) error { msg := "RUNNING: " + name - return env.streamMessage(msg) + return env.streamMessage(msg, release.TestRun_RUNNING) } func (env *Environment) streamError(info string) error { msg := "ERROR: " + info - return env.streamMessage(msg) + return env.streamMessage(msg, release.TestRun_FAILURE) } func (env *Environment) streamFailed(name string) error { msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s --namespace %s` for more info", name, name, env.Namespace) - return env.streamMessage(msg) + return env.streamMessage(msg, release.TestRun_FAILURE) } func (env *Environment) streamSuccess(name string) error { msg := fmt.Sprintf("PASSED: %s", name) - return env.streamMessage(msg) + return env.streamMessage(msg, release.TestRun_SUCCESS) } func (env *Environment) streamUnknown(name, info string) error { msg := fmt.Sprintf("UNKNOWN: %s: %s", name, info) - return env.streamMessage(msg) + return env.streamMessage(msg, release.TestRun_UNKNOWN) } -func (env *Environment) streamMessage(msg string) error { - resp := &services.TestReleaseResponse{Msg: msg} +func (env *Environment) streamMessage(msg string, status release.TestRun_Status) error { + resp := &services.TestReleaseResponse{Msg: msg, Status: status} return env.Stream.Send(resp) } diff --git a/pkg/releasetesting/environment_test.go b/pkg/releasetesting/environment_test.go index deb8617f6..29ca93d09 100644 --- a/pkg/releasetesting/environment_test.go +++ b/pkg/releasetesting/environment_test.go @@ -24,6 +24,7 @@ import ( "testing" "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" tillerEnv "k8s.io/helm/pkg/tiller/environment" ) @@ -88,6 +89,29 @@ func TestDeleteTestPodsFailingDelete(t *testing.T) { } } +func TestStreamMessage(t *testing.T) { + mockTestEnv := newMockTestingEnvironment() + + expectedMessage := "testing streamMessage" + expectedStatus := release.TestRun_SUCCESS + err := mockTestEnv.streamMessage(expectedMessage, expectedStatus) + if err != nil { + t.Errorf("Expected no errors, got 1: %s", err) + } + + stream := mockTestEnv.Stream.(*mockStream) + if len(stream.messages) != 1 { + t.Errorf("Expected 1 message, got: %v", len(stream.messages)) + } + + if stream.messages[0].Msg != expectedMessage { + t.Errorf("Expected message: %s, got: %s", expectedMessage, stream.messages[0]) + } + if stream.messages[0].Status != expectedStatus { + t.Errorf("Expected status: %v, got: %v", expectedStatus, stream.messages[0].Status) + } +} + type MockTestingEnvironment struct { *Environment } @@ -110,7 +134,10 @@ func (mte MockTestingEnvironment) streamError(info string) error { retur func (mte MockTestingEnvironment) streamFailed(name string) error { return nil } func (mte MockTestingEnvironment) streamSuccess(name string) error { return nil } func (mte MockTestingEnvironment) streamUnknown(name, info string) error { return nil } -func (mte MockTestingEnvironment) streamMessage(msg string) error { return nil } +func (mte MockTestingEnvironment) streamMessage(msg string, status release.TestRun_Status) error { + mte.Stream.Send(&services.TestReleaseResponse{Msg: msg, Status: status}) + return nil +} type getFailingKubeClient struct { tillerEnv.PrintingKubeClient @@ -123,7 +150,7 @@ func newGetFailingKubeClient() *getFailingKubeClient { } func (p *getFailingKubeClient) Get(ns string, r io.Reader) (string, error) { - return "", errors.New("In the end, they did not find Nemo.") + return "", errors.New("in the end, they did not find Nemo") } type deleteFailingKubeClient struct { diff --git a/pkg/releasetesting/test_suite.go b/pkg/releasetesting/test_suite.go index f4362dc5b..e5e8db51e 100644 --- a/pkg/releasetesting/test_suite.go +++ b/pkg/releasetesting/test_suite.go @@ -65,7 +65,8 @@ func (ts *TestSuite) Run(env *Environment) error { ts.StartedAt = timeconv.Now() if len(ts.TestManifests) == 0 { - env.streamMessage("No Tests Found") + // TODO: make this better, adding test run status on test suite is weird + env.streamMessage("No Tests Found", release.TestRun_UNKNOWN) } for _, testManifest := range ts.TestManifests { @@ -78,6 +79,7 @@ func (ts *TestSuite) Run(env *Environment) error { if err := env.streamRunning(test.result.Name); err != nil { return err } + test.result.Status = release.TestRun_RUNNING resourceCreated := true if err := env.createTestPod(test); err != nil { @@ -93,7 +95,7 @@ func (ts *TestSuite) Run(env *Environment) error { status, err = env.getTestPodStatus(test) if err != nil { resourceCleanExit = false - if streamErr := env.streamUnknown(test.result.Name, test.result.Info); streamErr != nil { + if streamErr := env.streamError(test.result.Info); streamErr != nil { return streamErr } } diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go index cbf54c572..5e1b5c6cd 100644 --- a/pkg/repo/repo.go +++ b/pkg/repo/repo.go @@ -23,6 +23,7 @@ import ( "os" "time" + "github.com/facebookgo/atomicfile" "github.com/ghodss/yaml" ) @@ -134,9 +135,20 @@ func (r *RepoFile) Remove(name string) bool { // WriteFile writes a repositories file to the given path. func (r *RepoFile) WriteFile(path string, perm os.FileMode) error { + f, err := atomicfile.New(path, perm) + if err != nil { + return err + } + data, err := yaml.Marshal(r) if err != nil { return err } - return ioutil.WriteFile(path, data, perm) + + _, err = f.File.Write(data) + if err != nil { + return err + } + + return f.Close() } diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go index 6aee41faf..d4500c9e2 100644 --- a/pkg/repo/repo_test.go +++ b/pkg/repo/repo_test.go @@ -201,10 +201,18 @@ func TestWriteFile(t *testing.T) { t.Errorf("failed to create test-file (%v)", err) } defer os.Remove(repoFile.Name()) - if err := sampleRepository.WriteFile(repoFile.Name(), 744); err != nil { + + fileMode := os.FileMode(0744) + if err := sampleRepository.WriteFile(repoFile.Name(), fileMode); err != nil { t.Errorf("failed to write file (%v)", err) } + info, _ := os.Stat(repoFile.Name()) + mode := info.Mode() + if mode != fileMode { + t.Errorf("incorrect file mode: %s (expected %s)", mode, fileMode) + } + repos, err := LoadRepositoriesFile(repoFile.Name()) if err != nil { t.Errorf("failed to load file (%v)", err) diff --git a/pkg/resolver/resolver_test.go b/pkg/resolver/resolver_test.go index ef5a3bee0..9453a979f 100644 --- a/pkg/resolver/resolver_test.go +++ b/pkg/resolver/resolver_test.go @@ -146,7 +146,7 @@ func TestResolve(t *testing.T) { } func TestHashReq(t *testing.T) { - expect := "sha256:1feffe2016ca113f64159d91c1f77d6a83bcd23510b171d9264741bf9d63f741" + expect := "sha256:917e251ddba291096889f81eb7de713ab4e1afbbb07c576dfd7d66ba9300b12b" req := &chartutil.Requirements{ Dependencies: []*chartutil.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index d7d48a032..1c30951f3 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -22,7 +22,6 @@ import ( "encoding/base64" "fmt" "io/ioutil" - "log" "strconv" "strings" "time" @@ -51,12 +50,16 @@ var magicGzip = []byte{0x1f, 0x8b, 0x08} // ConfigMapsInterface. type ConfigMaps struct { impl internalversion.ConfigMapInterface + Log func(string, ...interface{}) } // NewConfigMaps initializes a new ConfigMaps wrapping an implmenetation of // the kubernetes ConfigMapsInterface. func NewConfigMaps(impl internalversion.ConfigMapInterface) *ConfigMaps { - return &ConfigMaps{impl: impl} + return &ConfigMaps{ + impl: impl, + Log: func(_ string, _ ...interface{}) {}, + } } // Name returns the name of the driver. @@ -74,13 +77,13 @@ func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { return nil, ErrReleaseNotFound(key) } - logerrf(err, "get: failed to get %q", key) + cfgmaps.Log("get: failed to get %q: %s", key, err) return nil, err } // found the configmap, decode the base64 data string r, err := decodeRelease(obj.Data["release"]) if err != nil { - logerrf(err, "get: failed to decode data %q", key) + cfgmaps.Log("get: failed to decode data %q: %s", key, err) return nil, err } // return the release object @@ -96,7 +99,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas list, err := cfgmaps.impl.List(opts) if err != nil { - logerrf(err, "list: failed to list") + cfgmaps.Log("list: failed to list: %s", err) return nil, err } @@ -107,7 +110,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas for _, item := range list.Items { rls, err := decodeRelease(item.Data["release"]) if err != nil { - logerrf(err, "list: failed to decode release: %v", item) + cfgmaps.Log("list: failed to decode release: %v: %s", item, err) continue } if filter(rls) { @@ -132,7 +135,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err list, err := cfgmaps.impl.List(opts) if err != nil { - logerrf(err, "query: failed to query with labels") + cfgmaps.Log("query: failed to query with labels: %s", err) return nil, err } @@ -144,7 +147,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err for _, item := range list.Items { rls, err := decodeRelease(item.Data["release"]) if err != nil { - logerrf(err, "query: failed to decode release: %s", err) + cfgmaps.Log("query: failed to decode release: %s", err) continue } results = append(results, rls) @@ -164,7 +167,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { // create a new configmap to hold the release obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { - logerrf(err, "create: failed to encode release %q", rls.Name) + cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err) return err } // push the configmap object out into the kubiverse @@ -173,7 +176,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { return ErrReleaseExists(rls.Name) } - logerrf(err, "create: failed to create") + cfgmaps.Log("create: failed to create: %s", err) return err } return nil @@ -191,13 +194,13 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { // create a new configmap object to hold the release obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { - logerrf(err, "update: failed to encode release %q", rls.Name) + cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err) return err } // push the configmap object out into the kubiverse _, err = cfgmaps.impl.Update(obj) if err != nil { - logerrf(err, "update: failed to update") + cfgmaps.Log("update: failed to update: %s", err) return err } return nil @@ -211,7 +214,7 @@ func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) { return nil, ErrReleaseExists(rls.Name) } - logerrf(err, "delete: failed to get release %q", key) + cfgmaps.Log("delete: failed to get release %q: %s", key, err) return nil, err } // delete the release @@ -316,8 +319,3 @@ func decodeRelease(data string) (*rspb.Release, error) { } return &rls, nil } - -// logerrf wraps an error with a formatted string (used for debugging) -func logerrf(err error, format string, args ...interface{}) { - log.Printf("configmaps: %s: %s\n", fmt.Sprintf(format, args...), err) -} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 46bd21d4a..b5aa16ccb 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -18,7 +18,6 @@ package storage // import "k8s.io/helm/pkg/storage" import ( "fmt" - "log" "sync" rspb "k8s.io/helm/pkg/proto/hapi/release" @@ -34,13 +33,15 @@ type Storage struct { releaseLocks map[string]*sync.Mutex // releaseLocksLock is a mutex for accessing releaseLocks releaseLocksLock *sync.Mutex + + Log func(string, ...interface{}) } // Get retrieves the release from storage. An error is returned // if the storage driver failed to fetch the release, or the // release identified by the key, version pair does not exist. func (s *Storage) Get(name string, version int32) (*rspb.Release, error) { - log.Printf("Getting release %q (v%d) from storage\n", name, version) + s.Log("getting release %q", makeKey(name, version)) return s.Driver.Get(makeKey(name, version)) } @@ -48,7 +49,7 @@ func (s *Storage) Get(name string, version int32) (*rspb.Release, error) { // error is returned if the storage driver failed to store the // release, or a release with identical an key already exists. func (s *Storage) Create(rls *rspb.Release) error { - log.Printf("Create release %q (v%d) in storage\n", rls.Name, rls.Version) + s.Log("creating release %q", makeKey(rls.Name, rls.Version)) return s.Driver.Create(makeKey(rls.Name, rls.Version), rls) } @@ -56,7 +57,7 @@ func (s *Storage) Create(rls *rspb.Release) error { // storage backend fails to update the release or if the release // does not exist. func (s *Storage) Update(rls *rspb.Release) error { - log.Printf("Updating %q (v%d) in storage\n", rls.Name, rls.Version) + s.Log("updating release %q", makeKey(rls.Name, rls.Version)) return s.Driver.Update(makeKey(rls.Name, rls.Version), rls) } @@ -64,21 +65,21 @@ func (s *Storage) Update(rls *rspb.Release) error { // the storage backend fails to delete the release or if the release // does not exist. func (s *Storage) Delete(name string, version int32) (*rspb.Release, error) { - log.Printf("Deleting release %q (v%d) from storage\n", name, version) + s.Log("deleting release %q", makeKey(name, version)) return s.Driver.Delete(makeKey(name, version)) } // ListReleases returns all releases from storage. An error is returned if the // storage backend fails to retrieve the releases. func (s *Storage) ListReleases() ([]*rspb.Release, error) { - log.Println("Listing all releases in storage") + s.Log("listing all releases in storage") return s.Driver.List(func(_ *rspb.Release) bool { return true }) } // ListDeleted returns all releases with Status == DELETED. An error is returned // if the storage backend fails to retrieve the releases. func (s *Storage) ListDeleted() ([]*rspb.Release, error) { - log.Println("List deleted releases in storage") + s.Log("listing deleted releases in storage") return s.Driver.List(func(rls *rspb.Release) bool { return relutil.StatusFilter(rspb.Status_DELETED).Check(rls) }) @@ -87,7 +88,7 @@ func (s *Storage) ListDeleted() ([]*rspb.Release, error) { // ListDeployed returns all releases with Status == DEPLOYED. An error is returned // if the storage backend fails to retrieve the releases. func (s *Storage) ListDeployed() ([]*rspb.Release, error) { - log.Println("Listing all deployed releases in storage") + s.Log("listing all deployed releases in storage") return s.Driver.List(func(rls *rspb.Release) bool { return relutil.StatusFilter(rspb.Status_DEPLOYED).Check(rls) }) @@ -97,7 +98,7 @@ func (s *Storage) ListDeployed() ([]*rspb.Release, error) { // (filter0 && filter1 && ... && filterN), i.e. a Release is included in the results // if and only if all filters return true. func (s *Storage) ListFilterAll(fns ...relutil.FilterFunc) ([]*rspb.Release, error) { - log.Println("Listing all releases with filter") + s.Log("listing all releases with filter") return s.Driver.List(func(rls *rspb.Release) bool { return relutil.All(fns...).Check(rls) }) @@ -107,7 +108,7 @@ func (s *Storage) ListFilterAll(fns ...relutil.FilterFunc) ([]*rspb.Release, err // (filter0 || filter1 || ... || filterN), i.e. a Release is included in the results // if at least one of the filters returns true. func (s *Storage) ListFilterAny(fns ...relutil.FilterFunc) ([]*rspb.Release, error) { - log.Println("Listing any releases with filter") + s.Log("listing any releases with filter") return s.Driver.List(func(rls *rspb.Release) bool { return relutil.Any(fns...).Check(rls) }) @@ -116,7 +117,7 @@ func (s *Storage) ListFilterAny(fns ...relutil.FilterFunc) ([]*rspb.Release, err // Deployed returns the deployed release with the provided release name, or // returns ErrReleaseNotFound if not found. func (s *Storage) Deployed(name string) (*rspb.Release, error) { - log.Printf("Getting deployed release from '%s' history\n", name) + s.Log("getting deployed release from %q history", name) ls, err := s.Driver.Query(map[string]string{ "NAME": name, @@ -127,7 +128,7 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) { case err != nil: return nil, err case len(ls) == 0: - return nil, fmt.Errorf("'%s' has no deployed releases", name) + return nil, fmt.Errorf("%q has no deployed releases", name) default: return ls[0], nil } @@ -136,17 +137,14 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) { // History returns the revision history for the release with the provided name, or // returns ErrReleaseNotFound if no such release name exists. func (s *Storage) History(name string) ([]*rspb.Release, error) { - log.Printf("Getting release history for '%s'\n", name) + s.Log("getting release history for %q", name) - l, err := s.Driver.Query(map[string]string{"NAME": name, "OWNER": "TILLER"}) - if err != nil { - return nil, err - } - return l, nil + return s.Driver.Query(map[string]string{"NAME": name, "OWNER": "TILLER"}) } // Last fetches the last revision of the named release. func (s *Storage) Last(name string) (*rspb.Release, error) { + s.Log("getting last revision of %q", name) h, err := s.History(name) if err != nil { return nil, err @@ -161,6 +159,7 @@ func (s *Storage) Last(name string) (*rspb.Release, error) { // LockRelease gains a mutually exclusive access to a release via a mutex. func (s *Storage) LockRelease(name string) error { + s.Log("locking release %s", name) s.releaseLocksLock.Lock() defer s.releaseLocksLock.Unlock() @@ -180,7 +179,7 @@ func (s *Storage) LockRelease(name string) error { } } if !found { - return fmt.Errorf("Unable to lock release %s: release not found", name) + return fmt.Errorf("Unable to lock release %q: release not found", name) } lock = &sync.Mutex{} @@ -193,6 +192,7 @@ func (s *Storage) LockRelease(name string) error { // UnlockRelease releases a mutually exclusive access to a release. // If release doesn't exist or wasn't previously locked - the unlock will pass func (s *Storage) UnlockRelease(name string) { + s.Log("unlocking release %s", name) s.releaseLocksLock.Lock() defer s.releaseLocksLock.Unlock() @@ -222,5 +222,6 @@ func Init(d driver.Driver) *Storage { Driver: d, releaseLocks: make(map[string]*sync.Mutex), releaseLocksLock: &sync.Mutex{}, + Log: func(_ string, _ ...interface{}) {}, } } diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 26516474b..713699aca 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -147,7 +147,7 @@ type KubeClient interface { // // reader must contain a YAML stream (one or more YAML documents separated // by "\n---\n"). - Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error + Update(namespace string, originalReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error Build(namespace string, reader io.Reader) (kube.Result, error) BuildUnstructured(namespace string, reader io.Reader) (kube.Result, error) @@ -190,7 +190,7 @@ func (p *PrintingKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int } // Update implements KubeClient Update. -func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { +func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { _, err := io.Copy(p.Out, modifiedReader) return err } @@ -235,6 +235,5 @@ func New() *Environment { return &Environment{ EngineYard: ey, Releases: storage.Init(driver.NewMemory()), - KubeClient: kube.New(nil), } } diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index 716836438..916ff602f 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -48,7 +48,7 @@ func (k *mockKubeClient) Get(ns string, r io.Reader) (string, error) { func (k *mockKubeClient) Delete(ns string, r io.Reader) error { return nil } -func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { +func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { return nil } func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { diff --git a/pkg/tiller/release_content.go b/pkg/tiller/release_content.go new file mode 100644 index 000000000..13470bf8d --- /dev/null +++ b/pkg/tiller/release_content.go @@ -0,0 +1,37 @@ +/* +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 ( + ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/proto/hapi/services" +) + +// GetReleaseContent gets all of the stored information for the given release. +func (s *ReleaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { + if !ValidName.MatchString(req.Name) { + return nil, errMissingRelease + } + + if req.Version <= 0 { + rel, err := s.env.Releases.Deployed(req.Name) + return &services.GetReleaseContentResponse{Release: rel}, err + } + + rel, err := s.env.Releases.Get(req.Name, req.Version) + return &services.GetReleaseContentResponse{Release: rel}, err +} diff --git a/pkg/tiller/release_content_test.go b/pkg/tiller/release_content_test.go new file mode 100644 index 000000000..9a81f1e3f --- /dev/null +++ b/pkg/tiller/release_content_test.go @@ -0,0 +1,41 @@ +/* +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 ( + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/services" + "testing" +) + +func TestGetReleaseContent(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name, Version: 1}) + if err != nil { + t.Errorf("Error getting release content: %s", err) + } + + if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name { + t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name) + } +} diff --git a/pkg/tiller/release_history.go b/pkg/tiller/release_history.go index 63f4275e9..09131fad8 100644 --- a/pkg/tiller/release_history.go +++ b/pkg/tiller/release_history.go @@ -25,6 +25,7 @@ import ( // GetHistory gets the history for a given release. func (s *ReleaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryRequest) (*tpb.GetHistoryResponse, error) { + s.Log("getting history for release %s", req.Name) h, err := s.env.Releases.History(req.Name) if err != nil { return nil, err diff --git a/pkg/tiller/release_install.go b/pkg/tiller/release_install.go new file mode 100644 index 000000000..79b7b6354 --- /dev/null +++ b/pkg/tiller/release_install.go @@ -0,0 +1,222 @@ +/* +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 ( + "fmt" + "strings" + + ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/timeconv" +) + +// InstallRelease installs a release and stores the release record. +func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { + s.Log("preparing install for %s", req.Name) + rel, err := s.prepareRelease(req) + if err != nil { + s.Log("failed install prepare step: %s", err) + res := &services.InstallReleaseResponse{Release: rel} + + // On dry run, append the manifest contents to a failed release. This is + // a stop-gap until we can revisit an error backchannel post-2.0. + if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { + err = fmt.Errorf("%s\n%s", err, rel.Manifest) + } + return res, err + } + + s.Log("performing install for %s", req.Name) + res, err := s.performRelease(rel, req) + if err != nil { + s.Log("failed install perform step: %s", err) + } + return res, err +} + +// prepareRelease builds a release for an install operation. +func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { + if req.Chart == nil { + return nil, errMissingChart + } + + name, err := s.uniqName(req.Name, req.ReuseName) + if err != nil { + return nil, err + } + + caps, err := capabilities(s.clientset.Discovery()) + if err != nil { + return nil, err + } + + revision := 1 + ts := timeconv.Now() + options := chartutil.ReleaseOptions{ + Name: name, + Time: ts, + Namespace: req.Namespace, + Revision: revision, + IsInstall: true, + } + valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) + if err != nil { + return nil, err + } + + hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) + if err != nil { + // Return a release with partial data so that client can show debugging + // information. + rel := &release.Release{ + Name: name, + Namespace: req.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: ts, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_UNKNOWN}, + Description: fmt.Sprintf("Install failed: %s", err), + }, + Version: 0, + } + if manifestDoc != nil { + rel.Manifest = manifestDoc.String() + } + return rel, err + } + + // Store a release. + rel := &release.Release{ + Name: name, + Namespace: req.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: ts, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_UNKNOWN}, + Description: "Initial install underway", // Will be overwritten. + }, + Manifest: manifestDoc.String(), + Hooks: hooks, + Version: int32(revision), + } + if len(notesTxt) > 0 { + rel.Info.Status.Notes = notesTxt + } + + err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) + return rel, err +} + +// performRelease runs a release. +func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { + res := &services.InstallReleaseResponse{Release: r} + + if req.DryRun { + s.Log("dry run for %s", r.Name) + res.Release.Info.Description = "Dry run complete" + return res, nil + } + + // pre-install hooks + if !req.DisableHooks { + if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("install hooks disabled for %s", req.Name) + } + + switch h, err := s.env.Releases.History(req.Name); { + // if this is a replace operation, append to the release history + case req.ReuseName && err == nil && len(h) >= 1: + s.Log("name reuse for %s requested, replacing release", req.Name) + // get latest release revision + relutil.Reverse(h, relutil.SortByRevision) + + // old release + old := h[0] + + // update old release status + old.Info.Status.Code = release.Status_SUPERSEDED + s.recordRelease(old, true) + + // update new release with next revision number + // so as to append to the old release's history + r.Version = old.Version + 1 + updateReq := &services.UpdateReleaseRequest{ + Wait: req.Wait, + Recreate: false, + Timeout: req.Timeout, + } + if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil { + msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err) + s.Log("warning: %s", msg) + old.Info.Status.Code = release.Status_SUPERSEDED + r.Info.Status.Code = release.Status_FAILED + r.Info.Description = msg + s.recordRelease(old, true) + s.recordRelease(r, false) + return res, err + } + + default: + // nothing to replace, create as normal + // regular manifests + if err := s.ReleaseModule.Create(r, req, s.env); err != nil { + msg := fmt.Sprintf("Release %q failed: %s", r.Name, err) + s.Log("warning: %s", msg) + r.Info.Status.Code = release.Status_FAILED + r.Info.Description = msg + s.recordRelease(r, false) + return res, fmt.Errorf("release %s failed: %s", r.Name, err) + } + } + + // post-install hooks + if !req.DisableHooks { + if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { + msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err) + s.Log("warning: %s", msg) + r.Info.Status.Code = release.Status_FAILED + r.Info.Description = msg + s.recordRelease(r, false) + return res, err + } + } + + r.Info.Status.Code = release.Status_DEPLOYED + r.Info.Description = "Install complete" + // This is a tricky case. The release has been created, but the result + // cannot be recorded. The truest thing to tell the user is that the + // release was created. However, the user will not be able to do anything + // further with this release. + // + // One possible strategy would be to do a timed retry to see if we can get + // this stored in the future. + s.recordRelease(r, false) + + return res, nil +} diff --git a/pkg/tiller/release_install_test.go b/pkg/tiller/release_install_test.go new file mode 100644 index 000000000..481dee01b --- /dev/null +++ b/pkg/tiller/release_install_test.go @@ -0,0 +1,454 @@ +/* +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 ( + "fmt" + "strings" + "testing" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/version" +) + +func TestInstallRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + if res.Release.Namespace != "spaced" { + t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(rel.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) + } + if rel.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) + } + + if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { + t.Errorf("Expected event 0 is post install") + } + if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { + t.Errorf("Expected event 0 is pre-delete") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_WithNotes(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText)}, + }, + }, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + if res.Release.Namespace != "spaced" { + t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(rel.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) + } + if rel.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) + } + + if rel.Info.Status.Notes != notesText { + t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes) + } + + if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { + t.Errorf("Expected event 0 is post install") + } + if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { + t.Errorf("Expected event 0 is pre-delete") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_WithNotesRendered(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText + " {{.Release.Name}}")}, + }, + }, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + if res.Release.Namespace != "spaced" { + t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(rel.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) + } + if rel.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) + } + + expectedNotes := fmt.Sprintf("%s %s", notesText, res.Release.Name) + if rel.Info.Status.Notes != expectedNotes { + t.Fatalf("Expected '%s', got '%s'", expectedNotes, rel.Info.Status.Notes) + } + + if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { + t.Errorf("Expected event 0 is post install") + } + if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { + t.Errorf("Expected event 0 is pre-delete") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_TillerVersion(t *testing.T) { + version.Version = "2.2.0" + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello", TillerVersion: ">=2.2.0"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Expected valid range. Got %q", err) + } +} + +func TestInstallRelease_WrongTillerVersion(t *testing.T) { + version.Version = "2.2.0" + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello", TillerVersion: "<2.0.0"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err == nil { + t.Fatalf("Expected to fail because of wrong version") + } + + expect := "Chart incompatible with Tiller" + if !strings.Contains(err.Error(), expect) { + t.Errorf("Expected %q to contain %q", err.Error(), expect) + } +} + +func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText)}, + }, + Dependencies: []*chart.Chart{ + { + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText + " child")}, + }, + }, + }, + }, + } + + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if rel.Info.Status.Notes != notesText { + t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_DryRun(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + DryRun: true, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Errorf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if !strings.Contains(res.Release.Manifest, "hello: Earth") { + t.Errorf("Should contain partial content. %s", res.Release.Manifest) + } + + if strings.Contains(res.Release.Manifest, "hello: {{ template \"_planet\" . }}") { + t.Errorf("Should not contain partial templates itself. %s", res.Release.Manifest) + } + + if strings.Contains(res.Release.Manifest, "empty") { + t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) + } + + if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil { + t.Errorf("Expected no stored release.") + } + + if l := len(res.Release.Hooks); l != 1 { + t.Fatalf("Expected 1 hook, got %d", l) + } + + if res.Release.Hooks[0].LastRun != nil { + t.Error("Expected hook to not be marked as run.") + } + + if res.Release.Info.Description != "Dry run complete" { + t.Errorf("unexpected description: %s", res.Release.Info.Description) + } +} + +func TestInstallRelease_NoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + DisableHooks: true, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Errorf("Failed install: %s", err) + } + + if hl := res.Release.Hooks[0].LastRun; hl != nil { + t.Errorf("Expected that no hooks were run. Got %d", hl) + } +} + +func TestInstallRelease_FailedHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + rs.env.KubeClient = newHookFailingKubeClient() + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + } + res, err := rs.InstallRelease(c, req) + if err == nil { + t.Error("Expected failed install") + } + + if hl := res.Release.Info.Status.Code; hl != release.Status_FAILED { + t.Errorf("Expected FAILED release. Got %d", hl) + } +} + +func TestInstallRelease_ReuseName(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rel.Info.Status.Code = release.Status_DELETED + rs.env.Releases.Create(rel) + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + ReuseName: true, + Name: rel.Name, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + + if res.Release.Name != rel.Name { + t.Errorf("expected %q, got %q", rel.Name, res.Release.Name) + } + + getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 0} + getres, err := rs.GetReleaseStatus(c, getreq) + if err != nil { + t.Errorf("Failed to retrieve release: %s", err) + } + if getres.Info.Status.Code != release.Status_DEPLOYED { + t.Errorf("Release status is %q", getres.Info.Status.Code) + } +} diff --git a/pkg/tiller/release_list.go b/pkg/tiller/release_list.go new file mode 100644 index 000000000..14d5c0d38 --- /dev/null +++ b/pkg/tiller/release_list.go @@ -0,0 +1,141 @@ +/* +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 ( + "fmt" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "regexp" +) + +// ListReleases lists the releases found by the server. +func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { + if len(req.StatusCodes) == 0 { + req.StatusCodes = []release.Status_Code{release.Status_DEPLOYED} + } + + //rels, err := s.env.Releases.ListDeployed() + rels, err := s.env.Releases.ListFilterAll(func(r *release.Release) bool { + for _, sc := range req.StatusCodes { + if sc == r.Info.Status.Code { + return true + } + } + return false + }) + if err != nil { + return err + } + + if req.Namespace != "" { + rels, err = filterByNamespace(req.Namespace, rels) + if err != nil { + return err + } + } + + if len(req.Filter) != 0 { + rels, err = filterReleases(req.Filter, rels) + if err != nil { + return err + } + } + + total := int64(len(rels)) + + switch req.SortBy { + case services.ListSort_NAME: + relutil.SortByName(rels) + case services.ListSort_LAST_RELEASED: + relutil.SortByDate(rels) + } + + if req.SortOrder == services.ListSort_DESC { + ll := len(rels) + rr := make([]*release.Release, ll) + for i, item := range rels { + rr[ll-i-1] = item + } + rels = rr + } + + l := int64(len(rels)) + if req.Offset != "" { + + i := -1 + for ii, cur := range rels { + if cur.Name == req.Offset { + i = ii + } + } + if i == -1 { + return fmt.Errorf("offset %q not found", req.Offset) + } + + if len(rels) < i { + return fmt.Errorf("no items after %q", req.Offset) + } + + rels = rels[i:] + l = int64(len(rels)) + } + + if req.Limit == 0 { + req.Limit = ListDefaultLimit + } + + next := "" + if l > req.Limit { + next = rels[req.Limit].Name + rels = rels[0:req.Limit] + l = int64(len(rels)) + } + + res := &services.ListReleasesResponse{ + Next: next, + Count: l, + Total: total, + Releases: rels, + } + return stream.Send(res) +} + +func filterByNamespace(namespace string, rels []*release.Release) ([]*release.Release, error) { + matches := []*release.Release{} + for _, r := range rels { + if namespace == r.Namespace { + matches = append(matches, r) + } + } + return matches, nil +} + +func filterReleases(filter string, rels []*release.Release) ([]*release.Release, error) { + preg, err := regexp.Compile(filter) + if err != nil { + return rels, err + } + matches := []*release.Release{} + for _, r := range rels { + if preg.MatchString(r.Name) { + matches = append(matches, r) + } + } + return matches, nil +} diff --git a/pkg/tiller/release_list_test.go b/pkg/tiller/release_list_test.go new file mode 100644 index 000000000..64877422a --- /dev/null +++ b/pkg/tiller/release_list_test.go @@ -0,0 +1,233 @@ +/* +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 ( + "fmt" + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestListReleases(t *testing.T) { + rs := rsFixture() + num := 7 + for i := 0; i < num; i++ { + rel := releaseStub() + rel.Name = fmt.Sprintf("rel-%d", i) + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + mrs := &mockListServer{} + if err := rs.ListReleases(&services.ListReleasesRequest{Offset: "", Limit: 64}, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != num { + t.Errorf("Expected %d releases, got %d", num, len(mrs.val.Releases)) + } +} + +func TestListReleasesByStatus(t *testing.T) { + rs := rsFixture() + stubs := []*release.Release{ + namedReleaseStub("kamal", release.Status_DEPLOYED), + namedReleaseStub("astrolabe", release.Status_DELETED), + namedReleaseStub("octant", release.Status_FAILED), + namedReleaseStub("sextant", release.Status_UNKNOWN), + } + for _, stub := range stubs { + if err := rs.env.Releases.Create(stub); err != nil { + t.Fatalf("Could not create stub: %s", err) + } + } + + tests := []struct { + statusCodes []release.Status_Code + names []string + }{ + { + names: []string{"kamal"}, + statusCodes: []release.Status_Code{release.Status_DEPLOYED}, + }, + { + names: []string{"astrolabe"}, + statusCodes: []release.Status_Code{release.Status_DELETED}, + }, + { + names: []string{"kamal", "octant"}, + statusCodes: []release.Status_Code{release.Status_DEPLOYED, release.Status_FAILED}, + }, + { + names: []string{"kamal", "astrolabe", "octant", "sextant"}, + statusCodes: []release.Status_Code{ + release.Status_DEPLOYED, + release.Status_DELETED, + release.Status_FAILED, + release.Status_UNKNOWN, + }, + }, + } + + for i, tt := range tests { + mrs := &mockListServer{} + if err := rs.ListReleases(&services.ListReleasesRequest{StatusCodes: tt.statusCodes, Offset: "", Limit: 64}, mrs); err != nil { + t.Fatalf("Failed listing %d: %s", i, err) + } + + if len(tt.names) != len(mrs.val.Releases) { + t.Fatalf("Expected %d releases, got %d", len(tt.names), len(mrs.val.Releases)) + } + + for _, name := range tt.names { + found := false + for _, rel := range mrs.val.Releases { + if rel.Name == name { + found = true + } + } + if !found { + t.Errorf("%d: Did not find name %q", i, name) + } + } + } +} + +func TestListReleasesSort(t *testing.T) { + rs := rsFixture() + + // Put them in by reverse order so that the mock doesn't "accidentally" + // sort. + num := 7 + for i := num; i > 0; i-- { + rel := releaseStub() + rel.Name = fmt.Sprintf("rel-%d", i) + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + limit := 6 + mrs := &mockListServer{} + req := &services.ListReleasesRequest{ + Offset: "", + Limit: int64(limit), + SortBy: services.ListSort_NAME, + } + if err := rs.ListReleases(req, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != limit { + t.Errorf("Expected %d releases, got %d", limit, len(mrs.val.Releases)) + } + + for i := 0; i < limit; i++ { + n := fmt.Sprintf("rel-%d", i+1) + if mrs.val.Releases[i].Name != n { + t.Errorf("Expected %q, got %q", n, mrs.val.Releases[i].Name) + } + } +} + +func TestListReleasesFilter(t *testing.T) { + rs := rsFixture() + names := []string{ + "axon", + "dendrite", + "neuron", + "neuroglia", + "synapse", + "nucleus", + "organelles", + } + num := 7 + for i := 0; i < num; i++ { + rel := releaseStub() + rel.Name = names[i] + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + mrs := &mockListServer{} + req := &services.ListReleasesRequest{ + Offset: "", + Limit: 64, + Filter: "neuro[a-z]+", + SortBy: services.ListSort_NAME, + } + if err := rs.ListReleases(req, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != 2 { + t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases)) + } + + if mrs.val.Releases[0].Name != "neuroglia" { + t.Errorf("Unexpected sort order: %v.", mrs.val.Releases) + } + if mrs.val.Releases[1].Name != "neuron" { + t.Errorf("Unexpected sort order: %v.", mrs.val.Releases) + } +} + +func TestReleasesNamespace(t *testing.T) { + rs := rsFixture() + + names := []string{ + "axon", + "dendrite", + "neuron", + "ribosome", + } + + namespaces := []string{ + "default", + "test123", + "test123", + "cerebellum", + } + num := 4 + for i := 0; i < num; i++ { + rel := releaseStub() + rel.Name = names[i] + rel.Namespace = namespaces[i] + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + mrs := &mockListServer{} + req := &services.ListReleasesRequest{ + Offset: "", + Limit: 64, + Namespace: "test123", + } + + if err := rs.ListReleases(req, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != 2 { + t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases)) + } +} diff --git a/pkg/tiller/release_modules.go b/pkg/tiller/release_modules.go index e13b26de9..2f2a3c6ff 100644 --- a/pkg/tiller/release_modules.go +++ b/pkg/tiller/release_modules.go @@ -59,14 +59,14 @@ func (m *LocalReleaseModule) Create(r *release.Release, req *services.InstallRel func (m *LocalReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error { c := bytes.NewBufferString(current.Manifest) t := bytes.NewBufferString(target.Manifest) - return env.KubeClient.Update(target.Namespace, c, t, req.Recreate, req.Timeout, req.Wait) + return env.KubeClient.Update(target.Namespace, c, t, req.Force, req.Recreate, req.Timeout, req.Wait) } // Rollback performs a rollback from current to target release func (m *LocalReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error { c := bytes.NewBufferString(current.Manifest) t := bytes.NewBufferString(target.Manifest) - return env.KubeClient.Update(target.Namespace, c, t, req.Recreate, req.Timeout, req.Wait) + return env.KubeClient.Update(target.Namespace, c, t, req.Force, req.Recreate, req.Timeout, req.Wait) } // Status returns kubectl-like formatted status of release objects @@ -101,6 +101,7 @@ func (m *RemoteReleaseModule) Update(current, target *release.Release, req *serv Recreate: req.Recreate, Timeout: req.Timeout, Wait: req.Wait, + Force: req.Force, } _, err := rudder.UpgradeRelease(upgrade) return err diff --git a/pkg/tiller/release_rollback.go b/pkg/tiller/release_rollback.go new file mode 100644 index 000000000..43e06a6b6 --- /dev/null +++ b/pkg/tiller/release_rollback.go @@ -0,0 +1,152 @@ +/* +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 ( + "fmt" + + ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/timeconv" +) + +// RollbackRelease rolls back to a previous version of the given release. +func (s *ReleaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { + err := s.env.Releases.LockRelease(req.Name) + if err != nil { + return nil, err + } + defer s.env.Releases.UnlockRelease(req.Name) + + s.Log("preparing rollback of %s", req.Name) + currentRelease, targetRelease, err := s.prepareRollback(req) + if err != nil { + return nil, err + } + + s.Log("performing rollback of %s", req.Name) + res, err := s.performRollback(currentRelease, targetRelease, req) + if err != nil { + return res, err + } + + if !req.DryRun { + s.Log("creating rolled back release %s", req.Name) + if err := s.env.Releases.Create(targetRelease); err != nil { + return res, err + } + } + + return res, nil +} + +// prepareRollback finds the previous release and prepares a new release object with +// the previous release's configuration +func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*release.Release, *release.Release, error) { + switch { + case !ValidName.MatchString(req.Name): + return nil, nil, errMissingRelease + case req.Version < 0: + return nil, nil, errInvalidRevision + } + + crls, err := s.env.Releases.Last(req.Name) + if err != nil { + return nil, nil, err + } + + rbv := req.Version + if req.Version == 0 { + rbv = crls.Version - 1 + } + + s.Log("rolling back %s (current: v%d, target: v%d)", req.Name, crls.Version, rbv) + + prls, err := s.env.Releases.Get(req.Name, rbv) + if err != nil { + return nil, nil, err + } + + // Store a new release object with previous release's configuration + target := &release.Release{ + Name: req.Name, + Namespace: crls.Namespace, + Chart: prls.Chart, + Config: prls.Config, + Info: &release.Info{ + FirstDeployed: crls.Info.FirstDeployed, + LastDeployed: timeconv.Now(), + Status: &release.Status{ + Code: release.Status_UNKNOWN, + Notes: prls.Info.Status.Notes, + }, + // Because we lose the reference to rbv elsewhere, we set the + // message here, and only override it later if we experience failure. + Description: fmt.Sprintf("Rollback to %d", rbv), + }, + Version: crls.Version + 1, + Manifest: prls.Manifest, + Hooks: prls.Hooks, + } + + return crls, target, nil +} + +func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { + res := &services.RollbackReleaseResponse{Release: targetRelease} + + if req.DryRun { + s.Log("dry run for %s", targetRelease.Name) + return res, nil + } + + // pre-rollback hooks + if !req.DisableHooks { + if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PreRollback, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("rollback hooks disabled for %s", req.Name) + } + + if err := s.ReleaseModule.Rollback(currentRelease, targetRelease, req, s.env); err != nil { + msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) + s.Log("warning: %s", msg) + currentRelease.Info.Status.Code = release.Status_SUPERSEDED + targetRelease.Info.Status.Code = release.Status_FAILED + targetRelease.Info.Description = msg + s.recordRelease(currentRelease, true) + s.recordRelease(targetRelease, false) + return res, err + } + + // post-rollback hooks + if !req.DisableHooks { + if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PostRollback, req.Timeout); err != nil { + return res, err + } + } + + currentRelease.Info.Status.Code = release.Status_SUPERSEDED + s.recordRelease(currentRelease, true) + + targetRelease.Info.Status.Code = release.Status_DEPLOYED + + return res, nil +} diff --git a/pkg/tiller/release_rollback_test.go b/pkg/tiller/release_rollback_test.go new file mode 100644 index 000000000..11ba9d7e2 --- /dev/null +++ b/pkg/tiller/release_rollback_test.go @@ -0,0 +1,226 @@ +/* +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 ( + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "strings" + "testing" +) + +func TestRollbackRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + upgradedRel.Hooks = []*release.Hook{ + { + Name: "test-cm", + Kind: "ConfigMap", + Path: "test-cm", + Manifest: manifestWithRollbackHooks, + Events: []release.Hook_Event{ + release.Hook_PRE_ROLLBACK, + release.Hook_POST_ROLLBACK, + }, + }, + } + + upgradedRel.Manifest = "hello world" + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + } + res, err := rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if res.Release.Name != rel.Name { + t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) + } + + if res.Release.Namespace != rel.Namespace { + t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) + } + + if res.Release.Version != 3 { + t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) + } + + updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if len(updated.Hooks) != 2 { + t.Fatalf("Expected 2 hooks, got %d", len(updated.Hooks)) + } + + if updated.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) + } + + anotherUpgradedRelease := upgradeReleaseVersion(upgradedRel) + rs.env.Releases.Update(upgradedRel) + rs.env.Releases.Create(anotherUpgradedRelease) + + res, err = rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + + updated, err = rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if len(updated.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) + } + + if updated.Hooks[0].Manifest != manifestWithRollbackHooks { + t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) + } + + if res.Release.Version != 4 { + t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) + } + + if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK { + t.Errorf("Expected event 0 to be pre rollback") + } + + if updated.Hooks[0].Events[1] != release.Hook_POST_ROLLBACK { + t.Errorf("Expected event 1 to be post rollback") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(updated.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(updated.Manifest, "hello world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if res.Release.Info.Description != "Rollback to 2" { + t.Errorf("Expected rollback to 2, got %q", res.Release.Info.Description) + } +} + +func TestRollbackWithReleaseVersion(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Version: 1, + } + + _, err := rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } +} + +func TestRollbackReleaseNoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rel.Hooks = []*release.Hook{ + { + Name: "test-cm", + Kind: "ConfigMap", + Path: "test-cm", + Manifest: manifestWithRollbackHooks, + Events: []release.Hook_Event{ + release.Hook_PRE_ROLLBACK, + release.Hook_POST_ROLLBACK, + }, + }, + } + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + } + + res, err := rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + + if hl := res.Release.Hooks[0].LastRun; hl != nil { + t.Errorf("Expected that no hooks were run. Got %d", hl) + } +} + +func TestRollbackReleaseFailure(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + } + + rs.env.KubeClient = newUpdateFailingKubeClient() + res, err := rs.RollbackRelease(c, req) + if err == nil { + t.Error("Expected failed rollback") + } + + if targetStatus := res.Release.Info.Status.Code; targetStatus != release.Status_FAILED { + t.Errorf("Expected FAILED release. Got %v", targetStatus) + } + + oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) + if err != nil { + t.Errorf("Expected to be able to get previous release") + } + if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { + t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) + } +} diff --git a/pkg/tiller/release_server.go b/pkg/tiller/release_server.go index adc4ddf3c..07ea872df 100644 --- a/pkg/tiller/release_server.go +++ b/pkg/tiller/release_server.go @@ -20,23 +20,19 @@ import ( "bytes" "errors" "fmt" - "log" "path" "regexp" "strings" "github.com/technosophos/moniker" - ctx "golang.org/x/net/context" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/discovery" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/services" - reltesting "k8s.io/helm/pkg/releasetesting" relutil "k8s.io/helm/pkg/releaseutil" "k8s.io/helm/pkg/tiller/environment" "k8s.io/helm/pkg/timeconv" @@ -84,6 +80,7 @@ type ReleaseServer struct { ReleaseModule env *environment.Environment clientset internalclientset.Interface + Log func(string, ...interface{}) } // NewReleaseServer creates a new release server. @@ -101,264 +98,10 @@ func NewReleaseServer(env *environment.Environment, clientset internalclientset. env: env, clientset: clientset, ReleaseModule: releaseModule, + Log: func(_ string, _ ...interface{}) {}, } } -// ListReleases lists the releases found by the server. -func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { - if len(req.StatusCodes) == 0 { - req.StatusCodes = []release.Status_Code{release.Status_DEPLOYED} - } - - //rels, err := s.env.Releases.ListDeployed() - rels, err := s.env.Releases.ListFilterAll(func(r *release.Release) bool { - for _, sc := range req.StatusCodes { - if sc == r.Info.Status.Code { - return true - } - } - return false - }) - if err != nil { - return err - } - - if req.Namespace != "" { - rels, err = filterByNamespace(req.Namespace, rels) - if err != nil { - return err - } - } - - if len(req.Filter) != 0 { - rels, err = filterReleases(req.Filter, rels) - if err != nil { - return err - } - } - - total := int64(len(rels)) - - switch req.SortBy { - case services.ListSort_NAME: - relutil.SortByName(rels) - case services.ListSort_LAST_RELEASED: - relutil.SortByDate(rels) - } - - if req.SortOrder == services.ListSort_DESC { - ll := len(rels) - rr := make([]*release.Release, ll) - for i, item := range rels { - rr[ll-i-1] = item - } - rels = rr - } - - l := int64(len(rels)) - if req.Offset != "" { - - i := -1 - for ii, cur := range rels { - if cur.Name == req.Offset { - i = ii - } - } - if i == -1 { - return fmt.Errorf("offset %q not found", req.Offset) - } - - if len(rels) < i { - return fmt.Errorf("no items after %q", req.Offset) - } - - rels = rels[i:] - l = int64(len(rels)) - } - - if req.Limit == 0 { - req.Limit = ListDefaultLimit - } - - next := "" - if l > req.Limit { - next = rels[req.Limit].Name - rels = rels[0:req.Limit] - l = int64(len(rels)) - } - - res := &services.ListReleasesResponse{ - Next: next, - Count: l, - Total: total, - Releases: rels, - } - return stream.Send(res) -} - -func filterByNamespace(namespace string, rels []*release.Release) ([]*release.Release, error) { - matches := []*release.Release{} - for _, r := range rels { - if namespace == r.Namespace { - matches = append(matches, r) - } - } - return matches, nil -} - -func filterReleases(filter string, rels []*release.Release) ([]*release.Release, error) { - preg, err := regexp.Compile(filter) - if err != nil { - return rels, err - } - matches := []*release.Release{} - for _, r := range rels { - if preg.MatchString(r.Name) { - matches = append(matches, r) - } - } - return matches, nil -} - -// GetVersion sends the server version. -func (s *ReleaseServer) GetVersion(c ctx.Context, req *services.GetVersionRequest) (*services.GetVersionResponse, error) { - v := version.GetVersionProto() - return &services.GetVersionResponse{Version: v}, nil -} - -// GetReleaseStatus gets the status information for a named release. -func (s *ReleaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { - if !ValidName.MatchString(req.Name) { - return nil, errMissingRelease - } - - var rel *release.Release - - if req.Version <= 0 { - var err error - rel, err = s.env.Releases.Last(req.Name) - if err != nil { - return nil, fmt.Errorf("getting deployed release %q: %s", req.Name, err) - } - } else { - var err error - if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil { - return nil, fmt.Errorf("getting release '%s' (v%d): %s", req.Name, req.Version, err) - } - } - - if rel.Info == nil { - return nil, errors.New("release info is missing") - } - if rel.Chart == nil { - return nil, errors.New("release chart is missing") - } - - sc := rel.Info.Status.Code - statusResp := &services.GetReleaseStatusResponse{ - Name: rel.Name, - Namespace: rel.Namespace, - Info: rel.Info, - } - - // Ok, we got the status of the release as we had jotted down, now we need to match the - // manifest we stashed away with reality from the cluster. - resp, err := s.ReleaseModule.Status(rel, req, s.env) - if sc == release.Status_DELETED || sc == release.Status_FAILED { - // Skip errors if this is already deleted or failed. - return statusResp, nil - } else if err != nil { - log.Printf("warning: Get for %s failed: %v", rel.Name, err) - return nil, err - } - rel.Info.Status.Resources = resp - return statusResp, nil -} - -// GetReleaseContent gets all of the stored information for the given release. -func (s *ReleaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { - if !ValidName.MatchString(req.Name) { - return nil, errMissingRelease - } - - if req.Version <= 0 { - rel, err := s.env.Releases.Deployed(req.Name) - return &services.GetReleaseContentResponse{Release: rel}, err - } - - rel, err := s.env.Releases.Get(req.Name, req.Version) - return &services.GetReleaseContentResponse{Release: rel}, err -} - -// UpdateRelease takes an existing release and new information, and upgrades the release. -func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { - err := s.env.Releases.LockRelease(req.Name) - if err != nil { - return nil, err - } - defer s.env.Releases.UnlockRelease(req.Name) - - currentRelease, updatedRelease, err := s.prepareUpdate(req) - if err != nil { - return nil, err - } - - res, err := s.performUpdate(currentRelease, updatedRelease, req) - if err != nil { - return res, err - } - - if !req.DryRun { - if err := s.env.Releases.Create(updatedRelease); err != nil { - return res, err - } - } - - return res, nil -} - -func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { - res := &services.UpdateReleaseResponse{Release: updatedRelease} - - if req.DryRun { - log.Printf("Dry run for %s", updatedRelease.Name) - res.Release.Info.Description = "Dry run complete" - return res, nil - } - - // pre-upgrade hooks - if !req.DisableHooks { - if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { - return res, err - } - } - if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil { - msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err) - log.Printf("warning: %s", msg) - originalRelease.Info.Status.Code = release.Status_SUPERSEDED - updatedRelease.Info.Status.Code = release.Status_FAILED - updatedRelease.Info.Description = msg - s.recordRelease(originalRelease, true) - s.recordRelease(updatedRelease, false) - return res, err - } - - // post-upgrade hooks - if !req.DisableHooks { - if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { - return res, err - } - } - - originalRelease.Info.Status.Code = release.Status_SUPERSEDED - s.recordRelease(originalRelease, true) - - updatedRelease.Info.Status.Code = release.Status_DEPLOYED - updatedRelease.Info.Description = "Upgrade complete" - - return res, nil -} - // reuseValues copies values from the current release to a new release if the // new release does not have any values. // @@ -370,19 +113,19 @@ func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.R 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.") + s.Log("resetting values to the chart's original version") return nil } // 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") + s.Log("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) + s.Log("%s", err) return err } nv, err := oldVals.YAML() @@ -399,204 +142,12 @@ func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current current.Config != nil && current.Config.Raw != "" && current.Config.Raw != "{}\n" { - log.Printf("Copying values from %s (v%d) to new release.", current.Name, current.Version) + s.Log("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. -func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { - if !ValidName.MatchString(req.Name) { - return nil, nil, errMissingRelease - } - - if req.Chart == nil { - return nil, nil, errMissingChart - } - - // finds the non-deleted release with the given name - currentRelease, err := s.env.Releases.Last(req.Name) - if err != nil { - return nil, nil, err - } - - // If new values were not supplied in the upgrade, re-use the existing values. - 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. - revision := currentRelease.Version + 1 - - ts := timeconv.Now() - options := chartutil.ReleaseOptions{ - Name: req.Name, - Time: ts, - Namespace: currentRelease.Namespace, - IsUpgrade: true, - Revision: int(revision), - } - - caps, err := capabilities(s.clientset.Discovery()) - if err != nil { - return nil, nil, err - } - valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) - if err != nil { - return nil, nil, err - } - - hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) - if err != nil { - return nil, nil, err - } - - // Store an updated release. - updatedRelease := &release.Release{ - Name: req.Name, - Namespace: currentRelease.Namespace, - Chart: req.Chart, - Config: req.Values, - Info: &release.Info{ - FirstDeployed: currentRelease.Info.FirstDeployed, - LastDeployed: ts, - Status: &release.Status{Code: release.Status_UNKNOWN}, - Description: "Preparing upgrade", // This should be overwritten later. - }, - Version: revision, - Manifest: manifestDoc.String(), - Hooks: hooks, - } - - if len(notesTxt) > 0 { - updatedRelease.Info.Status.Notes = notesTxt - } - err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) - return currentRelease, updatedRelease, err -} - -// RollbackRelease rolls back to a previous version of the given release. -func (s *ReleaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { - err := s.env.Releases.LockRelease(req.Name) - if err != nil { - return nil, err - } - defer s.env.Releases.UnlockRelease(req.Name) - - currentRelease, targetRelease, err := s.prepareRollback(req) - if err != nil { - return nil, err - } - - res, err := s.performRollback(currentRelease, targetRelease, req) - if err != nil { - return res, err - } - - if !req.DryRun { - if err := s.env.Releases.Create(targetRelease); err != nil { - return res, err - } - } - - return res, nil -} - -func (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { - res := &services.RollbackReleaseResponse{Release: targetRelease} - - if req.DryRun { - log.Printf("Dry run for %s", targetRelease.Name) - return res, nil - } - - // pre-rollback hooks - if !req.DisableHooks { - if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PreRollback, req.Timeout); err != nil { - return res, err - } - } - - if err := s.ReleaseModule.Rollback(currentRelease, targetRelease, req, s.env); err != nil { - msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) - log.Printf("warning: %s", msg) - currentRelease.Info.Status.Code = release.Status_SUPERSEDED - targetRelease.Info.Status.Code = release.Status_FAILED - targetRelease.Info.Description = msg - s.recordRelease(currentRelease, true) - s.recordRelease(targetRelease, false) - return res, err - } - - // post-rollback hooks - if !req.DisableHooks { - if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PostRollback, req.Timeout); err != nil { - return res, err - } - } - - currentRelease.Info.Status.Code = release.Status_SUPERSEDED - s.recordRelease(currentRelease, true) - - targetRelease.Info.Status.Code = release.Status_DEPLOYED - - return res, nil -} - -// prepareRollback finds the previous release and prepares a new release object with -// the previous release's configuration -func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*release.Release, *release.Release, error) { - switch { - case !ValidName.MatchString(req.Name): - return nil, nil, errMissingRelease - case req.Version < 0: - return nil, nil, errInvalidRevision - } - - crls, err := s.env.Releases.Last(req.Name) - if err != nil { - return nil, nil, err - } - - rbv := req.Version - if req.Version == 0 { - rbv = crls.Version - 1 - } - - log.Printf("rolling back %s (current: v%d, target: v%d)", req.Name, crls.Version, rbv) - - prls, err := s.env.Releases.Get(req.Name, rbv) - if err != nil { - return nil, nil, err - } - - // Store a new release object with previous release's configuration - target := &release.Release{ - Name: req.Name, - Namespace: crls.Namespace, - Chart: prls.Chart, - Config: prls.Config, - Info: &release.Info{ - FirstDeployed: crls.Info.FirstDeployed, - LastDeployed: timeconv.Now(), - Status: &release.Status{ - Code: release.Status_UNKNOWN, - Notes: prls.Info.Status.Notes, - }, - // Because we lose the reference to rbv elsewhere, we set the - // message here, and only override it later if we experience failure. - Description: fmt.Sprintf("Rollback to %d", rbv), - }, - Version: crls.Version + 1, - Manifest: prls.Manifest, - Hooks: prls.Hooks, - } - - return crls, target, nil -} - func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { // If a name is supplied, we check to see if that name is taken. If not, it @@ -617,13 +168,13 @@ func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { if st := rel.Info.Status.Code; reuse && (st == release.Status_DELETED || st == release.Status_FAILED) { // Allowe re-use of names if the previous release is marked deleted. - log.Printf("reusing name %q", start) + s.Log("name %s exists but is not in use, reusing name", start) return start, nil } else if reuse { return "", errors.New("cannot re-use a name that is still in use") } - return "", fmt.Errorf("a release named %q already exists.\nPlease run: helm ls --all %q; helm del --help", start, start) + return "", fmt.Errorf("a release named %s already exists.\nRun: helm ls --all %s; to check the status of the release\nOr run: helm del --purge %s; to delete it", start, start, start) } maxTries := 5 @@ -636,9 +187,9 @@ func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { if _, err := s.env.Releases.Get(name, 1); strings.Contains(err.Error(), "not found") { return name, nil } - log.Printf("info: Name %q is taken. Searching again.", name) + s.Log("info: generated name %s is taken. Searching again.", name) } - log.Printf("warning: No available release names found after %d tries", maxTries) + s.Log("warning: No available release names found after %d tries", maxTries) return "ERROR", errors.New("no available release name found") } @@ -648,34 +199,12 @@ func (s *ReleaseServer) engine(ch *chart.Chart) environment.Engine { if r, ok := s.env.EngineYard.Get(ch.Metadata.Engine); ok { renderer = r } else { - log.Printf("warning: %s requested non-existent template engine %s", ch.Metadata.Name, ch.Metadata.Engine) + s.Log("warning: %s requested non-existent template engine %s", ch.Metadata.Name, ch.Metadata.Engine) } } return renderer } -// InstallRelease installs a release and stores the release record. -func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { - rel, err := s.prepareRelease(req) - if err != nil { - log.Printf("Failed install prepare step: %s", err) - res := &services.InstallReleaseResponse{Release: rel} - - // On dry run, append the manifest contents to a failed release. This is - // a stop-gap until we can revisit an error backchannel post-2.0. - if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { - err = fmt.Errorf("%s\n%s", err, rel.Manifest) - } - return res, err - } - - res, err := s.performRelease(rel, req) - if err != nil { - log.Printf("Failed install perform step: %s", err) - } - return res, err -} - // capabilities builds a Capabilities from discovery information. func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, error) { sv, err := disc.ServerVersion() @@ -693,83 +222,6 @@ func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, e }, nil } -// prepareRelease builds a release for an install operation. -func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { - if req.Chart == nil { - return nil, errMissingChart - } - - name, err := s.uniqName(req.Name, req.ReuseName) - if err != nil { - return nil, err - } - - caps, err := capabilities(s.clientset.Discovery()) - if err != nil { - return nil, err - } - - revision := 1 - ts := timeconv.Now() - options := chartutil.ReleaseOptions{ - Name: name, - Time: ts, - Namespace: req.Namespace, - Revision: revision, - IsInstall: true, - } - valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) - if err != nil { - return nil, err - } - - hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) - if err != nil { - // Return a release with partial data so that client can show debugging - // information. - rel := &release.Release{ - Name: name, - Namespace: req.Namespace, - Chart: req.Chart, - Config: req.Values, - Info: &release.Info{ - FirstDeployed: ts, - LastDeployed: ts, - Status: &release.Status{Code: release.Status_UNKNOWN}, - Description: fmt.Sprintf("Install failed: %s", err), - }, - Version: 0, - } - if manifestDoc != nil { - rel.Manifest = manifestDoc.String() - } - return rel, err - } - - // Store a release. - rel := &release.Release{ - Name: name, - Namespace: req.Namespace, - Chart: req.Chart, - Config: req.Values, - Info: &release.Info{ - FirstDeployed: ts, - LastDeployed: ts, - Status: &release.Status{Code: release.Status_UNKNOWN}, - Description: "Initial install underway", // Will be overwritten. - }, - Manifest: manifestDoc.String(), - Hooks: hooks, - Version: int32(revision), - } - if len(notesTxt) > 0 { - rel.Info.Status.Notes = notesTxt - } - - err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) - return rel, err -} - // GetVersionSet retrieves a set of available k8s API versions func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet, error) { groups, err := client.ServerGroups() @@ -797,6 +249,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values return nil, nil, "", fmt.Errorf("Chart incompatible with Tiller %s", sver) } + s.Log("rendering %s chart using values", ch.GetMetadata().Name) renderer := s.engine(ch) files, err := renderer.Render(ch, values) if err != nil { @@ -854,109 +307,21 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values func (s *ReleaseServer) recordRelease(r *release.Release, reuse bool) { if reuse { if err := s.env.Releases.Update(r); err != nil { - log.Printf("warning: Failed to update release %q: %s", r.Name, err) + s.Log("warning: Failed to update release %s: %s", r.Name, err) } } else if err := s.env.Releases.Create(r); err != nil { - log.Printf("warning: Failed to record release %q: %s", r.Name, err) - } -} - -// performRelease runs a release. -func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { - res := &services.InstallReleaseResponse{Release: r} - - if req.DryRun { - log.Printf("Dry run for %s", r.Name) - res.Release.Info.Description = "Dry run complete" - return res, nil - } - - // pre-install hooks - if !req.DisableHooks { - if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { - return res, err - } - } - - switch h, err := s.env.Releases.History(req.Name); { - // if this is a replace operation, append to the release history - case req.ReuseName && err == nil && len(h) >= 1: - // get latest release revision - relutil.Reverse(h, relutil.SortByRevision) - - // old release - old := h[0] - - // update old release status - old.Info.Status.Code = release.Status_SUPERSEDED - s.recordRelease(old, true) - - // update new release with next revision number - // so as to append to the old release's history - r.Version = old.Version + 1 - updateReq := &services.UpdateReleaseRequest{ - Wait: req.Wait, - Recreate: false, - Timeout: req.Timeout, - } - if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil { - msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err) - log.Printf("warning: %s", msg) - old.Info.Status.Code = release.Status_SUPERSEDED - r.Info.Status.Code = release.Status_FAILED - r.Info.Description = msg - s.recordRelease(old, true) - s.recordRelease(r, false) - return res, err - } - - default: - // nothing to replace, create as normal - // regular manifests - if err := s.ReleaseModule.Create(r, req, s.env); err != nil { - msg := fmt.Sprintf("Release %q failed: %s", r.Name, err) - log.Printf("warning: %s", msg) - r.Info.Status.Code = release.Status_FAILED - r.Info.Description = msg - s.recordRelease(r, false) - return res, fmt.Errorf("release %s failed: %s", r.Name, err) - } - } - - // post-install hooks - if !req.DisableHooks { - if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { - msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err) - log.Printf("warning: %s", msg) - r.Info.Status.Code = release.Status_FAILED - r.Info.Description = msg - s.recordRelease(r, false) - return res, err - } + s.Log("warning: Failed to record release %s: %s", r.Name, err) } - - r.Info.Status.Code = release.Status_DEPLOYED - r.Info.Description = "Install complete" - // This is a tricky case. The release has been created, but the result - // cannot be recorded. The truest thing to tell the user is that the - // release was created. However, the user will not be able to do anything - // further with this release. - // - // One possible strategy would be to do a timed retry to see if we can get - // this stored in the future. - s.recordRelease(r, false) - - return res, nil } func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook string, timeout int64) error { kubeCli := s.env.KubeClient code, ok := events[hook] if !ok { - return fmt.Errorf("unknown hook %q", hook) + return fmt.Errorf("unknown hook %s", hook) } - log.Printf("Executing %s hooks for %s", hook, name) + s.Log("executing %d %s hooks for %s", len(hs), hook, name) executingHooks := []*release.Hook{} for _, h := range hs { for _, e := range h.Events { @@ -972,174 +337,25 @@ func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook strin b := bytes.NewBufferString(h.Manifest) if err := kubeCli.Create(namespace, b, timeout, false); err != nil { - log.Printf("warning: Release %q %s %s failed: %s", name, hook, h.Path, err) + s.Log("warning: Release %s %s %s failed: %s", name, hook, h.Path, err) return err } // No way to rewind a bytes.Buffer()? b.Reset() b.WriteString(h.Manifest) if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil { - log.Printf("warning: Release %q %s %s could not complete: %s", name, hook, h.Path, err) + s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err) return err } h.LastRun = timeconv.Now() } - log.Printf("Hooks complete for %s %s", hook, name) + s.Log("hooks complete for %s %s", hook, name) return nil } -func (s *ReleaseServer) purgeReleases(rels ...*release.Release) error { - for _, rel := range rels { - if _, err := s.env.Releases.Delete(rel.Name, rel.Version); err != nil { - return err - } - } - return nil -} - -// UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED. -func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { - err := s.env.Releases.LockRelease(req.Name) - if err != nil { - return nil, err - } - defer s.env.Releases.UnlockRelease(req.Name) - - if !ValidName.MatchString(req.Name) { - log.Printf("uninstall: Release not found: %s", req.Name) - return nil, errMissingRelease - } - - if len(req.Name) > releaseNameMaxLen { - return nil, fmt.Errorf("release name %q exceeds max length of %d", req.Name, releaseNameMaxLen) - } - - rels, err := s.env.Releases.History(req.Name) - if err != nil { - log.Printf("uninstall: Release not loaded: %s", req.Name) - return nil, err - } - if len(rels) < 1 { - return nil, errMissingRelease - } - - relutil.SortByRevision(rels) - rel := rels[len(rels)-1] - - // TODO: Are there any cases where we want to force a delete even if it's - // already marked deleted? - if rel.Info.Status.Code == release.Status_DELETED { - if req.Purge { - if err := s.purgeReleases(rels...); err != nil { - log.Printf("uninstall: Failed to purge the release: %s", err) - return nil, err - } - return &services.UninstallReleaseResponse{Release: rel}, nil - } - return nil, fmt.Errorf("the release named %q is already deleted", req.Name) - } - - log.Printf("uninstall: Deleting %s", req.Name) - rel.Info.Status.Code = release.Status_DELETING - rel.Info.Deleted = timeconv.Now() - rel.Info.Description = "Deletion in progress (or silently failed)" - res := &services.UninstallReleaseResponse{Release: rel} - - if !req.DisableHooks { - if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PreDelete, req.Timeout); err != nil { - return res, err - } - } - - // From here on out, the release is currently considered to be in Status_DELETING - // state. - if err := s.env.Releases.Update(rel); err != nil { - log.Printf("uninstall: Failed to store updated release: %s", err) - } - - kept, errs := s.ReleaseModule.Delete(rel, req, s.env) - res.Info = kept - - es := make([]string, 0, len(errs)) - for _, e := range errs { - log.Printf("error: %v", e) - es = append(es, e.Error()) - } - - if !req.DisableHooks { - if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PostDelete, req.Timeout); err != nil { - es = append(es, err.Error()) - } - } - - rel.Info.Status.Code = release.Status_DELETED - rel.Info.Description = "Deletion complete" - - if req.Purge { - err := s.purgeReleases(rels...) - if err != nil { - log.Printf("uninstall: Failed to purge the release: %s", err) - } - return res, err - } - - if err := s.env.Releases.Update(rel); err != nil { - log.Printf("uninstall: Failed to store updated release: %s", err) - } - - if len(es) > 0 { - return res, fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) - } - return res, nil -} - func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { r := bytes.NewReader(manifest) _, err := c.BuildUnstructured(ns, r) return err } - -// RunReleaseTest runs pre-defined tests stored as hooks on a given release -func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { - - if !ValidName.MatchString(req.Name) { - return errMissingRelease - } - - // finds the non-deleted release with the given name - rel, err := s.env.Releases.Last(req.Name) - if err != nil { - return err - } - - testEnv := &reltesting.Environment{ - Namespace: rel.Namespace, - KubeClient: s.env.KubeClient, - Timeout: req.Timeout, - Stream: stream, - } - - tSuite, err := reltesting.NewTestSuite(rel) - if err != nil { - log.Printf("Error creating test suite for %s", rel.Name) - return err - } - - if err := tSuite.Run(testEnv); err != nil { - log.Printf("Error running test suite for %s", rel.Name) - return err - } - - rel.Info.Status.LastTestSuiteRun = &release.TestSuite{ - StartedAt: tSuite.StartedAt, - CompletedAt: tSuite.CompletedAt, - Results: tSuite.Results, - } - - if req.Cleanup { - testEnv.DeleteTestPods(tSuite.TestManifests) - } - - return s.env.Releases.Update(rel) -} diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index fd09fa47b..6fd2100b0 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -18,11 +18,9 @@ package tiller import ( "errors" - "fmt" "io" "os" "regexp" - "strings" "testing" "github.com/golang/protobuf/ptypes/timestamp" @@ -38,7 +36,6 @@ import ( "k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage/driver" "k8s.io/helm/pkg/tiller/environment" - "k8s.io/helm/pkg/version" ) const notesText = "my notes here" @@ -105,6 +102,7 @@ func rsFixture() *ReleaseServer { }, env: MockEnvironment(), clientset: clientset, + Log: func(_ string, _ ...interface{}) {}, } } @@ -266,1024 +264,6 @@ func TestUniqName(t *testing.T) { } } -func TestInstallRelease(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - - // TODO: Refactor this into a mock. - req := &services.InstallReleaseRequest{ - Namespace: "spaced", - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - }, - }, - } - res, err := rs.InstallRelease(c, req) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - if res.Release.Name == "" { - t.Errorf("Expected release name.") - } - if res.Release.Namespace != "spaced" { - t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) - } - - rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } - - t.Logf("rel: %v", rel) - - if len(rel.Hooks) != 1 { - t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) - } - if rel.Hooks[0].Manifest != manifestWithHook { - t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) - } - - if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { - t.Errorf("Expected event 0 is post install") - } - if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { - t.Errorf("Expected event 0 is pre-delete") - } - - if len(res.Release.Manifest) == 0 { - t.Errorf("No manifest returned: %v", res.Release) - } - - if len(rel.Manifest) == 0 { - t.Errorf("Expected manifest in %v", res) - } - - if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { - t.Errorf("unexpected output: %s", rel.Manifest) - } - - if rel.Info.Description != "Install complete" { - t.Errorf("unexpected description: %s", rel.Info.Description) - } -} - -func TestInstallRelease_WithNotes(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - - // TODO: Refactor this into a mock. - req := &services.InstallReleaseRequest{ - Namespace: "spaced", - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - {Name: "templates/NOTES.txt", Data: []byte(notesText)}, - }, - }, - } - res, err := rs.InstallRelease(c, req) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - if res.Release.Name == "" { - t.Errorf("Expected release name.") - } - if res.Release.Namespace != "spaced" { - t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) - } - - rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } - - t.Logf("rel: %v", rel) - - if len(rel.Hooks) != 1 { - t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) - } - if rel.Hooks[0].Manifest != manifestWithHook { - t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) - } - - if rel.Info.Status.Notes != notesText { - t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes) - } - - if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { - t.Errorf("Expected event 0 is post install") - } - if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { - t.Errorf("Expected event 0 is pre-delete") - } - - if len(res.Release.Manifest) == 0 { - t.Errorf("No manifest returned: %v", res.Release) - } - - if len(rel.Manifest) == 0 { - t.Errorf("Expected manifest in %v", res) - } - - if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { - t.Errorf("unexpected output: %s", rel.Manifest) - } - - if rel.Info.Description != "Install complete" { - t.Errorf("unexpected description: %s", rel.Info.Description) - } -} - -func TestInstallRelease_WithNotesRendered(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - - // TODO: Refactor this into a mock. - req := &services.InstallReleaseRequest{ - Namespace: "spaced", - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - {Name: "templates/NOTES.txt", Data: []byte(notesText + " {{.Release.Name}}")}, - }, - }, - } - res, err := rs.InstallRelease(c, req) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - if res.Release.Name == "" { - t.Errorf("Expected release name.") - } - if res.Release.Namespace != "spaced" { - t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) - } - - rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } - - t.Logf("rel: %v", rel) - - if len(rel.Hooks) != 1 { - t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) - } - if rel.Hooks[0].Manifest != manifestWithHook { - t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) - } - - expectedNotes := fmt.Sprintf("%s %s", notesText, res.Release.Name) - if rel.Info.Status.Notes != expectedNotes { - t.Fatalf("Expected '%s', got '%s'", expectedNotes, rel.Info.Status.Notes) - } - - if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { - t.Errorf("Expected event 0 is post install") - } - if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { - t.Errorf("Expected event 0 is pre-delete") - } - - if len(res.Release.Manifest) == 0 { - t.Errorf("No manifest returned: %v", res.Release) - } - - if len(rel.Manifest) == 0 { - t.Errorf("Expected manifest in %v", res) - } - - if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { - t.Errorf("unexpected output: %s", rel.Manifest) - } - - if rel.Info.Description != "Install complete" { - t.Errorf("unexpected description: %s", rel.Info.Description) - } -} - -func TestInstallRelease_TillerVersion(t *testing.T) { - version.Version = "2.2.0" - c := helm.NewContext() - rs := rsFixture() - - // TODO: Refactor this into a mock. - req := &services.InstallReleaseRequest{ - Namespace: "spaced", - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello", TillerVersion: ">=2.2.0"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - }, - }, - } - _, err := rs.InstallRelease(c, req) - if err != nil { - t.Fatalf("Expected valid range. Got %q", err) - } -} - -func TestInstallRelease_WrongTillerVersion(t *testing.T) { - version.Version = "2.2.0" - c := helm.NewContext() - rs := rsFixture() - - // TODO: Refactor this into a mock. - req := &services.InstallReleaseRequest{ - Namespace: "spaced", - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello", TillerVersion: "<2.0.0"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - }, - }, - } - _, err := rs.InstallRelease(c, req) - if err == nil { - t.Fatalf("Expected to fail because of wrong version") - } - - expect := "Chart incompatible with Tiller" - if !strings.Contains(err.Error(), expect) { - t.Errorf("Expected %q to contain %q", err.Error(), expect) - } -} - -func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - - // TODO: Refactor this into a mock. - req := &services.InstallReleaseRequest{ - Namespace: "spaced", - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - {Name: "templates/NOTES.txt", Data: []byte(notesText)}, - }, - Dependencies: []*chart.Chart{ - { - Metadata: &chart.Metadata{Name: "hello"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - {Name: "templates/NOTES.txt", Data: []byte(notesText + " child")}, - }, - }, - }, - }, - } - - res, err := rs.InstallRelease(c, req) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - if res.Release.Name == "" { - t.Errorf("Expected release name.") - } - - rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } - - t.Logf("rel: %v", rel) - - if rel.Info.Status.Notes != notesText { - t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes) - } - - if rel.Info.Description != "Install complete" { - t.Errorf("unexpected description: %s", rel.Info.Description) - } -} - -func TestInstallRelease_DryRun(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - - req := &services.InstallReleaseRequest{ - Chart: chartStub(), - DryRun: true, - } - res, err := rs.InstallRelease(c, req) - if err != nil { - t.Errorf("Failed install: %s", err) - } - if res.Release.Name == "" { - t.Errorf("Expected release name.") - } - - if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { - t.Errorf("unexpected output: %s", res.Release.Manifest) - } - - if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") { - t.Errorf("unexpected output: %s", res.Release.Manifest) - } - - if !strings.Contains(res.Release.Manifest, "hello: Earth") { - t.Errorf("Should contain partial content. %s", res.Release.Manifest) - } - - if strings.Contains(res.Release.Manifest, "hello: {{ template \"_planet\" . }}") { - t.Errorf("Should not contain partial templates itself. %s", res.Release.Manifest) - } - - if strings.Contains(res.Release.Manifest, "empty") { - t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) - } - - if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil { - t.Errorf("Expected no stored release.") - } - - if l := len(res.Release.Hooks); l != 1 { - t.Fatalf("Expected 1 hook, got %d", l) - } - - if res.Release.Hooks[0].LastRun != nil { - t.Error("Expected hook to not be marked as run.") - } - - if res.Release.Info.Description != "Dry run complete" { - t.Errorf("unexpected description: %s", res.Release.Info.Description) - } -} - -func TestInstallRelease_NoHooks(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rs.env.Releases.Create(releaseStub()) - - req := &services.InstallReleaseRequest{ - Chart: chartStub(), - DisableHooks: true, - } - res, err := rs.InstallRelease(c, req) - if err != nil { - t.Errorf("Failed install: %s", err) - } - - if hl := res.Release.Hooks[0].LastRun; hl != nil { - t.Errorf("Expected that no hooks were run. Got %d", hl) - } -} - -func TestInstallRelease_FailedHooks(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rs.env.Releases.Create(releaseStub()) - rs.env.KubeClient = newHookFailingKubeClient() - - req := &services.InstallReleaseRequest{ - Chart: chartStub(), - } - res, err := rs.InstallRelease(c, req) - if err == nil { - t.Error("Expected failed install") - } - - if hl := res.Release.Info.Status.Code; hl != release.Status_FAILED { - t.Errorf("Expected FAILED release. Got %d", hl) - } -} - -func TestInstallRelease_ReuseName(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rel.Info.Status.Code = release.Status_DELETED - rs.env.Releases.Create(rel) - - req := &services.InstallReleaseRequest{ - Chart: chartStub(), - ReuseName: true, - Name: rel.Name, - } - res, err := rs.InstallRelease(c, req) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - if res.Release.Name != rel.Name { - t.Errorf("expected %q, got %q", rel.Name, res.Release.Name) - } - - getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 0} - getres, err := rs.GetReleaseStatus(c, getreq) - if err != nil { - t.Errorf("Failed to retrieve release: %s", err) - } - if getres.Info.Status.Code != release.Status_DEPLOYED { - t.Errorf("Release status is %q", getres.Info.Status.Code) - } -} - -func TestUpdateRelease(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)}, - }, - }, - } - res, err := rs.UpdateRelease(c, req) - if err != nil { - t.Fatalf("Failed updated: %s", err) - } - - if res.Release.Name == "" { - t.Errorf("Expected release name.") - } - - if res.Release.Name != rel.Name { - t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) - } - - if res.Release.Namespace != rel.Namespace { - t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) - } - - updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } - - if len(updated.Hooks) != 1 { - t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) - } - if updated.Hooks[0].Manifest != manifestWithUpgradeHooks { - t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) - } - - if updated.Hooks[0].Events[0] != release.Hook_POST_UPGRADE { - t.Errorf("Expected event 0 to be post upgrade") - } - - if updated.Hooks[0].Events[1] != release.Hook_PRE_UPGRADE { - t.Errorf("Expected event 0 to be pre upgrade") - } - - if len(res.Release.Manifest) == 0 { - t.Errorf("No manifest returned: %v", res.Release) - } - - if res.Release.Config == nil { - t.Errorf("Got release without config: %#v", res.Release) - } else if res.Release.Config.Raw != rel.Config.Raw { - t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw) - } - - if len(updated.Manifest) == 0 { - t.Errorf("Expected manifest in %v", res) - } - - if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { - t.Errorf("unexpected output: %s", rel.Manifest) - } - - if res.Release.Version != 2 { - t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version) - } - - edesc := "Upgrade complete" - if got := res.Release.Info.Description; got != edesc { - t.Errorf("Expected description %q, got %q", edesc, got) - } -} -func TestUpdateRelease_ResetValues(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)}, - }, - }, - ResetValues: 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 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() - rel := releaseStub() - rs.env.Releases.Create(rel) - rs.env.KubeClient = newUpdateFailingKubeClient() - - req := &services.UpdateReleaseRequest{ - Name: rel.Name, - DisableHooks: true, - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello"}, - Templates: []*chart.Template{ - {Name: "templates/something", Data: []byte("hello: world")}, - }, - }, - } - - res, err := rs.UpdateRelease(c, req) - if err == nil { - t.Error("Expected failed update") - } - - if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_FAILED { - t.Errorf("Expected FAILED release. Got %d", updatedStatus) - } - - edesc := "Upgrade \"angry-panda\" failed: Failed update in kube client" - if got := res.Release.Info.Description; got != edesc { - t.Errorf("Expected description %q, got %q", edesc, got) - } - - oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) - if err != nil { - t.Errorf("Expected to be able to get previous release") - } - if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { - t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) - } -} - -func TestRollbackReleaseFailure(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rs.env.Releases.Create(rel) - upgradedRel := upgradeReleaseVersion(rel) - rs.env.Releases.Update(rel) - rs.env.Releases.Create(upgradedRel) - - req := &services.RollbackReleaseRequest{ - Name: rel.Name, - DisableHooks: true, - } - - rs.env.KubeClient = newUpdateFailingKubeClient() - res, err := rs.RollbackRelease(c, req) - if err == nil { - t.Error("Expected failed rollback") - } - - if targetStatus := res.Release.Info.Status.Code; targetStatus != release.Status_FAILED { - t.Errorf("Expected FAILED release. Got %v", targetStatus) - } - - oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) - if err != nil { - t.Errorf("Expected to be able to get previous release") - } - if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { - t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) - } -} - -func TestUpdateReleaseNoHooks(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rs.env.Releases.Create(rel) - - req := &services.UpdateReleaseRequest{ - Name: rel.Name, - DisableHooks: true, - Chart: &chart.Chart{ - Metadata: &chart.Metadata{Name: "hello"}, - Templates: []*chart.Template{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, - }, - }, - } - - res, err := rs.UpdateRelease(c, req) - if err != nil { - t.Fatalf("Failed updated: %s", err) - } - - if hl := res.Release.Hooks[0].LastRun; hl != nil { - t.Errorf("Expected that no hooks were run. Got %d", hl) - } - -} - -func TestUpdateReleaseNoChanges(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rs.env.Releases.Create(rel) - - req := &services.UpdateReleaseRequest{ - Name: rel.Name, - DisableHooks: true, - Chart: rel.GetChart(), - } - - _, err := rs.UpdateRelease(c, req) - if err != nil { - t.Fatalf("Failed updated: %s", err) - } -} - -func TestRollbackReleaseNoHooks(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rel.Hooks = []*release.Hook{ - { - Name: "test-cm", - Kind: "ConfigMap", - Path: "test-cm", - Manifest: manifestWithRollbackHooks, - Events: []release.Hook_Event{ - release.Hook_PRE_ROLLBACK, - release.Hook_POST_ROLLBACK, - }, - }, - } - rs.env.Releases.Create(rel) - upgradedRel := upgradeReleaseVersion(rel) - rs.env.Releases.Update(rel) - rs.env.Releases.Create(upgradedRel) - - req := &services.RollbackReleaseRequest{ - Name: rel.Name, - DisableHooks: true, - } - - res, err := rs.RollbackRelease(c, req) - if err != nil { - t.Fatalf("Failed rollback: %s", err) - } - - if hl := res.Release.Hooks[0].LastRun; hl != nil { - t.Errorf("Expected that no hooks were run. Got %d", hl) - } -} - -func TestRollbackWithReleaseVersion(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rs.env.Releases.Create(rel) - upgradedRel := upgradeReleaseVersion(rel) - rs.env.Releases.Update(rel) - rs.env.Releases.Create(upgradedRel) - - req := &services.RollbackReleaseRequest{ - Name: rel.Name, - DisableHooks: true, - Version: 1, - } - - _, err := rs.RollbackRelease(c, req) - if err != nil { - t.Fatalf("Failed rollback: %s", err) - } -} - -func TestRollbackRelease(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rs.env.Releases.Create(rel) - upgradedRel := upgradeReleaseVersion(rel) - upgradedRel.Hooks = []*release.Hook{ - { - Name: "test-cm", - Kind: "ConfigMap", - Path: "test-cm", - Manifest: manifestWithRollbackHooks, - Events: []release.Hook_Event{ - release.Hook_PRE_ROLLBACK, - release.Hook_POST_ROLLBACK, - }, - }, - } - - upgradedRel.Manifest = "hello world" - rs.env.Releases.Update(rel) - rs.env.Releases.Create(upgradedRel) - - req := &services.RollbackReleaseRequest{ - Name: rel.Name, - } - res, err := rs.RollbackRelease(c, req) - if err != nil { - t.Fatalf("Failed rollback: %s", err) - } - - if res.Release.Name == "" { - t.Errorf("Expected release name.") - } - - if res.Release.Name != rel.Name { - t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) - } - - if res.Release.Namespace != rel.Namespace { - t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) - } - - if res.Release.Version != 3 { - t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) - } - - updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } - - if len(updated.Hooks) != 2 { - t.Fatalf("Expected 2 hooks, got %d", len(updated.Hooks)) - } - - if updated.Hooks[0].Manifest != manifestWithHook { - t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) - } - - anotherUpgradedRelease := upgradeReleaseVersion(upgradedRel) - rs.env.Releases.Update(upgradedRel) - rs.env.Releases.Create(anotherUpgradedRelease) - - res, err = rs.RollbackRelease(c, req) - if err != nil { - t.Fatalf("Failed rollback: %s", err) - } - - updated, err = rs.env.Releases.Get(res.Release.Name, res.Release.Version) - if err != nil { - t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) - } - - if len(updated.Hooks) != 1 { - t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) - } - - if updated.Hooks[0].Manifest != manifestWithRollbackHooks { - t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) - } - - if res.Release.Version != 4 { - t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) - } - - if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK { - t.Errorf("Expected event 0 to be pre rollback") - } - - if updated.Hooks[0].Events[1] != release.Hook_POST_ROLLBACK { - t.Errorf("Expected event 1 to be post rollback") - } - - if len(res.Release.Manifest) == 0 { - t.Errorf("No manifest returned: %v", res.Release) - } - - if len(updated.Manifest) == 0 { - t.Errorf("Expected manifest in %v", res) - } - - if !strings.Contains(updated.Manifest, "hello world") { - t.Errorf("unexpected output: %s", rel.Manifest) - } - - if res.Release.Info.Description != "Rollback to 2" { - t.Errorf("Expected rollback to 2, got %q", res.Release.Info.Description) - } - -} - -func TestUninstallRelease(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rs.env.Releases.Create(releaseStub()) - - req := &services.UninstallReleaseRequest{ - Name: "angry-panda", - } - - res, err := rs.UninstallRelease(c, req) - if err != nil { - t.Fatalf("Failed uninstall: %s", err) - } - - if res.Release.Name != "angry-panda" { - t.Errorf("Expected angry-panda, got %q", res.Release.Name) - } - - if res.Release.Info.Status.Code != release.Status_DELETED { - t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) - } - - if res.Release.Hooks[0].LastRun.Seconds == 0 { - t.Error("Expected LastRun to be greater than zero.") - } - - if res.Release.Info.Deleted.Seconds <= 0 { - t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) - } - - if res.Release.Info.Description != "Deletion complete" { - t.Errorf("Expected Deletion complete, got %q", res.Release.Info.Description) - } -} - -func TestUninstallPurgeRelease(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rs.env.Releases.Create(rel) - upgradedRel := upgradeReleaseVersion(rel) - rs.env.Releases.Update(rel) - rs.env.Releases.Create(upgradedRel) - - req := &services.UninstallReleaseRequest{ - Name: "angry-panda", - Purge: true, - } - - res, err := rs.UninstallRelease(c, req) - if err != nil { - t.Fatalf("Failed uninstall: %s", err) - } - - if res.Release.Name != "angry-panda" { - t.Errorf("Expected angry-panda, got %q", res.Release.Name) - } - - if res.Release.Info.Status.Code != release.Status_DELETED { - t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) - } - - if res.Release.Info.Deleted.Seconds <= 0 { - t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) - } - rels, err := rs.GetHistory(helm.NewContext(), &services.GetHistoryRequest{Name: "angry-panda"}) - if err != nil { - t.Fatal(err) - } - if len(rels.Releases) != 0 { - t.Errorf("Expected no releases in storage, got %d", len(rels.Releases)) - } -} - -func TestUninstallPurgeDeleteRelease(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rs.env.Releases.Create(releaseStub()) - - req := &services.UninstallReleaseRequest{ - Name: "angry-panda", - } - - _, err := rs.UninstallRelease(c, req) - if err != nil { - t.Fatalf("Failed uninstall: %s", err) - } - - req2 := &services.UninstallReleaseRequest{ - Name: "angry-panda", - Purge: true, - } - - _, err2 := rs.UninstallRelease(c, req2) - if err2 != nil && err2.Error() != "'angry-panda' has no deployed releases" { - t.Errorf("Failed uninstall: %s", err2) - } -} - -func TestUninstallReleaseWithKeepPolicy(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - name := "angry-bunny" - rs.env.Releases.Create(releaseWithKeepStub(name)) - - req := &services.UninstallReleaseRequest{ - Name: name, - } - - res, err := rs.UninstallRelease(c, req) - if err != nil { - t.Fatalf("Failed uninstall: %s", err) - } - - if res.Release.Name != name { - t.Errorf("Expected angry-bunny, got %q", res.Release.Name) - } - - if res.Release.Info.Status.Code != release.Status_DELETED { - t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) - } - - if res.Info == "" { - t.Errorf("Expected response info to not be empty") - } else { - if !strings.Contains(res.Info, "[ConfigMap] test-cm-keep") { - t.Errorf("unexpected output: %s", res.Info) - } - } -} - func releaseWithKeepStub(rlsName string) *release.Release { ch := &chart.Chart{ Metadata: &chart.Metadata{ @@ -1309,305 +289,6 @@ func releaseWithKeepStub(rlsName string) *release.Release { } } -func TestUninstallReleaseNoHooks(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rs.env.Releases.Create(releaseStub()) - - req := &services.UninstallReleaseRequest{ - Name: "angry-panda", - DisableHooks: true, - } - - res, err := rs.UninstallRelease(c, req) - if err != nil { - t.Errorf("Failed uninstall: %s", err) - } - - // The default value for a protobuf timestamp is nil. - if res.Release.Hooks[0].LastRun != nil { - t.Errorf("Expected LastRun to be zero, got %d.", res.Release.Hooks[0].LastRun.Seconds) - } -} - -func TestGetReleaseContent(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - if err := rs.env.Releases.Create(rel); err != nil { - t.Fatalf("Could not store mock release: %s", err) - } - - res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name, Version: 1}) - if err != nil { - t.Errorf("Error getting release content: %s", err) - } - - if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name { - t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name) - } -} - -func TestGetReleaseStatus(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - if err := rs.env.Releases.Create(rel); err != nil { - t.Fatalf("Could not store mock release: %s", err) - } - - res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) - if err != nil { - t.Errorf("Error getting release content: %s", err) - } - - if res.Name != rel.Name { - t.Errorf("Expected name %q, got %q", rel.Name, res.Name) - } - if res.Info.Status.Code != release.Status_DEPLOYED { - t.Errorf("Expected %d, got %d", release.Status_DEPLOYED, res.Info.Status.Code) - } -} - -func TestGetReleaseStatusDeleted(t *testing.T) { - c := helm.NewContext() - rs := rsFixture() - rel := releaseStub() - rel.Info.Status.Code = release.Status_DELETED - if err := rs.env.Releases.Create(rel); err != nil { - t.Fatalf("Could not store mock release: %s", err) - } - - res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) - if err != nil { - t.Fatalf("Error getting release content: %s", err) - } - - if res.Info.Status.Code != release.Status_DELETED { - t.Errorf("Expected %d, got %d", release.Status_DELETED, res.Info.Status.Code) - } -} - -func TestListReleases(t *testing.T) { - rs := rsFixture() - num := 7 - for i := 0; i < num; i++ { - rel := releaseStub() - rel.Name = fmt.Sprintf("rel-%d", i) - if err := rs.env.Releases.Create(rel); err != nil { - t.Fatalf("Could not store mock release: %s", err) - } - } - - mrs := &mockListServer{} - if err := rs.ListReleases(&services.ListReleasesRequest{Offset: "", Limit: 64}, mrs); err != nil { - t.Fatalf("Failed listing: %s", err) - } - - if len(mrs.val.Releases) != num { - t.Errorf("Expected %d releases, got %d", num, len(mrs.val.Releases)) - } -} - -func TestListReleasesByStatus(t *testing.T) { - rs := rsFixture() - stubs := []*release.Release{ - namedReleaseStub("kamal", release.Status_DEPLOYED), - namedReleaseStub("astrolabe", release.Status_DELETED), - namedReleaseStub("octant", release.Status_FAILED), - namedReleaseStub("sextant", release.Status_UNKNOWN), - } - for _, stub := range stubs { - if err := rs.env.Releases.Create(stub); err != nil { - t.Fatalf("Could not create stub: %s", err) - } - } - - tests := []struct { - statusCodes []release.Status_Code - names []string - }{ - { - names: []string{"kamal"}, - statusCodes: []release.Status_Code{release.Status_DEPLOYED}, - }, - { - names: []string{"astrolabe"}, - statusCodes: []release.Status_Code{release.Status_DELETED}, - }, - { - names: []string{"kamal", "octant"}, - statusCodes: []release.Status_Code{release.Status_DEPLOYED, release.Status_FAILED}, - }, - { - names: []string{"kamal", "astrolabe", "octant", "sextant"}, - statusCodes: []release.Status_Code{ - release.Status_DEPLOYED, - release.Status_DELETED, - release.Status_FAILED, - release.Status_UNKNOWN, - }, - }, - } - - for i, tt := range tests { - mrs := &mockListServer{} - if err := rs.ListReleases(&services.ListReleasesRequest{StatusCodes: tt.statusCodes, Offset: "", Limit: 64}, mrs); err != nil { - t.Fatalf("Failed listing %d: %s", i, err) - } - - if len(tt.names) != len(mrs.val.Releases) { - t.Fatalf("Expected %d releases, got %d", len(tt.names), len(mrs.val.Releases)) - } - - for _, name := range tt.names { - found := false - for _, rel := range mrs.val.Releases { - if rel.Name == name { - found = true - } - } - if !found { - t.Errorf("%d: Did not find name %q", i, name) - } - } - } -} - -func TestListReleasesSort(t *testing.T) { - rs := rsFixture() - - // Put them in by reverse order so that the mock doesn't "accidentally" - // sort. - num := 7 - for i := num; i > 0; i-- { - rel := releaseStub() - rel.Name = fmt.Sprintf("rel-%d", i) - if err := rs.env.Releases.Create(rel); err != nil { - t.Fatalf("Could not store mock release: %s", err) - } - } - - limit := 6 - mrs := &mockListServer{} - req := &services.ListReleasesRequest{ - Offset: "", - Limit: int64(limit), - SortBy: services.ListSort_NAME, - } - if err := rs.ListReleases(req, mrs); err != nil { - t.Fatalf("Failed listing: %s", err) - } - - if len(mrs.val.Releases) != limit { - t.Errorf("Expected %d releases, got %d", limit, len(mrs.val.Releases)) - } - - for i := 0; i < limit; i++ { - n := fmt.Sprintf("rel-%d", i+1) - if mrs.val.Releases[i].Name != n { - t.Errorf("Expected %q, got %q", n, mrs.val.Releases[i].Name) - } - } -} - -func TestListReleasesFilter(t *testing.T) { - rs := rsFixture() - names := []string{ - "axon", - "dendrite", - "neuron", - "neuroglia", - "synapse", - "nucleus", - "organelles", - } - num := 7 - for i := 0; i < num; i++ { - rel := releaseStub() - rel.Name = names[i] - if err := rs.env.Releases.Create(rel); err != nil { - t.Fatalf("Could not store mock release: %s", err) - } - } - - mrs := &mockListServer{} - req := &services.ListReleasesRequest{ - Offset: "", - Limit: 64, - Filter: "neuro[a-z]+", - SortBy: services.ListSort_NAME, - } - if err := rs.ListReleases(req, mrs); err != nil { - t.Fatalf("Failed listing: %s", err) - } - - if len(mrs.val.Releases) != 2 { - t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases)) - } - - if mrs.val.Releases[0].Name != "neuroglia" { - t.Errorf("Unexpected sort order: %v.", mrs.val.Releases) - } - if mrs.val.Releases[1].Name != "neuron" { - t.Errorf("Unexpected sort order: %v.", mrs.val.Releases) - } -} - -func TestReleasesNamespace(t *testing.T) { - rs := rsFixture() - - names := []string{ - "axon", - "dendrite", - "neuron", - "ribosome", - } - - namespaces := []string{ - "default", - "test123", - "test123", - "cerebellum", - } - num := 4 - for i := 0; i < num; i++ { - rel := releaseStub() - rel.Name = names[i] - rel.Namespace = namespaces[i] - if err := rs.env.Releases.Create(rel); err != nil { - t.Fatalf("Could not store mock release: %s", err) - } - } - - mrs := &mockListServer{} - req := &services.ListReleasesRequest{ - Offset: "", - Limit: 64, - Namespace: "test123", - } - - if err := rs.ListReleases(req, mrs); err != nil { - t.Fatalf("Failed listing: %s", err) - } - - if len(mrs.val.Releases) != 2 { - t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases)) - } -} - -func TestRunReleaseTest(t *testing.T) { - rs := rsFixture() - rel := namedReleaseStub("nemo", release.Status_DEPLOYED) - rs.env.Releases.Create(rel) - - req := &services.TestReleaseRequest{Name: "nemo", Timeout: 2} - err := rs.RunReleaseTest(req, mockRunReleaseTestServer{}) - if err != nil { - t.Fatalf("failed to run release tests on %s: %s", rel.Name, err) - } -} - func MockEnvironment() *environment.Environment { e := environment.New() e.Releases = storage.Init(driver.NewMemory()) @@ -1626,7 +307,7 @@ type updateFailingKubeClient struct { environment.PrintingKubeClient } -func (u *updateFailingKubeClient) Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error { +func (u *updateFailingKubeClient) Update(namespace string, originalReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { return errors.New("Failed update in kube client") } diff --git a/pkg/tiller/release_status.go b/pkg/tiller/release_status.go new file mode 100644 index 000000000..41eba1174 --- /dev/null +++ b/pkg/tiller/release_status.go @@ -0,0 +1,74 @@ +/* +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 ( + "errors" + "fmt" + ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +// GetReleaseStatus gets the status information for a named release. +func (s *ReleaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { + if !ValidName.MatchString(req.Name) { + return nil, errMissingRelease + } + + var rel *release.Release + + if req.Version <= 0 { + var err error + rel, err = s.env.Releases.Last(req.Name) + if err != nil { + return nil, fmt.Errorf("getting deployed release %q: %s", req.Name, err) + } + } else { + var err error + if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil { + return nil, fmt.Errorf("getting release '%s' (v%d): %s", req.Name, req.Version, err) + } + } + + if rel.Info == nil { + return nil, errors.New("release info is missing") + } + if rel.Chart == nil { + return nil, errors.New("release chart is missing") + } + + sc := rel.Info.Status.Code + statusResp := &services.GetReleaseStatusResponse{ + Name: rel.Name, + Namespace: rel.Namespace, + Info: rel.Info, + } + + // Ok, we got the status of the release as we had jotted down, now we need to match the + // manifest we stashed away with reality from the cluster. + resp, err := s.ReleaseModule.Status(rel, req, s.env) + if sc == release.Status_DELETED || sc == release.Status_FAILED { + // Skip errors if this is already deleted or failed. + return statusResp, nil + } else if err != nil { + s.Log("warning: Get for %s failed: %v", rel.Name, err) + return nil, err + } + rel.Info.Status.Resources = resp + return statusResp, nil +} diff --git a/pkg/tiller/release_status_test.go b/pkg/tiller/release_status_test.go new file mode 100644 index 000000000..9128b43a1 --- /dev/null +++ b/pkg/tiller/release_status_test.go @@ -0,0 +1,64 @@ +/* +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 ( + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "testing" +) + +func TestGetReleaseStatus(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) + if err != nil { + t.Errorf("Error getting release content: %s", err) + } + + if res.Name != rel.Name { + t.Errorf("Expected name %q, got %q", rel.Name, res.Name) + } + if res.Info.Status.Code != release.Status_DEPLOYED { + t.Errorf("Expected %d, got %d", release.Status_DEPLOYED, res.Info.Status.Code) + } +} + +func TestGetReleaseStatusDeleted(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rel.Info.Status.Code = release.Status_DELETED + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) + if err != nil { + t.Fatalf("Error getting release content: %s", err) + } + + if res.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected %d, got %d", release.Status_DELETED, res.Info.Status.Code) + } +} diff --git a/pkg/tiller/release_testing.go b/pkg/tiller/release_testing.go new file mode 100644 index 000000000..4f9a38a96 --- /dev/null +++ b/pkg/tiller/release_testing.go @@ -0,0 +1,71 @@ +/* +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 ( + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + reltesting "k8s.io/helm/pkg/releasetesting" +) + +// RunReleaseTest runs pre-defined tests stored as hooks on a given release +func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { + + if !ValidName.MatchString(req.Name) { + return errMissingRelease + } + + // finds the non-deleted release with the given name + rel, err := s.env.Releases.Last(req.Name) + if err != nil { + return err + } + + testEnv := &reltesting.Environment{ + Namespace: rel.Namespace, + KubeClient: s.env.KubeClient, + Timeout: req.Timeout, + Stream: stream, + } + s.Log("running tests for release %s", rel.Name) + tSuite, err := reltesting.NewTestSuite(rel) + if err != nil { + s.Log("error creating test suite for %s: %s", rel.Name, err) + return err + } + + if err := tSuite.Run(testEnv); err != nil { + s.Log("error running test suite for %s: %s", rel.Name, err) + return err + } + + rel.Info.Status.LastTestSuiteRun = &release.TestSuite{ + StartedAt: tSuite.StartedAt, + CompletedAt: tSuite.CompletedAt, + Results: tSuite.Results, + } + + if req.Cleanup { + testEnv.DeleteTestPods(tSuite.TestManifests) + } + + if err := s.env.Releases.Update(rel); err != nil { + s.Log("test: Failed to store updated release: %s", err) + } + + return nil +} diff --git a/pkg/tiller/release_testing_test.go b/pkg/tiller/release_testing_test.go new file mode 100644 index 000000000..0b3b7768e --- /dev/null +++ b/pkg/tiller/release_testing_test.go @@ -0,0 +1,35 @@ +/* +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 ( + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "testing" +) + +func TestRunReleaseTest(t *testing.T) { + rs := rsFixture() + rel := namedReleaseStub("nemo", release.Status_DEPLOYED) + rs.env.Releases.Create(rel) + + req := &services.TestReleaseRequest{Name: "nemo", Timeout: 2} + err := rs.RunReleaseTest(req, mockRunReleaseTestServer{}) + if err != nil { + t.Fatalf("failed to run release tests on %s: %s", rel.Name, err) + } +} diff --git a/pkg/tiller/release_uninstall.go b/pkg/tiller/release_uninstall.go new file mode 100644 index 000000000..8f7846d68 --- /dev/null +++ b/pkg/tiller/release_uninstall.go @@ -0,0 +1,137 @@ +/* +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 ( + "fmt" + "strings" + + ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/timeconv" +) + +// UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED. +func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { + err := s.env.Releases.LockRelease(req.Name) + if err != nil { + return nil, err + } + defer s.env.Releases.UnlockRelease(req.Name) + + if !ValidName.MatchString(req.Name) { + s.Log("uninstall: Release not found: %s", req.Name) + return nil, errMissingRelease + } + + if len(req.Name) > releaseNameMaxLen { + return nil, fmt.Errorf("release name %q exceeds max length of %d", req.Name, releaseNameMaxLen) + } + + rels, err := s.env.Releases.History(req.Name) + if err != nil { + s.Log("uninstall: Release not loaded: %s", req.Name) + return nil, err + } + if len(rels) < 1 { + return nil, errMissingRelease + } + + relutil.SortByRevision(rels) + rel := rels[len(rels)-1] + + // TODO: Are there any cases where we want to force a delete even if it's + // already marked deleted? + if rel.Info.Status.Code == release.Status_DELETED { + if req.Purge { + if err := s.purgeReleases(rels...); err != nil { + s.Log("uninstall: Failed to purge the release: %s", err) + return nil, err + } + return &services.UninstallReleaseResponse{Release: rel}, nil + } + return nil, fmt.Errorf("the release named %q is already deleted", req.Name) + } + + s.Log("uninstall: Deleting %s", req.Name) + rel.Info.Status.Code = release.Status_DELETING + rel.Info.Deleted = timeconv.Now() + rel.Info.Description = "Deletion in progress (or silently failed)" + res := &services.UninstallReleaseResponse{Release: rel} + + if !req.DisableHooks { + if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PreDelete, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("delete hooks disabled for %s", req.Name) + } + + // From here on out, the release is currently considered to be in Status_DELETING + // state. + if err := s.env.Releases.Update(rel); err != nil { + s.Log("uninstall: Failed to store updated release: %s", err) + } + + kept, errs := s.ReleaseModule.Delete(rel, req, s.env) + res.Info = kept + + es := make([]string, 0, len(errs)) + for _, e := range errs { + s.Log("error: %v", e) + es = append(es, e.Error()) + } + + if !req.DisableHooks { + if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PostDelete, req.Timeout); err != nil { + es = append(es, err.Error()) + } + } + + rel.Info.Status.Code = release.Status_DELETED + rel.Info.Description = "Deletion complete" + + if req.Purge { + s.Log("purge requested for %s", req.Name) + err := s.purgeReleases(rels...) + if err != nil { + s.Log("uninstall: Failed to purge the release: %s", err) + } + return res, err + } + + if err := s.env.Releases.Update(rel); err != nil { + s.Log("uninstall: Failed to store updated release: %s", err) + } + + if len(es) > 0 { + return res, fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) + } + return res, nil +} + +func (s *ReleaseServer) purgeReleases(rels ...*release.Release) error { + for _, rel := range rels { + if _, err := s.env.Releases.Delete(rel.Name, rel.Version); err != nil { + return err + } + } + return nil +} diff --git a/pkg/tiller/release_uninstall_test.go b/pkg/tiller/release_uninstall_test.go new file mode 100644 index 000000000..ef3afb444 --- /dev/null +++ b/pkg/tiller/release_uninstall_test.go @@ -0,0 +1,177 @@ +/* +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 ( + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "strings" + "testing" +) + +func TestUninstallRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + if res.Release.Name != "angry-panda" { + t.Errorf("Expected angry-panda, got %q", res.Release.Name) + } + + if res.Release.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) + } + + if res.Release.Hooks[0].LastRun.Seconds == 0 { + t.Error("Expected LastRun to be greater than zero.") + } + + if res.Release.Info.Deleted.Seconds <= 0 { + t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) + } + + if res.Release.Info.Description != "Deletion complete" { + t.Errorf("Expected Deletion complete, got %q", res.Release.Info.Description) + } +} + +func TestUninstallPurgeRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + Purge: true, + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + if res.Release.Name != "angry-panda" { + t.Errorf("Expected angry-panda, got %q", res.Release.Name) + } + + if res.Release.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) + } + + if res.Release.Info.Deleted.Seconds <= 0 { + t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) + } + rels, err := rs.GetHistory(helm.NewContext(), &services.GetHistoryRequest{Name: "angry-panda"}) + if err != nil { + t.Fatal(err) + } + if len(rels.Releases) != 0 { + t.Errorf("Expected no releases in storage, got %d", len(rels.Releases)) + } +} + +func TestUninstallPurgeDeleteRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + } + + _, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + req2 := &services.UninstallReleaseRequest{ + Name: "angry-panda", + Purge: true, + } + + _, err2 := rs.UninstallRelease(c, req2) + if err2 != nil && err2.Error() != "'angry-panda' has no deployed releases" { + t.Errorf("Failed uninstall: %s", err2) + } +} + +func TestUninstallReleaseWithKeepPolicy(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + name := "angry-bunny" + rs.env.Releases.Create(releaseWithKeepStub(name)) + + req := &services.UninstallReleaseRequest{ + Name: name, + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + if res.Release.Name != name { + t.Errorf("Expected angry-bunny, got %q", res.Release.Name) + } + + if res.Release.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) + } + + if res.Info == "" { + t.Errorf("Expected response info to not be empty") + } else { + if !strings.Contains(res.Info, "[ConfigMap] test-cm-keep") { + t.Errorf("unexpected output: %s", res.Info) + } + } +} + +func TestUninstallReleaseNoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + DisableHooks: true, + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Errorf("Failed uninstall: %s", err) + } + + // The default value for a protobuf timestamp is nil. + if res.Release.Hooks[0].LastRun != nil { + t.Errorf("Expected LastRun to be zero, got %d.", res.Release.Hooks[0].LastRun.Seconds) + } +} diff --git a/pkg/tiller/release_update.go b/pkg/tiller/release_update.go new file mode 100644 index 000000000..fb30d1661 --- /dev/null +++ b/pkg/tiller/release_update.go @@ -0,0 +1,174 @@ +/* +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 ( + "fmt" + + ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/timeconv" +) + +// UpdateRelease takes an existing release and new information, and upgrades the release. +func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { + err := s.env.Releases.LockRelease(req.Name) + if err != nil { + return nil, err + } + defer s.env.Releases.UnlockRelease(req.Name) + + s.Log("preparing update for %s", req.Name) + currentRelease, updatedRelease, err := s.prepareUpdate(req) + if err != nil { + return nil, err + } + + s.Log("performing update for %s", req.Name) + res, err := s.performUpdate(currentRelease, updatedRelease, req) + if err != nil { + return res, err + } + + if !req.DryRun { + s.Log("creating updated release for %s", req.Name) + if err := s.env.Releases.Create(updatedRelease); err != nil { + return res, err + } + } + + return res, nil +} + +// prepareUpdate builds an updated release for an update operation. +func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { + if !ValidName.MatchString(req.Name) { + return nil, nil, errMissingRelease + } + + if req.Chart == nil { + return nil, nil, errMissingChart + } + + // finds the non-deleted release with the given name + currentRelease, err := s.env.Releases.Last(req.Name) + if err != nil { + return nil, nil, err + } + + // If new values were not supplied in the upgrade, re-use the existing values. + 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. + revision := currentRelease.Version + 1 + + ts := timeconv.Now() + options := chartutil.ReleaseOptions{ + Name: req.Name, + Time: ts, + Namespace: currentRelease.Namespace, + IsUpgrade: true, + Revision: int(revision), + } + + caps, err := capabilities(s.clientset.Discovery()) + if err != nil { + return nil, nil, err + } + valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) + if err != nil { + return nil, nil, err + } + + hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) + if err != nil { + return nil, nil, err + } + + // Store an updated release. + updatedRelease := &release.Release{ + Name: req.Name, + Namespace: currentRelease.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: currentRelease.Info.FirstDeployed, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_UNKNOWN}, + Description: "Preparing upgrade", // This should be overwritten later. + }, + Version: revision, + Manifest: manifestDoc.String(), + Hooks: hooks, + } + + if len(notesTxt) > 0 { + updatedRelease.Info.Status.Notes = notesTxt + } + err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) + return currentRelease, updatedRelease, err +} + +func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { + res := &services.UpdateReleaseResponse{Release: updatedRelease} + + if req.DryRun { + s.Log("dry run for %s", updatedRelease.Name) + res.Release.Info.Description = "Dry run complete" + return res, nil + } + + // pre-upgrade hooks + if !req.DisableHooks { + if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("update hooks disabled for %s", req.Name) + } + if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil { + msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err) + s.Log("warning: %s", msg) + originalRelease.Info.Status.Code = release.Status_SUPERSEDED + updatedRelease.Info.Status.Code = release.Status_FAILED + updatedRelease.Info.Description = msg + s.recordRelease(originalRelease, true) + s.recordRelease(updatedRelease, false) + return res, err + } + + // post-upgrade hooks + if !req.DisableHooks { + if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { + return res, err + } + } + + originalRelease.Info.Status.Code = release.Status_SUPERSEDED + s.recordRelease(originalRelease, true) + + updatedRelease.Info.Status.Code = release.Status_DEPLOYED + updatedRelease.Info.Description = "Upgrade complete" + + return res, nil +} diff --git a/pkg/tiller/release_update_test.go b/pkg/tiller/release_update_test.go new file mode 100644 index 000000000..cba07e11e --- /dev/null +++ b/pkg/tiller/release_update_test.go @@ -0,0 +1,286 @@ +/* +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 ( + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "strings" + "testing" +) + +func TestUpdateRelease(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)}, + }, + }, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if res.Release.Name != rel.Name { + t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) + } + + if res.Release.Namespace != rel.Namespace { + t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) + } + + updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if len(updated.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) + } + if updated.Hooks[0].Manifest != manifestWithUpgradeHooks { + t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) + } + + if updated.Hooks[0].Events[0] != release.Hook_POST_UPGRADE { + t.Errorf("Expected event 0 to be post upgrade") + } + + if updated.Hooks[0].Events[1] != release.Hook_PRE_UPGRADE { + t.Errorf("Expected event 0 to be pre upgrade") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if res.Release.Config == nil { + t.Errorf("Got release without config: %#v", res.Release) + } else if res.Release.Config.Raw != rel.Config.Raw { + t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw) + } + + if len(updated.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if res.Release.Version != 2 { + t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version) + } + + edesc := "Upgrade complete" + if got := res.Release.Info.Description; got != edesc { + t.Errorf("Expected description %q, got %q", edesc, got) + } +} +func TestUpdateRelease_ResetValues(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)}, + }, + }, + ResetValues: 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 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() + rel := releaseStub() + rs.env.Releases.Create(rel) + rs.env.KubeClient = newUpdateFailingKubeClient() + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/something", Data: []byte("hello: world")}, + }, + }, + } + + res, err := rs.UpdateRelease(c, req) + if err == nil { + t.Error("Expected failed update") + } + + if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_FAILED { + t.Errorf("Expected FAILED release. Got %d", updatedStatus) + } + + edesc := "Upgrade \"angry-panda\" failed: Failed update in kube client" + if got := res.Release.Info.Description; got != edesc { + t.Errorf("Expected description %q, got %q", edesc, got) + } + + oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) + if err != nil { + t.Errorf("Expected to be able to get previous release") + } + if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { + t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) + } +} + +func TestUpdateReleaseNoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + }, + } + + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + + if hl := res.Release.Hooks[0].LastRun; hl != nil { + t.Errorf("Expected that no hooks were run. Got %d", hl) + } + +} + +func TestUpdateReleaseNoChanges(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Chart: rel.GetChart(), + } + + _, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } +} diff --git a/pkg/tiller/release_version.go b/pkg/tiller/release_version.go new file mode 100644 index 000000000..5cf3ff828 --- /dev/null +++ b/pkg/tiller/release_version.go @@ -0,0 +1,29 @@ +/* +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 ( + ctx "golang.org/x/net/context" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/version" +) + +// GetVersion sends the server version. +func (s *ReleaseServer) GetVersion(c ctx.Context, req *services.GetVersionRequest) (*services.GetVersionResponse, error) { + v := version.GetVersionProto() + return &services.GetVersionResponse{Version: v}, nil +} diff --git a/rootfs/Dockerfile b/rootfs/Dockerfile index e30d981cb..53757cd8d 100644 --- a/rootfs/Dockerfile +++ b/rootfs/Dockerfile @@ -14,9 +14,11 @@ FROM alpine:3.3 +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* + ENV HOME /tmp -COPY . / +COPY tiller /tiller EXPOSE 44134 diff --git a/rootfs/Dockerfile.experimental b/rootfs/Dockerfile.experimental index 8f3bce3c0..990bcde51 100644 --- a/rootfs/Dockerfile.experimental +++ b/rootfs/Dockerfile.experimental @@ -14,6 +14,8 @@ FROM alpine:3.3 +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* + ENV HOME /tmp COPY tiller /tiller diff --git a/rootfs/Dockerfile.rudder b/rootfs/Dockerfile.rudder index 7821096c5..6bb3a2d92 100644 --- a/rootfs/Dockerfile.rudder +++ b/rootfs/Dockerfile.rudder @@ -14,6 +14,8 @@ FROM alpine:3.3 +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* + ENV HOME /tmp COPY rudder /rudder diff --git a/scripts/completions.bash b/scripts/completions.bash index be9ca298b..b2524ea38 100644 --- a/scripts/completions.bash +++ b/scripts/completions.bash @@ -431,6 +431,8 @@ _helm_fetch() flags+=("--destination=") two_word_flags+=("-d") local_nonpersistent_flags+=("--destination=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") flags+=("--key-file=") local_nonpersistent_flags+=("--key-file=") flags+=("--keyring=") @@ -805,6 +807,8 @@ _helm_install() local_nonpersistent_flags+=("--ca-file=") flags+=("--cert-file=") local_nonpersistent_flags+=("--cert-file=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") flags+=("--dry-run") local_nonpersistent_flags+=("--dry-run") flags+=("--key-file=") @@ -1044,6 +1048,28 @@ _helm_plugin_remove() noun_aliases=() } +_helm_plugin_update() +{ + last_command="helm_plugin_update" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + _helm_plugin() { last_command="helm_plugin" @@ -1051,6 +1077,7 @@ _helm_plugin() commands+=("install") commands+=("list") commands+=("remove") + commands+=("update") flags=() two_word_flags=() @@ -1268,6 +1295,8 @@ _helm_rollback() flags+=("--dry-run") local_nonpersistent_flags+=("--dry-run") + flags+=("--force") + local_nonpersistent_flags+=("--force") flags+=("--no-hooks") local_nonpersistent_flags+=("--no-hooks") flags+=("--recreate-pods") @@ -1440,10 +1469,14 @@ _helm_upgrade() local_nonpersistent_flags+=("--ca-file=") flags+=("--cert-file=") local_nonpersistent_flags+=("--cert-file=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") flags+=("--disable-hooks") local_nonpersistent_flags+=("--disable-hooks") flags+=("--dry-run") local_nonpersistent_flags+=("--dry-run") + flags+=("--force") + local_nonpersistent_flags+=("--force") flags+=("--install") flags+=("-i") local_nonpersistent_flags+=("--install") diff --git a/scripts/get b/scripts/get index a52a0a9a1..02884d7f9 100755 --- a/scripts/get +++ b/scripts/get @@ -62,31 +62,31 @@ verifySupported() { fi } -# checkLatestVersion checks the latest available version. -checkLatestVersion() { - # Use the GitHub releases webpage for the project to find the latest version for this project. - local latest_url="https://github.com/kubernetes/helm/releases/latest" +# checkDesiredVersion checks if the desired version is available. +checkDesiredVersion() { + # Use the GitHub releases webpage for the project to find the desired version for this project. + local release_url="https://github.com/kubernetes/helm/releases/${DESIRED_VERSION:-latest}" if type "curl" > /dev/null; then - TAG=$(curl -SsL $latest_url | awk '/\/tag\//' | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}') + TAG=$(curl -SsL $release_url | awk '/\/tag\//' | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}') elif type "wget" > /dev/null; then - TAG=$(wget -q -O - $latest_url | awk '/\/tag\//' | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}') + TAG=$(wget -q -O - $release_url | awk '/\/tag\//' | head -n 1 | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}') fi if [ "x$TAG" == "x" ]; then - echo "Cannot determine latest tag." + echo "Cannot determine ${DESIRED_VERSION} tag." exit 1 fi } # checkHelmInstalledVersion checks which version of helm is installed and -# if it needs to be updated. +# if it needs to be changed. 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." + echo "Helm ${version} is already ${DESIRED_VERSION:-latest}" return 0 else - echo "Helm ${TAG} is available. Upgrading from version ${version}." + echo "Helm ${TAG} is available. Changing from version ${version}." return 1 fi else @@ -137,7 +137,12 @@ installFile() { fail_trap() { result=$? if [ "$result" != "0" ]; then - echo "Failed to install $PROJECT_NAME" + if [[ -n "$INPUT_ARGUMENTS" ]]; then + echo "Failed to install $PROJECT_NAME with the arguments provided: $INPUT_ARGUMENTS" + help + else + echo "Failed to install $PROJECT_NAME" + fi echo -e "\tFor support, go to https://github.com/kubernetes/helm." fi exit $result @@ -156,15 +161,44 @@ testVersion() { echo "Run '$PROJECT_NAME init' to configure $PROJECT_NAME." } +# help provides possible cli installation arguments +help () { + echo "Accepted cli arguments are:" + echo -e "\t[--help|-h ] ->> prints this help" + echo -e "\t[--version|-v ] . When not defined it defaults to latest" + echo -e "\te.g. --version v2.4.0 or -v latest" +} + # Execution #Stop execution on any error trap "fail_trap" EXIT set -e + +# Parsing input arguments (if any) +export INPUT_ARGUMENTS="${@}" +set -u +while [[ $# -gt 0 ]]; do + case $1 in + '--version'|-v) + export DESIRED_VERSION="${2}" + shift + ;; + '--help'|-h) + help + exit 0 + ;; + *) exit 1 + ;; + esac + shift +done +set +u + initArch initOS verifySupported -checkLatestVersion +checkDesiredVersion if ! checkHelmInstalledVersion; then downloadFile installFile diff --git a/scripts/update-docs.sh b/scripts/update-docs.sh index 12a4ead4b..e014b537e 100755 --- a/scripts/update-docs.sh +++ b/scripts/update-docs.sh @@ -31,11 +31,13 @@ kube::util::ensure-temp-dir export HELM_NO_PLUGINS=1 -mkdir -p ${KUBE_TEMP}/docs/helm ${KUBE_TEMP}/docs/man/man1 ${KUBE_TEMP}/scripts +# Reset Helm Home because it is used in the generation of docs. +OLD_HELM_HOME=${HELM_HOME:-} +HELM_HOME="$HOME/.helm" +bin/helm init --client-only +mkdir -p ${KUBE_TEMP}/docs/helm bin/helm docs --dir ${KUBE_TEMP}/docs/helm -bin/helm docs --dir ${KUBE_TEMP}/docs/man/man1 --type man -bin/helm docs --dir ${KUBE_TEMP}/scripts --type bash - +HELM_HOME=$OLD_HELM_HOME FILES=$(find ${KUBE_TEMP} -type f) diff --git a/scripts/verify-docs.sh b/scripts/verify-docs.sh index 72af9e743..b0b799eac 100755 --- a/scripts/verify-docs.sh +++ b/scripts/verify-docs.sh @@ -31,10 +31,13 @@ kube::util::ensure-temp-dir export HELM_NO_PLUGINS=1 -mkdir -p ${KUBE_TEMP}/docs/helm ${KUBE_TEMP}/docs/man/man1 ${KUBE_TEMP}/scripts +# Reset Helm Home because it is used in the generation of docs. +OLD_HELM_HOME=${HELM_HOME:-} +HELM_HOME="$HOME/.helm" +bin/helm init --client-only +mkdir -p ${KUBE_TEMP}/docs/helm bin/helm docs --dir ${KUBE_TEMP}/docs/helm -bin/helm docs --dir ${KUBE_TEMP}/docs/man/man1 --type man -bin/helm docs --dir ${KUBE_TEMP}/scripts --type bash +HELM_HOME=$OLD_HELM_HOME FILES=$(find ${KUBE_TEMP} -type f)