Commit 89c78158 authored by Matt Butcher's avatar Matt Butcher

fix(helm): add dry-run flag for init

This adds a --dry-run flag to init, and causes the manifest file for
Tiller to be emitted on --debug. Together, this means you can do a
'helm init --dry-run --debug' and dump the Tiller manifest much as you
can with 'helm install --dry-run --debug'.

This does not require a server round-trip.

Closes #1417
parent 05c04bcc
...@@ -33,7 +33,23 @@ import ( ...@@ -33,7 +33,23 @@ import (
const initDesc = ` const initDesc = `
This command installs Tiller (the helm server side component) onto your 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 ( const (
...@@ -47,6 +63,7 @@ type initCmd struct { ...@@ -47,6 +63,7 @@ type initCmd struct {
image string image string
clientOnly bool clientOnly bool
canary bool canary bool
dryRun bool
out io.Writer out io.Writer
home helmpath.Home home helmpath.Home
kubeClient unversioned.DeploymentsNamespacer kubeClient unversioned.DeploymentsNamespacer
...@@ -74,12 +91,25 @@ func newInitCmd(out io.Writer) *cobra.Command { ...@@ -74,12 +91,25 @@ 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.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")
return cmd return cmd
} }
// runInit initializes local config and installs tiller to Kubernetes Cluster // runInit initializes local config and installs tiller to Kubernetes Cluster
func (i *initCmd) run() error { 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 { if err := ensureHome(i.home, i.out); err != nil {
return err return err
} }
......
...@@ -23,6 +23,8 @@ import ( ...@@ -23,6 +23,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/ghodss/yaml"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned/testclient" "k8s.io/kubernetes/pkg/client/unversioned/testclient"
...@@ -87,7 +89,7 @@ func TestInitCmd_clientOnly(t *testing.T) { ...@@ -87,7 +89,7 @@ func TestInitCmd_clientOnly(t *testing.T) {
fake := testclient.Fake{} fake := testclient.Fake{}
cmd := &initCmd{out: &buf, home: helmpath.Home(home), kubeClient: fake.Extensions(), clientOnly: true} cmd := &initCmd{out: &buf, home: helmpath.Home(home), kubeClient: fake.Extensions(), clientOnly: true}
if err := cmd.run(); err != nil { if err := cmd.run(); err != nil {
t.Errorf("expected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if len(fake.Actions()) != 0 { if len(fake.Actions()) != 0 {
t.Error("expected client call") t.Error("expected client call")
...@@ -97,6 +99,42 @@ func TestInitCmd_clientOnly(t *testing.T) { ...@@ -97,6 +99,42 @@ func TestInitCmd_clientOnly(t *testing.T) {
t.Errorf("expected %q, got %q", expected, buf.String()) 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) { func TestEnsureHome(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home") home, err := ioutil.TempDir("", "helm_home")
if err != nil { if err != nil {
......
...@@ -19,6 +19,8 @@ package installer // import "k8s.io/helm/cmd/helm/installer" ...@@ -19,6 +19,8 @@ package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"fmt" "fmt"
"github.com/ghodss/yaml"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned"
...@@ -36,15 +38,29 @@ const defaultImage = "gcr.io/kubernetes-helm/tiller" ...@@ -36,15 +38,29 @@ const defaultImage = "gcr.io/kubernetes-helm/tiller"
// //
// If verbose is true, this will print the manifest to stdout. // If verbose is true, this will print the manifest to stdout.
func Install(client unversioned.DeploymentsNamespacer, namespace, image string, canary, verbose bool) error { 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 { switch {
case canary: case canary:
image = defaultImage + ":canary" image = defaultImage + ":canary"
case image == "": case image == "":
image = fmt.Sprintf("%s:%s", defaultImage, version.Version) image = fmt.Sprintf("%s:%s", defaultImage, version.Version)
} }
obj := generateDeployment(image) return generateDeployment(image)
_, err := client.Deployments(namespace).Create(obj) }
return err
// 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 { func generateLabels(labels map[string]string) map[string]string {
......
...@@ -20,11 +20,45 @@ import ( ...@@ -20,11 +20,45 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/version"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/unversioned/testclient" "k8s.io/kubernetes/pkg/client/unversioned/testclient"
"k8s.io/kubernetes/pkg/runtime" "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) { func TestInstall(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
......
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