Commit fa06dd17 authored by Florian Zysset's avatar Florian Zysset Committed by Matthew Fisher

helm init --upgrade will downgrade (#2805)

* Don't downgrade tiller with helm init --upgrade unless --force-upgrade is specified
Fix tests after merging master

* Reformatting with gofmt
parent 70192cda
...@@ -76,6 +76,7 @@ type initCmd struct { ...@@ -76,6 +76,7 @@ type initCmd struct {
upgrade bool upgrade bool
namespace string namespace string
dryRun bool dryRun bool
forceUpgrade bool
skipRefresh bool skipRefresh bool
out io.Writer out io.Writer
home helmpath.Home home helmpath.Home
...@@ -106,6 +107,7 @@ func newInitCmd(out io.Writer) *cobra.Command { ...@@ -106,6 +107,7 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.StringVarP(&i.image, "tiller-image", "i", "", "override Tiller image") 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.canary, "canary-image", false, "use the canary Tiller image")
f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Tiller is already installed") 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.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.dryRun, "dry-run", false, "do not install local or remote")
f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache")
...@@ -164,6 +166,7 @@ func (i *initCmd) run() error { ...@@ -164,6 +166,7 @@ func (i *initCmd) run() error {
i.opts.Namespace = i.namespace i.opts.Namespace = i.namespace
i.opts.UseCanary = i.canary i.opts.UseCanary = i.canary
i.opts.ImageSpec = i.image i.opts.ImageSpec = i.image
i.opts.ForceUpgrade = i.forceUpgrade
i.opts.ServiceAccount = i.serviceAccount i.opts.ServiceAccount = i.serviceAccount
i.opts.MaxHistory = i.maxHistory i.opts.MaxHistory = i.maxHistory
......
...@@ -17,10 +17,12 @@ limitations under the License. ...@@ -17,10 +17,12 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer" package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strings" "strings"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1" "k8s.io/api/extensions/v1beta1"
...@@ -30,6 +32,7 @@ import ( ...@@ -30,6 +32,7 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
"k8s.io/helm/pkg/version"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
) )
...@@ -60,6 +63,10 @@ func Upgrade(client kubernetes.Interface, opts *Options) error { ...@@ -60,6 +63,10 @@ func Upgrade(client kubernetes.Interface, opts *Options) error {
if err != nil { if err != nil {
return err 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].Image = opts.selectImage()
obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy() obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy()
obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount
...@@ -75,6 +82,17 @@ func Upgrade(client kubernetes.Interface, opts *Options) error { ...@@ -75,6 +82,17 @@ func Upgrade(client kubernetes.Interface, opts *Options) error {
return err 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. // createDeployment creates the Tiller Deployment resource.
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
obj, err := deployment(opts) obj, err := deployment(opts)
......
...@@ -337,7 +337,7 @@ func TestUpgrade(t *testing.T) { ...@@ -337,7 +337,7 @@ func TestUpgrade(t *testing.T) {
serviceAccount := "newServiceAccount" serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{ existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault, Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace", ImageSpec: "imageToReplace:v1.0.0",
ServiceAccount: "serviceAccountToReplace", ServiceAccount: "serviceAccountToReplace",
UseCanary: false, UseCanary: false,
}) })
...@@ -416,6 +416,66 @@ func TestUpgrade_serviceNotFound(t *testing.T) { ...@@ -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 { func tlsTestFile(t *testing.T, path string) string {
const tlsTestDir = "../../../testdata" const tlsTestDir = "../../../testdata"
path = filepath.Join(tlsTestDir, path) path = filepath.Join(tlsTestDir, path)
......
...@@ -47,6 +47,9 @@ type Options struct { ...@@ -47,6 +47,9 @@ type Options struct {
// ServiceAccount is the Kubernetes service account to add to Tiller. // ServiceAccount is the Kubernetes service account to add to Tiller.
ServiceAccount string 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. // ImageSpec indentifies the image Tiller will use when deployed.
// //
// Valid if and only if UseCanary is false. // Valid if and only if UseCanary is false.
......
...@@ -36,6 +36,7 @@ helm init ...@@ -36,6 +36,7 @@ helm init
--canary-image use the canary Tiller image --canary-image use the canary Tiller image
-c, --client-only if set does not install Tiller -c, --client-only if set does not install Tiller
--dry-run do not install local or remote --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. --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") --local-repo-url string URL for local repository (default "http://127.0.0.1:8879/charts")
--net-host install Tiller with net=host --net-host install Tiller with net=host
...@@ -68,4 +69,4 @@ helm init ...@@ -68,4 +69,4 @@ helm init
### SEE ALSO ### SEE ALSO
* [helm](helm.md) - The Helm package manager for Kubernetes. * [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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment