diff --git a/cmd/helm/init.go b/cmd/helm/init.go index f053cbf86..c1d2c9e6b 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -76,6 +76,7 @@ type initCmd struct { upgrade bool namespace string dryRun bool + forceUpgrade bool skipRefresh bool out io.Writer home helmpath.Home @@ -106,6 +107,7 @@ func newInitCmd(out io.Writer) *cobra.Command { f.StringVarP(&i.image, "tiller-image", "i", "", "override Tiller image") f.BoolVar(&i.canary, "canary-image", false, "use the canary Tiller image") f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Tiller is already installed") + f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "force upgrade of Tiller to the current helm version") f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install Tiller") f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") @@ -164,6 +166,7 @@ func (i *initCmd) run() error { i.opts.Namespace = i.namespace i.opts.UseCanary = i.canary i.opts.ImageSpec = i.image + i.opts.ForceUpgrade = i.forceUpgrade i.opts.ServiceAccount = i.serviceAccount i.opts.MaxHistory = i.maxHistory diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index f0ec3afb9..0d954081a 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -17,10 +17,12 @@ limitations under the License. package installer // import "k8s.io/helm/cmd/helm/installer" import ( + "errors" "fmt" "io/ioutil" "strings" + "github.com/Masterminds/semver" "github.com/ghodss/yaml" "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" @@ -30,7 +32,8 @@ import ( "k8s.io/client-go/kubernetes" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" - + "k8s.io/helm/pkg/version" + "k8s.io/helm/pkg/chartutil" ) @@ -60,6 +63,10 @@ func Upgrade(client kubernetes.Interface, opts *Options) error { if err != nil { return err } + existingImage := obj.Spec.Template.Spec.Containers[0].Image + if !isNewerVersion(existingImage) && !opts.ForceUpgrade { + return errors.New("current Tiller version is newer, use --force-upgrade to downgrade") + } 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 @@ -75,6 +82,17 @@ func Upgrade(client kubernetes.Interface, opts *Options) error { return err } +// isNewerVersion returns whether the current version is newer than the give image's version +func isNewerVersion(image string) bool { + split := strings.Split(image, ":") + if len(split) < 2 { + // If we don't know the version, we consider the current version newer + return true + } + imageVersion := split[1] + return semver.MustParse(version.Version).GreaterThan(semver.MustParse(imageVersion)) +} + // createDeployment creates the Tiller Deployment resource. func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { obj, err := deployment(opts) diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index ad30557e2..1133bff67 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -337,7 +337,7 @@ func TestUpgrade(t *testing.T) { serviceAccount := "newServiceAccount" existingDeployment, _ := deployment(&Options{ Namespace: v1.NamespaceDefault, - ImageSpec: "imageToReplace", + ImageSpec: "imageToReplace:v1.0.0", ServiceAccount: "serviceAccountToReplace", UseCanary: false, }) @@ -416,6 +416,66 @@ func TestUpgrade_serviceNotFound(t *testing.T) { } } +func TestUgrade_newerVersion(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + serviceAccount := "newServiceAccount" + existingDeployment,_ := deployment(&Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: "imageToReplace:v100.5.0", + ServiceAccount: "serviceAccountToReplace", + UseCanary: false, + }) + existingService := service(v1.NamespaceDefault) + + fc := &fake.Clientset{} + fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingDeployment, nil + }) + fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + 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: v1.NamespaceDefault, + ImageSpec: image, + ServiceAccount: serviceAccount, + ForceUpgrade: false, + } + if err := Upgrade(fc, opts); err == nil { + t.Errorf("Expected error because the deployed version is newer") + } + + if actions := fc.Actions(); len(actions) != 1 { + t.Errorf("unexpected actions: %v, expected 1 action got %d", actions, len(actions)) + } + + opts = &Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: image, + ServiceAccount: serviceAccount, + ForceUpgrade: true, + } + if err := Upgrade(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 4 { + t.Errorf("unexpected actions: %v, expected 4 action got %d", actions, len(actions)) + } +} + func tlsTestFile(t *testing.T, path string) string { const tlsTestDir = "../../../testdata" path = filepath.Join(tlsTestDir, path) diff --git a/cmd/helm/installer/options.go b/cmd/helm/installer/options.go index 6ea60935a..edff2740f 100644 --- a/cmd/helm/installer/options.go +++ b/cmd/helm/installer/options.go @@ -47,6 +47,9 @@ type Options struct { // ServiceAccount is the Kubernetes service account to add to Tiller. ServiceAccount string + // Force allows to force upgrading tiller if deployed version is greater than current version + ForceUpgrade bool + // ImageSpec indentifies the image Tiller will use when deployed. // // Valid if and only if UseCanary is false. diff --git a/docs/helm/helm_init.md b/docs/helm/helm_init.md index eeee9b013..4696de58f 100644 --- a/docs/helm/helm_init.md +++ b/docs/helm/helm_init.md @@ -36,6 +36,7 @@ helm init --canary-image use the canary Tiller image -c, --client-only if set does not install Tiller --dry-run do not install local or remote + --force-upgrade force upgrade of Tiller to the current helm version --history-max int limit the maximum number of revisions saved per release. Use 0 for no limit. --local-repo-url string URL for local repository (default "http://127.0.0.1:8879/charts") --net-host install Tiller with net=host @@ -68,4 +69,4 @@ helm init ### SEE ALSO * [helm](helm.md) - The Helm package manager for Kubernetes. -###### Auto generated by spf13/cobra on 7-Nov-2017 +###### Auto generated by spf13/cobra on 9-Jan-2018