diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index c6bfcdf22..2fdb00e7d 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -32,6 +32,7 @@ import ( "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/helm/cmd/helm/installer" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/portforwarder" @@ -56,10 +57,11 @@ var ( ) var ( - helmHome string - tillerHost string - tillerNamespace string - kubeContext string + helmHome string + tillerHost string + usesHostedTiller bool + tillerNamespace string + kubeContext string // TODO refactor out this global var tillerTunnel *kube.Tunnel ) @@ -182,14 +184,22 @@ func setupConnection(c *cobra.Command, args []string) error { return err } - tunnel, err := portforwarder.New(tillerNamespace, client, config) - if err != nil { + if externalName, err := installer.GetTillerExternalName(client, tillerNamespace); err != nil { return err - } - - tillerHost = fmt.Sprintf("localhost:%d", tunnel.Local) - if flagDebug { - fmt.Printf("Created tunnel using local port: '%d'\n", tunnel.Local) + } else if externalName != "" { + tillerHost = externalName + usesHostedTiller = true + } else { + tunnel, err := portforwarder.New(tillerNamespace, client, config) + if err != nil { + return err + } + tillerTunnel = tunnel + + tillerHost = fmt.Sprintf("localhost:%d", tunnel.Local) + if flagDebug { + fmt.Printf("Created tunnel using local port: '%d'\n", tunnel.Local) + } } } diff --git a/cmd/helm/init.go b/cmd/helm/init.go index be3d05528..3f71ea2dd 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -236,10 +236,16 @@ func (i *initCmd) run() error { return fmt.Errorf("error installing: %s", err) } if i.upgrade { - if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { - return fmt.Errorf("error when upgrading: %s", err) + if externalName, err := installer.GetTillerExternalName(i.kubeClient, i.opts.Namespace); err != nil { + return fmt.Errorf("error detecting tiller mode: %s", err) + } else if externalName != "" { + fmt.Fprintf(i.out, "\nThis cluster uses hosted Tiller (the helm server side component) %v. Contact cluster operators regarding upgrading Tiller.\n", externalName) + } else { + if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { + return fmt.Errorf("error when upgrading: %s", err) + } + fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.") } - fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.") } else { fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+ "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index f6f93fed9..4715e3c1c 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -58,11 +58,11 @@ func TestInitCmd(t *testing.T) { 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[0].Matches("create", "services") { + t.Errorf("unexpected action: %v, expected create service", actions[0]) } - if !actions[1].Matches("create", "services") { - t.Errorf("unexpected action: %v, expected create service", actions[1]) + if !actions[1].Matches("create", "deployments") { + t.Errorf("unexpected action: %v, expected create deployment", actions[1]) } expected := "Tiller (the helm server side component) has been installed into your Kubernetes Cluster." if !strings.Contains(buf.String(), expected) { diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index 12f9b903a..a230faccf 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -17,6 +17,7 @@ limitations under the License. package installer // import "k8s.io/helm/cmd/helm/installer" import ( + "errors" "io/ioutil" "github.com/ghodss/yaml" @@ -34,10 +35,11 @@ import ( // // Returns an error if the command failed. func Install(client internalclientset.Interface, opts *Options) error { - if err := createDeployment(client.Extensions(), opts); err != nil { + // create service first. If cluster is using hosted Tiller, this will error out and actual deployment will not be created. + if err := createService(client.Core(), opts.Namespace); err != nil { return err } - if err := createService(client.Core(), opts.Namespace); err != nil { + if err := createDeployment(client.Extensions(), opts); err != nil { return err } if opts.tls() { @@ -89,6 +91,19 @@ func createService(client internalversion.ServicesGetter, namespace string) erro return err } +// GetTillerExternalName returns the configured external name of a hosted Tiller server. +func GetTillerExternalName(client internalclientset.Interface, namespace string) (string, error) { + if svc, err := client.Core().Services(namespace).Get("tiller-deploy"); err != nil { + return "", err + } else if svc.Spec.Type == api.ServiceTypeExternalName { + if svc.Spec.ExternalName == "" { + return "", errors.New("Missing external name of hosted Tiller") + } + return svc.Spec.ExternalName, nil + } + return "", nil +} + // service gets the service object that installs Tiller. func service(namespace string) *api.Service { return generateService(namespace)