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 (
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
}
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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"
......
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