ref(cmd): remove reset and init tiller commands

pull/3945/head
Adam Reese 7 years ago
parent 6bca9686a1
commit 9409adfa8d
No known key found for this signature in database
GPG Key ID: 06F35E60A7A18DD6

@ -72,9 +72,9 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command {
cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision")
cmd.AddCommand(addFlagsTLS(newGetValuesCmd(nil, out)))
cmd.AddCommand(addFlagsTLS(newGetManifestCmd(nil, out)))
cmd.AddCommand(addFlagsTLS(newGetHooksCmd(nil, out)))
cmd.AddCommand(newGetValuesCmd(nil, out))
cmd.AddCommand(newGetManifestCmd(nil, out))
cmd.AddCommand(newGetHooksCmd(nil, out))
return cmd
}

@ -28,28 +28,17 @@ import (
"google.golang.org/grpc/status"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
// Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/helm/pkg/helm"
helm_env "k8s.io/helm/pkg/helm/environment"
"k8s.io/helm/pkg/helm/portforwarder"
"k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/tlsutil"
)
var (
tlsCaCertFile string // path to TLS CA certificate file
tlsCertFile string // path to TLS certificate file
tlsKeyFile string // path to TLS key file
tlsVerify bool // enable TLS and verify remote certificates
tlsEnable bool // enable TLS
tlsCaCertDefault = "$HELM_HOME/ca.pem"
tlsCertDefault = "$HELM_HOME/cert.pem"
tlsKeyDefault = "$HELM_HOME/key.pem"
tillerTunnel *kube.Tunnel
settings helm_env.EnvSettings
)
@ -84,11 +73,6 @@ func newRootCmd(args []string) *cobra.Command {
Short: "The Helm package manager for Kubernetes.",
Long: globalUsage,
SilenceUsage: true,
PersistentPreRun: func(*cobra.Command, []string) {
tlsCaCertFile = os.ExpandEnv(tlsCaCertFile)
tlsCertFile = os.ExpandEnv(tlsCertFile)
tlsKeyFile = os.ExpandEnv(tlsKeyFile)
},
PersistentPostRun: func(*cobra.Command, []string) {
teardown()
},
@ -113,18 +97,17 @@ func newRootCmd(args []string) *cobra.Command {
newVerifyCmd(out),
// release commands
addFlagsTLS(newDeleteCmd(nil, out)),
addFlagsTLS(newGetCmd(nil, out)),
addFlagsTLS(newHistoryCmd(nil, out)),
addFlagsTLS(newInstallCmd(nil, out)),
addFlagsTLS(newListCmd(nil, out)),
addFlagsTLS(newRollbackCmd(nil, out)),
addFlagsTLS(newStatusCmd(nil, out)),
addFlagsTLS(newUpgradeCmd(nil, out)),
addFlagsTLS(newReleaseTestCmd(nil, out)),
addFlagsTLS(newResetCmd(nil, out)),
addFlagsTLS(newVersionCmd(nil, out)),
newDeleteCmd(nil, out),
newGetCmd(nil, out),
newHistoryCmd(nil, out),
newInstallCmd(nil, out),
newListCmd(nil, out),
newRollbackCmd(nil, out),
newStatusCmd(nil, out),
newUpgradeCmd(nil, out),
newReleaseTestCmd(nil, out),
newVersionCmd(nil, out),
newCompletionCmd(out),
newHomeCmd(out),
@ -244,21 +227,6 @@ func getKubeClient(context string) (*rest.Config, kubernetes.Interface, error) {
return config, client, nil
}
// getInternalKubeClient creates a Kubernetes config and an "internal" client for a given kubeconfig context.
//
// Prefer the similar getKubeClient if you don't need to use such an internal client.
func getInternalKubeClient(context string) (internalclientset.Interface, error) {
config, err := configForContext(context)
if err != nil {
return nil, err
}
client, err := internalclientset.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("could not get Kubernetes client: %s", err)
}
return client, nil
}
// ensureHelmClient returns a new helm client impl. if h is not nil.
func ensureHelmClient(h helm.Interface) helm.Interface {
if h != nil {
@ -269,42 +237,5 @@ func ensureHelmClient(h helm.Interface) helm.Interface {
func newClient() helm.Interface {
options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)}
if tlsVerify || tlsEnable {
if tlsCaCertFile == "" {
tlsCaCertFile = settings.Home.TLSCaCert()
}
if tlsCertFile == "" {
tlsCertFile = settings.Home.TLSCert()
}
if tlsKeyFile == "" {
tlsKeyFile = settings.Home.TLSKey()
}
debug("Key=%q, Cert=%q, CA=%q\n", tlsKeyFile, tlsCertFile, tlsCaCertFile)
tlsopts := tlsutil.Options{KeyFile: tlsKeyFile, CertFile: tlsCertFile, InsecureSkipVerify: true}
if tlsVerify {
tlsopts.CaCertFile = tlsCaCertFile
tlsopts.InsecureSkipVerify = false
}
tlscfg, err := tlsutil.ClientConfig(tlsopts)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
options = append(options, helm.WithTLS(tlscfg))
}
return helm.NewClient(options...)
}
// addFlagsTLS adds the flags for supporting client side TLS to the
// helm command (only those that invoke communicate to Tiller.)
func addFlagsTLS(cmd *cobra.Command) *cobra.Command {
// add flags
cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file")
cmd.Flags().StringVar(&tlsCertFile, "tls-cert", tlsCertDefault, "path to TLS certificate file")
cmd.Flags().StringVar(&tlsKeyFile, "tls-key", tlsKeyDefault, "path to TLS key file")
cmd.Flags().BoolVar(&tlsVerify, "tls-verify", false, "enable TLS for request and verify remote")
cmd.Flags().BoolVar(&tlsEnable, "tls", false, "enable TLS for request")
return cmd
}

@ -17,24 +17,15 @@ limitations under the License.
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"time"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/getter"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/helm/portforwarder"
"k8s.io/helm/pkg/repo"
)
@ -73,23 +64,9 @@ var (
)
type initCmd struct {
image string
clientOnly bool
canary bool
upgrade bool
namespace string
dryRun bool
forceUpgrade bool
skipRefresh bool
out io.Writer
client helm.Interface
home helmpath.Home
opts installer.Options
kubeClient kubernetes.Interface
serviceAccount string
maxHistory int
replicas int
wait bool
}
func newInitCmd(out io.Writer) *cobra.Command {
@ -97,185 +74,27 @@ func newInitCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "initialize Helm on both client and server",
Short: "initialize Helm client",
Long: initDesc,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return errors.New("This command does not accept arguments")
}
i.namespace = settings.TillerNamespace
i.home = settings.Home
i.client = ensureHelmClient(i.client)
return i.run()
},
}
f := cmd.Flags()
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.upgrade, "upgrade", false, "upgrade if Tiller is already installed")
f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "force upgrade of Tiller to the current helm version")
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")
f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache")
f.BoolVar(&i.wait, "wait", false, "block until Tiller is running and ready to receive requests")
f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled")
f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates")
f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller")
f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller")
f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate")
f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository")
f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository")
f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host")
f.StringVar(&i.serviceAccount, "service-account", "", "name of service account")
f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.")
f.IntVar(&i.replicas, "replicas", 1, "amount of tiller instances to run on the cluster")
f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)")
f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)")
f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)")
return cmd
}
// tlsOptions sanitizes the tls flags as well as checks for the existence of required
// tls files indicated by those flags, if any.
func (i *initCmd) tlsOptions() error {
i.opts.EnableTLS = tlsEnable || tlsVerify
i.opts.VerifyTLS = tlsVerify
if i.opts.EnableTLS {
missing := func(file string) bool {
_, err := os.Stat(file)
return os.IsNotExist(err)
}
if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) {
return errors.New("missing required TLS key file")
}
if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) {
return errors.New("missing required TLS certificate file")
}
if i.opts.VerifyTLS {
if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) {
return errors.New("missing required TLS CA file")
}
}
}
return nil
}
// run initializes local config and installs Tiller to Kubernetes cluster.
func (i *initCmd) run() error {
if err := i.tlsOptions(); err != nil {
return err
}
i.opts.Namespace = i.namespace
i.opts.UseCanary = i.canary
i.opts.ImageSpec = i.image
i.opts.ForceUpgrade = i.forceUpgrade
i.opts.ServiceAccount = i.serviceAccount
i.opts.MaxHistory = i.maxHistory
i.opts.Replicas = i.replicas
writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error {
w := i.out
if !first {
// YAML starting document boundary marker
if _, err := fmt.Fprintln(w, "---"); err != nil {
return err
}
}
if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil {
return err
}
if _, err := fmt.Fprintln(w, "kind:", kind); err != nil {
return err
}
if _, err := fmt.Fprint(w, body); err != nil {
return err
}
if !last {
return nil
}
// YAML ending document boundary marker
_, err := fmt.Fprintln(w, "...")
return err
}
if len(i.opts.Output) > 0 {
var body string
var err error
const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",`
if body, err = installer.DeploymentManifest(&i.opts); err != nil {
return err
}
switch i.opts.Output.String() {
case "json":
var out bytes.Buffer
jsonb, err := yaml.ToJSON([]byte(body))
if err != nil {
return err
}
buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1))
buf.WriteString(tm)
// Drop the opening object delimiter ('{').
buf.Write(jsonb[1:])
if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil {
return err
}
if _, err = i.out.Write(out.Bytes()); err != nil {
return err
}
return nil
case "yaml":
if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil {
return err
}
return nil
default:
return fmt.Errorf("unknown output format: %q", i.opts.Output)
}
}
if settings.Debug {
var body string
var err error
// write Deployment manifest
if body, err = installer.DeploymentManifest(&i.opts); err != nil {
return err
}
if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil {
return err
}
// write Service manifest
if body, err = installer.ServiceManifest(i.namespace); err != nil {
return err
}
if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil {
return err
}
// write Secret manifest
if i.opts.EnableTLS {
if body, err = installer.SecretManifest(&i.opts); err != nil {
return err
}
if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil {
return err
}
}
}
if i.dryRun {
return nil
}
if err := ensureDirectories(i.home, i.out); err != nil {
return err
}
@ -286,37 +105,6 @@ func (i *initCmd) run() error {
return err
}
fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", settings.Home)
if !i.clientOnly {
if i.kubeClient == nil {
_, c, err := getKubeClient(settings.KubeContext)
if err != nil {
return fmt.Errorf("could not get kubernetes client: %s", err)
}
i.kubeClient = c
}
if err := installer.Install(i.kubeClient, &i.opts); err != nil {
if !apierrors.IsAlreadyExists(err) {
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)
}
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.)")
}
} else {
fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.\n\n"+
"Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+
"For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation")
}
} else {
fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set")
}
fmt.Fprintln(i.out, "Happy Helming!")
return nil
}
@ -428,37 +216,5 @@ func ensureRepoFileFormat(file string, out io.Writer) error {
return err
}
}
return nil
}
// watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we
// want to wait before we call New().
//
// Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false.
func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64) bool {
deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C
checkTillerPodTicker := time.NewTicker(500 * time.Millisecond)
doneChan := make(chan bool)
defer checkTillerPodTicker.Stop()
go func() {
for range checkTillerPodTicker.C {
_, err := portforwarder.GetTillerPodName(client.CoreV1(), namespace)
if err == nil {
doneChan <- true
break
}
}
}()
for {
select {
case <-deadlinePollingChan:
return false
case <-doneChan:
return true
}
}
}

@ -20,165 +20,11 @@ import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
testcore "k8s.io/client-go/testing"
"encoding/json"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/helm/helmpath"
)
func TestInitCmd(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset()
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc,
namespace: v1.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("expected error: %v", err)
}
actions := fc.Actions()
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[1].Matches("create", "services") {
t.Errorf("unexpected action: %v, expected create service", actions[1])
}
expected := "Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster."
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
}
func TestInitCmd_exists(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset(&v1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: v1.NamespaceDefault,
Name: "tiller-deploy",
},
})
fc.PrependReactor("*", "*", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewAlreadyExists(v1.Resource("deployments"), "1")
})
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc,
namespace: v1.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("expected error: %v", err)
}
expected := "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.)"
if !strings.Contains(buf.String(), expected) {
t.Errorf("expected %q, got %q", expected, buf.String())
}
}
func TestInitCmd_clientOnly(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(home)
var buf bytes.Buffer
fc := fake.NewSimpleClientset()
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc,
clientOnly: true,
namespace: v1.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(fc.Actions()) != 0 {
t.Error("expected client call")
}
expected := "Not installing Tiller due to 'client-only' flag having been set"
if !strings.Contains(buf.String(), expected) {
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)
}
cleanup := resetEnv()
defer func() {
os.Remove(home)
cleanup()
}()
settings.Debug = true
var buf bytes.Buffer
fc := fake.NewSimpleClientset()
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc,
clientOnly: true,
dryRun: true,
namespace: v1.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Fatal(err)
}
if got := len(fc.Actions()); got != 0 {
t.Errorf("expected no server calls, got %d", got)
}
docs := bytes.Split(buf.Bytes(), []byte("\n---"))
if got, want := len(docs), 2; got != want {
t.Fatalf("Expected document count of %d, got %d", want, got)
}
for _, doc := range docs {
var y map[string]interface{}
if err := yaml.Unmarshal(doc, &y); err != nil {
t.Errorf("Expected parseable YAML, got %q\n\t%s", doc, err)
}
}
}
func TestEnsureHome(t *testing.T) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
@ -223,135 +69,3 @@ func TestEnsureHome(t *testing.T) {
t.Errorf("%s should not be a directory", fi)
}
}
func TestInitCmd_tlsOptions(t *testing.T) {
const testDir = "../../testdata"
// tls certificates in testDir
var (
testCaCertFile = filepath.Join(testDir, "ca.pem")
testCertFile = filepath.Join(testDir, "crt.pem")
testKeyFile = filepath.Join(testDir, "key.pem")
)
// these tests verify the effects of permuting the "--tls" and "--tls-verify" flags
// and the install options yieled as a result of (*initCmd).tlsOptions()
// during helm init.
var tests = []struct {
certFile string
keyFile string
caFile string
enable bool
verify bool
describe string
}{
{ // --tls and --tls-verify specified (--tls=true,--tls-verify=true)
certFile: testCertFile,
keyFile: testKeyFile,
caFile: testCaCertFile,
enable: true,
verify: true,
describe: "--tls and --tls-verify specified (--tls=true,--tls-verify=true)",
},
{ // --tls-verify implies --tls (--tls=false,--tls-verify=true)
certFile: testCertFile,
keyFile: testKeyFile,
caFile: testCaCertFile,
enable: false,
verify: true,
describe: "--tls-verify implies --tls (--tls=false,--tls-verify=true)",
},
{ // no --tls-verify (--tls=true,--tls-verify=false)
certFile: testCertFile,
keyFile: testKeyFile,
caFile: "",
enable: true,
verify: false,
describe: "no --tls-verify (--tls=true,--tls-verify=false)",
},
{ // tls is disabled (--tls=false,--tls-verify=false)
certFile: "",
keyFile: "",
caFile: "",
enable: false,
verify: false,
describe: "tls is disabled (--tls=false,--tls-verify=false)",
},
}
for _, tt := range tests {
// emulate tls file specific flags
tlsCaCertFile, tlsCertFile, tlsKeyFile = tt.caFile, tt.certFile, tt.keyFile
// emulate tls enable/verify flags
tlsEnable, tlsVerify = tt.enable, tt.verify
cmd := &initCmd{}
if err := cmd.tlsOptions(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// expected result options
expect := installer.Options{
TLSCaCertFile: tt.caFile,
TLSCertFile: tt.certFile,
TLSKeyFile: tt.keyFile,
VerifyTLS: tt.verify,
EnableTLS: tt.enable || tt.verify,
}
if !reflect.DeepEqual(cmd.opts, expect) {
t.Errorf("%s: got %#+v, want %#+v", tt.describe, cmd.opts, expect)
}
}
}
// TestInitCmd_output tests that init -o formats are unmarshal-able
func TestInitCmd_output(t *testing.T) {
// This is purely defensive in this case.
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
dbg := settings.Debug
settings.Debug = true
defer func() {
os.Remove(home)
settings.Debug = dbg
}()
fc := fake.NewSimpleClientset()
tests := []struct {
expectF func([]byte, interface{}) error
expectName string
}{
{
json.Unmarshal,
"json",
},
{
yaml.Unmarshal,
"yaml",
},
}
for _, s := range tests {
var buf bytes.Buffer
cmd := &initCmd{
out: &buf,
home: helmpath.Home(home),
kubeClient: fc,
opts: installer.Options{Output: installer.OutputFormat(s.expectName)},
namespace: v1.NamespaceDefault,
}
if err := cmd.run(); err != nil {
t.Fatal(err)
}
if got := len(fc.Actions()); got != 0 {
t.Errorf("expected no server calls, got %d", got)
}
d := &v1beta1.Deployment{}
if err = s.expectF(buf.Bytes(), &d); err != nil {
t.Errorf("error unmarshalling init %s output %s %s", s.expectName, err, buf.String())
}
}
}

@ -1,374 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"github.com/Masterminds/semver"
"github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1"
"k8s.io/helm/pkg/version"
"k8s.io/helm/pkg/chartutil"
)
// Install uses Kubernetes client to install Tiller.
//
// Returns an error if the command failed.
func Install(client kubernetes.Interface, opts *Options) error {
if err := createDeployment(client.ExtensionsV1beta1(), opts); err != nil {
return err
}
if err := createService(client.CoreV1(), opts.Namespace); err != nil {
return err
}
if opts.tls() {
if err := createSecret(client.CoreV1(), opts); err != nil {
return err
}
}
return nil
}
// Upgrade uses Kubernetes client to upgrade Tiller to current version.
//
// Returns an error if the command failed.
func Upgrade(client kubernetes.Interface, opts *Options) error {
obj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{})
if err != nil {
return err
}
tillerImage := obj.Spec.Template.Spec.Containers[0].Image
if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade {
return errors.New("current Tiller version is newer, use --force-upgrade to downgrade")
}
obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage()
obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy()
obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount
if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil {
return err
}
// If the service does not exists that would mean we are upgrading from a Tiller version
// that didn't deploy the service, so install it.
_, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return createService(client.CoreV1(), opts.Namespace)
}
return err
}
// semverCompare returns whether the client's version is older, equal or newer than the given image's version.
func semverCompare(image string) int {
split := strings.Split(image, ":")
if len(split) < 2 {
// If we don't know the version, we consider the client version newer.
return 1
}
tillerVersion, err := semver.NewVersion(split[1])
if err != nil {
// same thing with unparsable tiller versions (e.g. canary releases).
return 1
}
clientVersion, err := semver.NewVersion(version.Version)
if err != nil {
// aaaaaand same thing with unparsable helm versions (e.g. canary releases).
return 1
}
return clientVersion.Compare(tillerVersion)
}
// createDeployment creates the Tiller Deployment resource.
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
obj, err := deployment(opts)
if err != nil {
return err
}
_, err = client.Deployments(obj.Namespace).Create(obj)
return err
}
// deployment gets the deployment object that installs Tiller.
func deployment(opts *Options) (*v1beta1.Deployment, error) {
return generateDeployment(opts)
}
// createService creates the Tiller service resource
func createService(client corev1.ServicesGetter, namespace string) error {
obj := service(namespace)
_, err := client.Services(obj.Namespace).Create(obj)
return err
}
// service gets the service object that installs Tiller.
func service(namespace string) *v1.Service {
return generateService(namespace)
}
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
// resource.
func DeploymentManifest(opts *Options) (string, error) {
obj, err := deployment(opts)
if err != nil {
return "", err
}
buf, err := yaml.Marshal(obj)
return string(buf), err
}
// ServiceManifest gets the manifest (as a string) that describes the Tiller Service
// resource.
func ServiceManifest(namespace string) (string, error) {
obj := service(namespace)
buf, err := yaml.Marshal(obj)
return string(buf), err
}
func generateLabels(labels map[string]string) map[string]string {
labels["app"] = "helm"
return labels
}
// parseNodeSelectors parses a comma delimited list of key=values pairs into a map.
func parseNodeSelectorsInto(labels string, m map[string]string) error {
kv := strings.Split(labels, ",")
for _, v := range kv {
el := strings.Split(v, "=")
if len(el) == 2 {
m[el[0]] = el[1]
} else {
return fmt.Errorf("invalid nodeSelector label: %q", kv)
}
}
return nil
}
func generateDeployment(opts *Options) (*v1beta1.Deployment, error) {
labels := generateLabels(map[string]string{"name": "tiller"})
nodeSelectors := map[string]string{}
if len(opts.NodeSelectors) > 0 {
err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors)
if err != nil {
return nil, err
}
}
automountServiceAccountToken := opts.ServiceAccount != ""
d := &v1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: opts.Namespace,
Name: deploymentName,
Labels: labels,
},
Spec: v1beta1.DeploymentSpec{
Replicas: opts.getReplicas(),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: v1.PodSpec{
ServiceAccountName: opts.ServiceAccount,
AutomountServiceAccountToken: &automountServiceAccountToken,
Containers: []v1.Container{
{
Name: "tiller",
Image: opts.selectImage(),
ImagePullPolicy: opts.pullPolicy(),
Ports: []v1.ContainerPort{
{ContainerPort: 44134, Name: "tiller"},
{ContainerPort: 44135, Name: "http"},
},
Env: []v1.EnvVar{
{Name: "TILLER_NAMESPACE", Value: opts.Namespace},
{Name: "TILLER_HISTORY_MAX", Value: fmt.Sprintf("%d", opts.MaxHistory)},
},
LivenessProbe: &v1.Probe{
Handler: v1.Handler{
HTTPGet: &v1.HTTPGetAction{
Path: "/liveness",
Port: intstr.FromInt(44135),
},
},
InitialDelaySeconds: 1,
TimeoutSeconds: 1,
},
ReadinessProbe: &v1.Probe{
Handler: v1.Handler{
HTTPGet: &v1.HTTPGetAction{
Path: "/readiness",
Port: intstr.FromInt(44135),
},
},
InitialDelaySeconds: 1,
TimeoutSeconds: 1,
},
},
},
HostNetwork: opts.EnableHostNetwork,
NodeSelector: nodeSelectors,
},
},
},
}
if opts.tls() {
const certsDir = "/etc/certs"
var tlsVerify, tlsEnable = "", "1"
if opts.VerifyTLS {
tlsVerify = "1"
}
// Mount secret to "/etc/certs"
d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
Name: "tiller-certs",
ReadOnly: true,
MountPath: certsDir,
})
// Add environment variable required for enabling TLS
d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []v1.EnvVar{
{Name: "TILLER_TLS_VERIFY", Value: tlsVerify},
{Name: "TILLER_TLS_ENABLE", Value: tlsEnable},
{Name: "TILLER_TLS_CERTS", Value: certsDir},
}...)
// Add secret volume to deployment
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, v1.Volume{
Name: "tiller-certs",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: "tiller-secret",
},
},
})
}
// if --override values were specified, ultimately convert values and deployment to maps,
// merge them and convert back to Deployment
if len(opts.Values) > 0 {
// base deployment struct
var dd v1beta1.Deployment
// get YAML from original deployment
dy, err := yaml.Marshal(d)
if err != nil {
return nil, fmt.Errorf("Error marshalling base Tiller Deployment: %s", err)
}
// convert deployment YAML to values
dv, err := chartutil.ReadValues(dy)
if err != nil {
return nil, fmt.Errorf("Error converting Deployment manifest: %s ", err)
}
dm := dv.AsMap()
// merge --set values into our map
sm, err := opts.valuesMap(dm)
if err != nil {
return nil, fmt.Errorf("Error merging --set values into Deployment manifest")
}
finalY, err := yaml.Marshal(sm)
if err != nil {
return nil, fmt.Errorf("Error marshalling merged map to YAML: %s ", err)
}
// convert merged values back into deployment
err = yaml.Unmarshal(finalY, &dd)
if err != nil {
return nil, fmt.Errorf("Error unmarshalling Values to Deployment manifest: %s ", err)
}
d = &dd
}
return d, nil
}
func generateService(namespace string) *v1.Service {
labels := generateLabels(map[string]string{"name": "tiller"})
s := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: serviceName,
Labels: labels,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
Ports: []v1.ServicePort{
{
Name: "tiller",
Port: 44134,
TargetPort: intstr.FromString("tiller"),
},
},
Selector: labels,
},
}
return s
}
// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource.
func SecretManifest(opts *Options) (string, error) {
o, err := generateSecret(opts)
if err != nil {
return "", err
}
buf, err := yaml.Marshal(o)
return string(buf), err
}
// createSecret creates the Tiller secret resource.
func createSecret(client corev1.SecretsGetter, opts *Options) error {
o, err := generateSecret(opts)
if err != nil {
return err
}
_, err = client.Secrets(o.Namespace).Create(o)
return err
}
// generateSecret builds the secret object that hold Tiller secrets.
func generateSecret(opts *Options) (*v1.Secret, error) {
labels := generateLabels(map[string]string{"name": "tiller"})
secret := &v1.Secret{
Type: v1.SecretTypeOpaque,
Data: make(map[string][]byte),
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Labels: labels,
Namespace: opts.Namespace,
},
}
var err error
if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil {
return nil, err
}
if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil {
return nil, err
}
if opts.VerifyTLS {
if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil {
return nil, err
}
}
return secret, nil
}
func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }

@ -1,743 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"os"
"path/filepath"
"reflect"
"testing"
"github.com/ghodss/yaml"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
testcore "k8s.io/client-go/testing"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/version"
)
func TestDeploymentManifest(t *testing.T) {
tests := []struct {
name string
image string
canary bool
expect string
imagePullPolicy v1.PullPolicy
}{
{"default", "", false, "gcr.io/kubernetes-helm/tiller:" + version.Version, "IfNotPresent"},
{"canary", "example.com/tiller", true, "gcr.io/kubernetes-helm/tiller:canary", "Always"},
{"custom", "example.com/tiller:latest", false, "example.com/tiller:latest", "IfNotPresent"},
}
for _, tt := range tests {
o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary})
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
var dep v1beta1.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)
}
if got := dep.Spec.Template.Spec.Containers[0].ImagePullPolicy; got != tt.imagePullPolicy {
t.Errorf("%s: expected imagePullPolicy %q, got %q", tt.name, tt.imagePullPolicy, got)
}
if got := dep.Spec.Template.Spec.Containers[0].Env[0].Value; got != v1.NamespaceDefault {
t.Errorf("%s: expected namespace %q, got %q", tt.name, v1.NamespaceDefault, got)
}
}
}
func TestDeploymentManifestForServiceAccount(t *testing.T) {
tests := []struct {
name string
image string
canary bool
expect string
imagePullPolicy v1.PullPolicy
serviceAccount string
}{
{"withSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", "service-account"},
{"withoutSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", ""},
}
for _, tt := range tests {
o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary, ServiceAccount: tt.serviceAccount})
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
if got := d.Spec.Template.Spec.ServiceAccountName; got != tt.serviceAccount {
t.Errorf("%s: expected service account value %q, got %q", tt.name, tt.serviceAccount, got)
}
if got := *d.Spec.Template.Spec.AutomountServiceAccountToken; got != (tt.serviceAccount != "") {
t.Errorf("%s: unexpected automountServiceAccountToken = %t for serviceAccount %q", tt.name, got, tt.serviceAccount)
}
}
}
func TestDeploymentManifest_WithTLS(t *testing.T) {
tests := []struct {
opts Options
name string
enable string
verify string
}{
{
Options{Namespace: v1.NamespaceDefault, EnableTLS: true, VerifyTLS: true},
"tls enable (true), tls verify (true)",
"1",
"1",
},
{
Options{Namespace: v1.NamespaceDefault, EnableTLS: true, VerifyTLS: false},
"tls enable (true), tls verify (false)",
"1",
"",
},
{
Options{Namespace: v1.NamespaceDefault, EnableTLS: false, VerifyTLS: true},
"tls enable (false), tls verify (true)",
"1",
"1",
},
}
for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
// verify environment variable in deployment reflect the use of tls being enabled.
if got := d.Spec.Template.Spec.Containers[0].Env[2].Value; got != tt.verify {
t.Errorf("%s: expected tls verify env value %q, got %q", tt.name, tt.verify, got)
}
if got := d.Spec.Template.Spec.Containers[0].Env[3].Value; got != tt.enable {
t.Errorf("%s: expected tls enable env value %q, got %q", tt.name, tt.enable, got)
}
}
}
func TestServiceManifest(t *testing.T) {
o, err := ServiceManifest(v1.NamespaceDefault)
if err != nil {
t.Fatalf("error %q", err)
}
var svc v1.Service
if err := yaml.Unmarshal([]byte(o), &svc); err != nil {
t.Fatalf("error %q", err)
}
if got := svc.ObjectMeta.Namespace; got != v1.NamespaceDefault {
t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got)
}
}
func TestSecretManifest(t *testing.T) {
o, err := SecretManifest(&Options{
VerifyTLS: true,
EnableTLS: true,
Namespace: v1.NamespaceDefault,
TLSKeyFile: tlsTestFile(t, "key.pem"),
TLSCertFile: tlsTestFile(t, "crt.pem"),
TLSCaCertFile: tlsTestFile(t, "ca.pem"),
})
if err != nil {
t.Fatalf("error %q", err)
}
var obj v1.Secret
if err := yaml.Unmarshal([]byte(o), &obj); err != nil {
t.Fatalf("error %q", err)
}
if got := obj.ObjectMeta.Namespace; got != v1.NamespaceDefault {
t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got)
}
if _, ok := obj.Data["tls.key"]; !ok {
t.Errorf("missing 'tls.key' in generated secret object")
}
if _, ok := obj.Data["tls.crt"]; !ok {
t.Errorf("missing 'tls.crt' in generated secret object")
}
if _, ok := obj.Data["ca.crt"]; !ok {
t.Errorf("missing 'ca.crt' in generated secret object")
}
}
func TestInstall(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
ports := len(obj.Spec.Template.Spec.Containers[0].Ports)
if ports != 2 {
t.Errorf("expected ports = 2, got '%d'", ports)
}
replicas := obj.Spec.Replicas
if int(*replicas) != 1 {
t.Errorf("expected replicas = 1, got '%d'", replicas)
}
return true, obj, nil
})
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
n := obj.ObjectMeta.Namespace
if n != v1.NamespaceDefault {
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
}
return true, obj, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image}
if err := Install(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 2 {
t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions))
}
}
func TestInstallHA(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
replicas := obj.Spec.Replicas
if int(*replicas) != 2 {
t.Errorf("expected replicas = 2, got '%d'", replicas)
}
return true, obj, nil
})
opts := &Options{
Namespace: v1.NamespaceDefault,
ImageSpec: image,
Replicas: 2,
}
if err := Install(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
}
func TestInstall_WithTLS(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
name := "tiller-secret"
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
return true, obj, nil
})
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
n := obj.ObjectMeta.Namespace
if n != v1.NamespaceDefault {
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
}
return true, obj, nil
})
fc.AddReactor("create", "secrets", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1.Secret)
if l := obj.GetLabels(); reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
if n := obj.ObjectMeta.Namespace; n != v1.NamespaceDefault {
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
}
if s := obj.ObjectMeta.Name; s != name {
t.Errorf("expected name = '%s', got '%s'", name, s)
}
if _, ok := obj.Data["tls.key"]; !ok {
t.Errorf("missing 'tls.key' in generated secret object")
}
if _, ok := obj.Data["tls.crt"]; !ok {
t.Errorf("missing 'tls.crt' in generated secret object")
}
if _, ok := obj.Data["ca.crt"]; !ok {
t.Errorf("missing 'ca.crt' in generated secret object")
}
return true, obj, nil
})
opts := &Options{
Namespace: v1.NamespaceDefault,
ImageSpec: image,
EnableTLS: true,
VerifyTLS: true,
TLSKeyFile: tlsTestFile(t, "key.pem"),
TLSCertFile: tlsTestFile(t, "crt.pem"),
TLSCaCertFile: tlsTestFile(t, "ca.pem"),
}
if err := Install(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func TestInstall_canary(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != "gcr.io/kubernetes-helm/tiller:canary" {
t.Errorf("expected canary image, got '%s'", i)
}
return true, obj, nil
})
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
return true, obj, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, UseCanary: true}
if err := Install(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 2 {
t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions))
}
}
func TestUpgrade(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v1.0.0",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func TestUpgrade_serviceNotFound(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace",
UseCanary: false,
})
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(v1.Resource("services"), "1")
})
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*v1.Service)
n := obj.ObjectMeta.Namespace
if n != v1.NamespaceDefault {
t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n)
}
return true, obj, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 4 {
t.Errorf("unexpected actions: %v, expected 4 actions got %d", actions, len(actions))
}
}
func TestUgrade_newerVersion(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v100.5.0",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{
Namespace: v1.NamespaceDefault,
ImageSpec: image,
ServiceAccount: serviceAccount,
ForceUpgrade: false,
}
if err := Upgrade(fc, opts); err == nil {
t.Errorf("Expected error because the deployed version is newer")
}
if actions := fc.Actions(); len(actions) != 1 {
t.Errorf("unexpected actions: %v, expected 1 action got %d", actions, len(actions))
}
opts = &Options{
Namespace: v1.NamespaceDefault,
ImageSpec: image,
ServiceAccount: serviceAccount,
ForceUpgrade: true,
}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 4 {
t.Errorf("unexpected actions: %v, expected 4 action got %d", actions, len(actions))
}
}
func TestUpgrade_identical(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v2.0.0",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func TestUpgrade_canaryClient(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:canary"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:v1.0.0",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func TestUpgrade_canaryServer(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
serviceAccount := "newServiceAccount"
existingDeployment, _ := deployment(&Options{
Namespace: v1.NamespaceDefault,
ImageSpec: "imageToReplace:canary",
ServiceAccount: "serviceAccountToReplace",
UseCanary: false,
})
existingService := service(v1.NamespaceDefault)
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingDeployment, nil
})
fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment)
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
sa := obj.Spec.Template.Spec.ServiceAccountName
if sa != serviceAccount {
t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa)
}
return true, obj, nil
})
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, existingService, nil
})
opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount}
if err := Upgrade(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func tlsTestFile(t *testing.T, path string) string {
const tlsTestDir = "../../../testdata"
path = filepath.Join(tlsTestDir, path)
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Fatalf("tls test file %s does not exist", path)
}
return path
}
func TestDeploymentManifest_WithNodeSelectors(t *testing.T) {
tests := []struct {
opts Options
name string
expect map[string]interface{}
}{
{
Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller"},
"nodeSelector app=tiller",
map[string]interface{}{"app": "tiller"},
},
{
Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,helm=rocks"},
"nodeSelector app=tiller, helm=rocks",
map[string]interface{}{"app": "tiller", "helm": "rocks"},
},
// note: nodeSelector key and value are strings
{
Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,minCoolness=1"},
"nodeSelector app=tiller, helm=rocks",
map[string]interface{}{"app": "tiller", "minCoolness": "1"},
},
}
for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
var d v1beta1.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
// Verify that environment variables in Deployment reflect the use of TLS being enabled.
got := d.Spec.Template.Spec.NodeSelector
for k, v := range tt.expect {
if got[k] != v {
t.Errorf("%s: expected nodeSelector value %q, got %q", tt.name, tt.expect, got)
}
}
}
}
func TestDeploymentManifest_WithSetValues(t *testing.T) {
tests := []struct {
opts Options
name string
expectPath string
expect interface{}
}{
{
Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.nodeselector.app=tiller"}},
"setValues spec.template.spec.nodeSelector.app=tiller",
"spec.template.spec.nodeSelector.app",
"tiller",
},
{
Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.replicas=2"}},
"setValues spec.replicas=2",
"spec.replicas",
2,
},
{
Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.activedeadlineseconds=120"}},
"setValues spec.template.spec.activedeadlineseconds=120",
"spec.template.spec.activeDeadlineSeconds",
120,
},
}
for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
values, err := chartutil.ReadValues([]byte(o))
if err != nil {
t.Errorf("Error converting Deployment manifest to Values: %s", err)
}
// path value
pv, err := values.PathValue(tt.expectPath)
if err != nil {
t.Errorf("Error retrieving path value from Deployment Values: %s", err)
}
// convert our expected value to match the result type for comparison
ev := tt.expect
switch pvt := pv.(type) {
case float64:
floatType := reflect.TypeOf(float64(0))
v := reflect.ValueOf(ev)
v = reflect.Indirect(v)
if !v.Type().ConvertibleTo(floatType) {
t.Fatalf("Error converting expected value %v to float64", v.Type())
}
fv := v.Convert(floatType)
if fv.Float() != pvt {
t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv)
}
default:
if pv != tt.expect {
t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv)
}
}
}
}

@ -1,164 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"fmt"
"k8s.io/api/core/v1"
"k8s.io/helm/pkg/strvals"
"k8s.io/helm/pkg/version"
)
const defaultImage = "gcr.io/kubernetes-helm/tiller"
// Options control how to install Tiller into a cluster, upgrade, and uninstall Tiller from a cluster.
type Options struct {
// EnableTLS instructs Tiller to serve with TLS enabled.
//
// Implied by VerifyTLS. If set the TLSKey and TLSCert are required.
EnableTLS bool
// VerifyTLS instructs Tiller to serve with TLS enabled verify remote certificates.
//
// If set TLSKey, TLSCert, TLSCaCert are required.
VerifyTLS bool
// UseCanary indicates that Tiller should deploy using the latest Tiller image.
UseCanary bool
// Namespace is the Kubernetes namespace to use to deploy Tiller.
Namespace string
// ServiceAccount is the Kubernetes service account to add to Tiller.
ServiceAccount string
// Force allows to force upgrading tiller if deployed version is greater than current version
ForceUpgrade bool
// ImageSpec indentifies the image Tiller will use when deployed.
//
// Valid if and only if UseCanary is false.
ImageSpec string
// TLSKeyFile identifies the file containing the pem encoded TLS private
// key Tiller should use.
//
// Required and valid if and only if EnableTLS or VerifyTLS is set.
TLSKeyFile string
// TLSCertFile identifies the file containing the pem encoded TLS
// certificate Tiller should use.
//
// Required and valid if and only if EnableTLS or VerifyTLS is set.
TLSCertFile string
// TLSCaCertFile identifies the file containing the pem encoded TLS CA
// certificate Tiller should use to verify remotes certificates.
//
// Required and valid if and only if VerifyTLS is set.
TLSCaCertFile string
// EnableHostNetwork installs Tiller with net=host.
EnableHostNetwork bool
// MaxHistory sets the maximum number of release versions stored per release.
//
// Less than or equal to zero means no limit.
MaxHistory int
// Replicas sets the amount of Tiller replicas to start
//
// Less than or equals to 1 means 1.
Replicas int
// NodeSelectors determine which nodes Tiller can land on.
NodeSelectors string
// Output dumps the Tiller manifest in the specified format (e.g. JSON) but skips Helm/Tiller installation.
Output OutputFormat
// Set merges additional values into the Tiller Deployment manifest.
Values []string
}
func (opts *Options) selectImage() string {
switch {
case opts.UseCanary:
return defaultImage + ":canary"
case opts.ImageSpec == "":
return fmt.Sprintf("%s:%s", defaultImage, version.Version)
default:
return opts.ImageSpec
}
}
func (opts *Options) pullPolicy() v1.PullPolicy {
if opts.UseCanary {
return v1.PullAlways
}
return v1.PullIfNotPresent
}
func (opts *Options) getReplicas() *int32 {
replicas := int32(1)
if opts.Replicas > 1 {
replicas = int32(opts.Replicas)
}
return &replicas
}
func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS }
// valuesMap returns user set values in map format
func (opts *Options) valuesMap(m map[string]interface{}) (map[string]interface{}, error) {
for _, skv := range opts.Values {
if err := strvals.ParseInto(skv, m); err != nil {
return nil, err
}
}
return m, nil
}
// OutputFormat defines valid values for init output (json, yaml)
type OutputFormat string
// String returns the string value of the OutputFormat
func (f *OutputFormat) String() string {
return string(*f)
}
// Type returns the string value of the OutputFormat
func (f *OutputFormat) Type() string {
return "OutputFormat"
}
const (
fmtJSON OutputFormat = "json"
fmtYAML OutputFormat = "yaml"
)
// Set validates and sets the value of the OutputFormat
func (f *OutputFormat) Set(s string) error {
for _, of := range []OutputFormat{fmtJSON, fmtYAML} {
if s == string(of) {
*f = of
return nil
}
}
return fmt.Errorf("unknown output format %q", s)
}

@ -1,71 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl"
)
const (
deploymentName = "tiller-deploy"
serviceName = "tiller-deploy"
secretName = "tiller-secret"
)
// Uninstall uses Kubernetes client to uninstall Tiller.
func Uninstall(client internalclientset.Interface, opts *Options) error {
if err := deleteService(client.Core(), opts.Namespace); err != nil {
return err
}
if err := deleteDeployment(client, opts.Namespace); err != nil {
return err
}
return deleteSecret(client.Core(), opts.Namespace)
}
// deleteService deletes the Tiller Service resource
func deleteService(client coreclient.ServicesGetter, namespace string) error {
err := client.Services(namespace).Delete(serviceName, &metav1.DeleteOptions{})
return ingoreNotFound(err)
}
// deleteDeployment deletes the Tiller Deployment resource
// We need to use the reaper instead of the kube API because GC for deployment dependents
// is not yet supported at the k8s server level (<= 1.5)
func deleteDeployment(client internalclientset.Interface, namespace string) error {
reaper, _ := kubectl.ReaperFor(extensions.Kind("Deployment"), client)
err := reaper.Stop(namespace, deploymentName, 0, nil)
return ingoreNotFound(err)
}
// deleteSecret deletes the Tiller Secret resource
func deleteSecret(client coreclient.SecretsGetter, namespace string) error {
err := client.Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{})
return ingoreNotFound(err)
}
func ingoreNotFound(err error) error {
if apierrors.IsNotFound(err) {
return nil
}
return err
}

@ -1,88 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"testing"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
testcore "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
func TestUninstall(t *testing.T) {
fc := &fake.Clientset{}
opts := &Options{Namespace: core.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 7 {
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions))
}
}
func TestUninstall_serviceNotFound(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("delete", "services", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "services"}, "1")
})
opts := &Options{Namespace: core.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 7 {
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions))
}
}
func TestUninstall_deploymentNotFound(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("delete", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(core.Resource("deployments"), "1")
})
opts := &Options{Namespace: core.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 7 {
t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions))
}
}
func TestUninstall_secretNotFound(t *testing.T) {
fc := &fake.Clientset{}
fc.AddReactor("delete", "secrets", func(action testcore.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewNotFound(core.Resource("secrets"), "1")
})
opts := &Options{Namespace: core.NamespaceDefault}
if err := Uninstall(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 7 {
t.Errorf("unexpected actions: %v, expect 7 actions got %d", actions, len(actions))
}
}

@ -1,131 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"errors"
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"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/proto/hapi/release"
)
const resetDesc = `
This command uninstalls Tiller (the Helm server-side component) from your
Kubernetes Cluster and optionally deletes local configuration in
$HELM_HOME (default ~/.helm/)
`
type resetCmd struct {
force bool
removeHelmHome bool
namespace string
out io.Writer
home helmpath.Home
client helm.Interface
kubeClient internalclientset.Interface
}
func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command {
d := &resetCmd{
out: out,
client: client,
}
cmd := &cobra.Command{
Use: "reset",
Short: "uninstalls Tiller from a cluster",
Long: resetDesc,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := setupConnection(); !d.force && err != nil {
return err
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return errors.New("This command does not accept arguments")
}
d.namespace = settings.TillerNamespace
d.home = settings.Home
d.client = ensureHelmClient(d.client)
return d.run()
},
}
f := cmd.Flags()
f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state. Releases are not deleted.)")
f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME")
return cmd
}
// runReset uninstalls tiller from Kubernetes Cluster and deletes local config
func (d *resetCmd) run() error {
if d.kubeClient == nil {
c, err := getInternalKubeClient(settings.KubeContext)
if err != nil {
return fmt.Errorf("could not get kubernetes client: %s", err)
}
d.kubeClient = c
}
res, err := d.client.ListReleases(
helm.ReleaseListStatuses([]release.Status_Code{release.Status_DEPLOYED}),
)
if !d.force && err != nil {
return prettyError(err)
}
if !d.force && res != nil && len(res.Releases) > 0 {
return fmt.Errorf("there are still %d deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)", len(res.Releases))
}
if err := installer.Uninstall(d.kubeClient, &installer.Options{Namespace: d.namespace}); err != nil {
return fmt.Errorf("error unstalling Tiller: %s", err)
}
if d.removeHelmHome {
if err := deleteDirectories(d.home, d.out); err != nil {
return err
}
}
fmt.Fprintln(d.out, "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.")
return nil
}
// deleteDirectories deletes $HELM_HOME
func deleteDirectories(home helmpath.Home, out io.Writer) error {
if _, err := os.Stat(home.String()); err == nil {
fmt.Fprintf(out, "Deleting %s \n", home.String())
if err := os.RemoveAll(home.String()); err != nil {
return fmt.Errorf("Could not remove %s: %s", home.String(), err)
}
}
return nil
}

@ -1,131 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/proto/hapi/release"
)
type resetCase struct {
name string
err bool
resp []*release.Release
removeHelmHome bool
force bool
expectedActions int
expectedOutput string
}
func TestResetCmd(t *testing.T) {
verifyResetCmd(t, resetCase{
name: "test reset command",
expectedActions: 3,
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
})
}
func TestResetCmd_removeHelmHome(t *testing.T) {
verifyResetCmd(t, resetCase{
name: "test reset command - remove helm home",
removeHelmHome: true,
expectedActions: 3,
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
})
}
func TestReset_deployedReleases(t *testing.T) {
verifyResetCmd(t, resetCase{
name: "test reset command - deployed releases",
resp: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
err: true,
expectedOutput: "there are still 1 deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)",
})
}
func TestReset_forceFlag(t *testing.T) {
verifyResetCmd(t, resetCase{
name: "test reset command - force flag",
force: true,
resp: []*release.Release{
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
expectedActions: 3,
expectedOutput: "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.",
})
}
func verifyResetCmd(t *testing.T, tc resetCase) {
home, err := ioutil.TempDir("", "helm_home")
if err != nil {
t.Fatal(err)
}
defer os.Remove(home)
var buf bytes.Buffer
c := &helm.FakeClient{
Rels: tc.resp,
}
fc := fake.NewSimpleClientset()
cmd := &resetCmd{
removeHelmHome: tc.removeHelmHome,
force: tc.force,
out: &buf,
home: helmpath.Home(home),
client: c,
kubeClient: fc,
namespace: core.NamespaceDefault,
}
err = cmd.run()
if !tc.err && err != nil {
t.Errorf("unexpected error: %v", err)
}
got := buf.String()
if tc.err {
got = err.Error()
}
actions := fc.Actions()
if tc.expectedActions > 0 && len(actions) != tc.expectedActions {
t.Errorf("Expected %d actions, got %d", tc.expectedActions, len(actions))
}
if !strings.Contains(got, tc.expectedOutput) {
t.Errorf("expected %q, got %q", tc.expectedOutput, got)
}
_, err = os.Stat(home)
if !tc.removeHelmHome && err != nil {
t.Errorf("Helm home directory %s does not exists", home)
}
if tc.removeHelmHome && err == nil {
t.Errorf("Helm home directory %s exists", home)
}
}
Loading…
Cancel
Save