Skip to content

Commit

Permalink
fix(helm): add dry-run flag for init
Browse files Browse the repository at this point in the history
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
  • Loading branch information
technosophos committed Oct 20, 2016
1 parent 05c04bc commit 89c7815
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 5 deletions.
32 changes: 31 additions & 1 deletion cmd/helm/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -47,6 +63,7 @@ type initCmd struct {
image string
clientOnly bool
canary bool
dryRun bool
out io.Writer
home helmpath.Home
kubeClient unversioned.DeploymentsNamespacer
Expand Down Expand Up @@ -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
}
Expand Down
40 changes: 39 additions & 1 deletion cmd/helm/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand All @@ -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 {
Expand Down
22 changes: 19 additions & 3 deletions cmd/helm/installer/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down
34 changes: 34 additions & 0 deletions cmd/helm/installer/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down

0 comments on commit 89c7815

Please sign in to comment.