diff --git a/cmd/helm/init.go b/cmd/helm/init.go index bb9706414..5e8c47dc8 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -24,7 +24,7 @@ import ( "github.com/spf13/cobra" kerrors "k8s.io/kubernetes/pkg/api/errors" - extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/helm/cmd/helm/helmpath" "k8s.io/helm/cmd/helm/installer" @@ -70,7 +70,7 @@ type initCmd struct { dryRun bool out io.Writer home helmpath.Home - kubeClient extensionsclient.DeploymentsGetter + kubeClient internalclientset.Interface } func newInitCmd(out io.Writer) *cobra.Command { @@ -105,11 +105,16 @@ func newInitCmd(out io.Writer) *cobra.Command { // runInit initializes local config and installs tiller to Kubernetes Cluster func (i *initCmd) run() error { if flagDebug { - m, err := installer.DeploymentManifest(i.namespace, i.image, i.canary) + dm, err := installer.DeploymentManifest(i.namespace, i.image, i.canary) if err != nil { return err } - fmt.Fprintln(i.out, m) + fmt.Fprintln(i.out, dm) + sm, err := installer.ServiceManifest(i.namespace) + if err != nil { + return err + } + fmt.Fprintln(i.out, sm) } if i.dryRun { diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 292018993..00870e23b 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -47,15 +47,21 @@ func TestInitCmd(t *testing.T) { cmd := &initCmd{ out: &buf, home: helmpath.Home(home), - kubeClient: fc.Extensions(), + kubeClient: fc, namespace: api.NamespaceDefault, } if err := cmd.run(); err != nil { t.Errorf("expected error: %v", err) } - action := fc.Actions()[0] - if !action.Matches("create", "deployments") { - t.Errorf("unexpected action: %v, expected create deployment", action) + actions := fc.Actions() + if len(actions) != 2 { + t.Errorf("Expected 2 actions, got %d", len(actions)) + } + if !actions[0].Matches("create", "deployments") { + t.Errorf("unexpected action: %v, expected create deployment", actions[0]) + } + if !actions[1].Matches("create", "services") { + t.Errorf("unexpected action: %v, expected create service", actions[1]) } expected := "Tiller (the helm server side component) has been installed into your Kubernetes Cluster." if !strings.Contains(buf.String(), expected) { @@ -63,7 +69,7 @@ func TestInitCmd(t *testing.T) { } } -func TestInitCmd_exsits(t *testing.T) { +func TestInitCmd_exists(t *testing.T) { home, err := ioutil.TempDir("", "helm_home") if err != nil { t.Fatal(err) @@ -83,7 +89,7 @@ func TestInitCmd_exsits(t *testing.T) { cmd := &initCmd{ out: &buf, home: helmpath.Home(home), - kubeClient: fc.Extensions(), + kubeClient: fc, namespace: api.NamespaceDefault, } if err := cmd.run(); err != nil { @@ -108,7 +114,7 @@ func TestInitCmd_clientOnly(t *testing.T) { cmd := &initCmd{ out: &buf, home: helmpath.Home(home), - kubeClient: fc.Extensions(), + kubeClient: fc, clientOnly: true, namespace: api.NamespaceDefault, } @@ -142,7 +148,7 @@ func TestInitCmd_dryRun(t *testing.T) { cmd := &initCmd{ out: &buf, home: helmpath.Home(home), - kubeClient: fc.Extensions(), + kubeClient: fc, clientOnly: true, dryRun: true, namespace: api.NamespaceDefault, diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index 06856fcdc..cd3a357c7 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -22,7 +22,10 @@ import ( "github.com/ghodss/yaml" "k8s.io/kubernetes/pkg/api" + kerrors "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion" "k8s.io/kubernetes/pkg/util/intstr" @@ -37,22 +40,46 @@ const defaultImage = "gcr.io/kubernetes-helm/tiller" // command failed. // // If verbose is true, this will print the manifest to stdout. -func Install(client extensionsclient.DeploymentsGetter, namespace, image string, canary, verbose bool) error { - obj := deployment(namespace, image, canary) - _, err := client.Deployments(obj.Namespace).Create(obj) - return err +func Install(client internalclientset.Interface, namespace, image string, canary, verbose bool) error { + if err := createDeployment(client.Extensions(), namespace, image, canary); err != nil { + return err + } + if err := createService(client.Core(), namespace); err != nil { + return err + } + return nil } +// // Upgrade uses kubernetes client to upgrade tiller to current version // // Returns an error if the command failed. -func Upgrade(client extensionsclient.DeploymentsGetter, namespace, image string, canary bool) error { - obj, err := client.Deployments(namespace).Get("tiller-deploy") +func Upgrade(client internalclientset.Interface, namespace, image string, canary bool) error { + obj, err := client.Extensions().Deployments(namespace).Get("tiller-deploy") if err != nil { return err } obj.Spec.Template.Spec.Containers[0].Image = selectImage(image, canary) - _, err = client.Deployments(namespace).Update(obj) + if _, err := client.Extensions().Deployments(namespace).Update(obj); err != nil { + return err + } + // If the service does not exists that would mean we are upgrading from a tiller version + // that didn't deploy the service, so install it. + if _, err := client.Core().Services(namespace).Get("tiller-deploy"); err != nil { + if !kerrors.IsNotFound(err) { + return err + } + if err := createService(client.Core(), namespace); err != nil { + return err + } + } + return nil +} + +// createDeployment creates the Tiller deployment reource +func createDeployment(client extensionsclient.DeploymentsGetter, namespace, image string, canary bool) error { + obj := deployment(namespace, image, canary) + _, err := client.Deployments(obj.Namespace).Create(obj) return err } @@ -61,6 +88,18 @@ func deployment(namespace, image string, canary bool) *extensions.Deployment { return generateDeployment(namespace, selectImage(image, canary)) } +// createService creates the Tiller service resource +func createService(client internalversion.ServicesGetter, namespace string) error { + obj := service(namespace) + _, err := client.Services(obj.Namespace).Create(obj) + return err +} + +// service gets the service object that installs Tiller. +func service(namespace string) *api.Service { + return generateService(namespace) +} + func selectImage(image string, canary bool) string { switch { case canary: @@ -80,6 +119,15 @@ func DeploymentManifest(namespace, image string, canary bool) (string, error) { return string(buf), err } +// ServiceManifest gets the manifest (as a string) that describes the Tiller Service +// resource. +func ServiceManifest(namespace string) (string, error) { + obj := service(namespace) + + buf, err := yaml.Marshal(obj) + return string(buf), err +} + func generateLabels(labels map[string]string) map[string]string { labels["app"] = "helm" return labels @@ -139,3 +187,26 @@ func generateDeployment(namespace, image string) *extensions.Deployment { } return d } + +func generateService(namespace string) *api.Service { + labels := generateLabels(map[string]string{"name": "tiller"}) + s := &api.Service{ + ObjectMeta: api.ObjectMeta{ + Namespace: namespace, + Name: "tiller-deploy", + Labels: labels, + }, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{ + { + Name: "tiller", + Port: 44134, + TargetPort: intstr.FromString("tiller"), + }, + }, + Selector: labels, + }, + } + return s +} diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index 4e8715b18..4244cd828 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -22,6 +22,7 @@ import ( "github.com/ghodss/yaml" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" testcore "k8s.io/kubernetes/pkg/client/testing/core" @@ -64,6 +65,21 @@ func TestDeploymentManifest(t *testing.T) { } } +func TestServiceManifest(t *testing.T) { + o, err := ServiceManifest(api.NamespaceDefault) + if err != nil { + t.Fatalf("error %q", err) + } + var svc api.Service + if err := yaml.Unmarshal([]byte(o), &svc); err != nil { + t.Fatalf("error %q", err) + } + + if got := svc.ObjectMeta.Namespace; got != api.NamespaceDefault { + t.Errorf("expected namespace %s, got %s", api.NamespaceDefault, got) + } +} + func TestInstall(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" @@ -80,13 +96,25 @@ func TestInstall(t *testing.T) { } return true, obj, nil }) + fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*api.Service) + l := obj.GetLabels() + if reflect.DeepEqual(l, map[string]string{"app": "helm"}) { + t.Errorf("expected labels = '', got '%s'", l) + } + n := obj.ObjectMeta.Namespace + if n != api.NamespaceDefault { + t.Errorf("expected namespace = '%s', got '%s'", api.NamespaceDefault, n) + } + return true, obj, nil + }) - if err := Install(fc.Extensions(), api.NamespaceDefault, image, false, false); err != nil { + if err := Install(fc, api.NamespaceDefault, image, false, false); err != nil { t.Errorf("unexpected error: %#+v", err) } - if actions := fc.Actions(); len(actions) != 1 { - t.Errorf("unexpected actions: %v, expected 1 actions got %d", actions, len(actions)) + if actions := fc.Actions(); len(actions) != 2 { + t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions)) } } @@ -100,13 +128,17 @@ func TestInstall_canary(t *testing.T) { } return true, obj, nil }) + fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*api.Service) + return true, obj, nil + }) - if err := Install(fc.Extensions(), api.NamespaceDefault, "", true, false); err != nil { + if err := Install(fc, api.NamespaceDefault, "", true, false); err != nil { t.Errorf("unexpected error: %#+v", err) } - if actions := fc.Actions(); len(actions) != 1 { - t.Errorf("unexpected actions: %v, expected 1 actions got %d", actions, len(actions)) + if actions := fc.Actions(); len(actions) != 2 { + t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions)) } } @@ -114,6 +146,7 @@ func TestUpgrade(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0" existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) + existingService := service(api.NamespaceDefault) fc := &fake.Clientset{} fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { @@ -127,12 +160,53 @@ func TestUpgrade(t *testing.T) { } return true, obj, nil }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingService, nil + }) - if err := Upgrade(fc.Extensions(), api.NamespaceDefault, image, false); err != nil { + if err := Upgrade(fc, api.NamespaceDefault, image, false); err != nil { t.Errorf("unexpected error: %#+v", err) } - if actions := fc.Actions(); len(actions) != 2 { - t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions)) + if actions := fc.Actions(); len(actions) != 3 { + t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions)) + } +} + +func TestUpgrade_serviceNotFound(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + + existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false) + + 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().(*extensions.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + return true, obj, nil + }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, nil, errors.NewNotFound(api.Resource("services"), "1") + }) + fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*api.Service) + n := obj.ObjectMeta.Namespace + if n != api.NamespaceDefault { + t.Errorf("expected namespace = '%s', got '%s'", api.NamespaceDefault, n) + } + return true, obj, nil + }) + + if err := Upgrade(fc, api.NamespaceDefault, image, false); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 4 { + t.Errorf("unexpected actions: %v, expected 4 actions got %d", actions, len(actions)) } }