diff --git a/cmd/helm/init.go b/cmd/helm/init.go index fe8b5c03c..e3949bae8 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -33,7 +33,23 @@ import ( const initDesc = ` This command installs Tiller (the helm server side component) onto your -Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) +Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/) + +As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters +by reading $KUBECONFIG (default '~/.kube/config') and using the default context. + +To set up just a local environment, use '--client-only'. That will configure +$HELM_HOME, but not attempt to connect to a remote cluster and install the Tiller +deployment. + +When installing Tiller, 'helm init' will attempt to install the latest released +version. You can specify an alternative image with '--tiller-image'. For those +frequently working on the latest code, the flag '--canary-image' will install +the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub +repository on the master branch). + +To dump a manifest containing the Tiller deployment YAML, combine the +'--dry-run' and '--debug' flags. ` const ( @@ -47,6 +63,7 @@ type initCmd struct { image string clientOnly bool canary bool + dryRun bool out io.Writer home helmpath.Home kubeClient unversioned.DeploymentsNamespacer @@ -74,12 +91,25 @@ 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.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") return cmd } // runInit initializes local config and installs tiller to Kubernetes Cluster func (i *initCmd) run() error { + + if flagDebug { + m, err := installer.DeploymentManifest(i.image, i.canary) + if err != nil { + return err + } + fmt.Fprintln(i.out, m) + } + if i.dryRun { + return nil + } + if err := ensureHome(i.home, i.out); err != nil { return err } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 5b67ea962..f341d8745 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -23,6 +23,8 @@ import ( "strings" "testing" + "github.com/ghodss/yaml" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/client/unversioned/testclient" @@ -87,7 +89,7 @@ func TestInitCmd_clientOnly(t *testing.T) { fake := testclient.Fake{} cmd := &initCmd{out: &buf, home: helmpath.Home(home), kubeClient: fake.Extensions(), clientOnly: true} if err := cmd.run(); err != nil { - t.Errorf("expected error: %v", err) + t.Errorf("unexpected error: %v", err) } if len(fake.Actions()) != 0 { t.Error("expected client call") @@ -97,6 +99,42 @@ func TestInitCmd_clientOnly(t *testing.T) { t.Errorf("expected %q, got %q", expected, buf.String()) } } + +func TestInitCmd_dryRun(t *testing.T) { + // This is purely defensive in this case. + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + dbg := flagDebug + flagDebug = true + defer func() { + os.Remove(home) + flagDebug = dbg + }() + + var buf bytes.Buffer + fake := testclient.Fake{} + cmd := &initCmd{ + out: &buf, + home: helmpath.Home(home), + kubeClient: fake.Extensions(), + clientOnly: true, + dryRun: true, + } + if err := cmd.run(); err != nil { + t.Fatal(err) + } + if len(fake.Actions()) != 0 { + t.Error("expected no server calls") + } + + var y map[string]interface{} + if err := yaml.Unmarshal(buf.Bytes(), &y); err != nil { + t.Errorf("Expected parseable YAML, got %q\n\t%s", buf.String(), err) + } +} + func TestEnsureHome(t *testing.T) { home, err := ioutil.TempDir("", "helm_home") if err != nil { diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index c8dffdb75..e84aece71 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -19,6 +19,8 @@ package installer // import "k8s.io/helm/cmd/helm/installer" import ( "fmt" + "github.com/ghodss/yaml" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/unversioned" @@ -36,15 +38,29 @@ const defaultImage = "gcr.io/kubernetes-helm/tiller" // // If verbose is true, this will print the manifest to stdout. func Install(client unversioned.DeploymentsNamespacer, namespace, image string, canary, verbose bool) error { + obj := deployment(image, canary) + _, err := client.Deployments(namespace).Create(obj) + return err +} + +// deployment gets the deployment object that installs Tiller. +func deployment(image string, canary bool) *extensions.Deployment { switch { case canary: image = defaultImage + ":canary" case image == "": image = fmt.Sprintf("%s:%s", defaultImage, version.Version) } - obj := generateDeployment(image) - _, err := client.Deployments(namespace).Create(obj) - return err + return generateDeployment(image) +} + +// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment +// resource. +func DeploymentManifest(image string, canary bool) (string, error) { + obj := deployment(image, canary) + + buf, err := yaml.Marshal(obj) + return string(buf), err } func generateLabels(labels map[string]string) map[string]string { diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index 64bd9a784..00a02eb64 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -20,11 +20,45 @@ import ( "reflect" "testing" + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/version" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/unversioned/testclient" "k8s.io/kubernetes/pkg/runtime" ) +func TestDeploymentManifest(t *testing.T) { + + tests := []struct { + name string + image string + canary bool + expect string + }{ + {"default", "", false, "gcr.io/kubernetes-helm/tiller:" + version.Version}, + {"canary", "example.com/tiller", true, "gcr.io/kubernetes-helm/tiller:canary"}, + {"custom", "example.com/tiller:latest", false, "example.com/tiller:latest"}, + } + + for _, tt := range tests { + + o, err := DeploymentManifest(tt.image, tt.canary) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + var dep extensions.Deployment + if err := yaml.Unmarshal([]byte(o), &dep); err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + + if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect { + t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got) + } + } +} + func TestInstall(t *testing.T) { image := "gcr.io/kubernetes-helm/tiller:v2.0.0"