From 9409adfa8de0afcd58ddcb848f9cde946cc740dd Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 12 Apr 2018 16:08:31 -0700 Subject: [PATCH] ref(cmd): remove reset and init tiller commands --- cmd/helm/get.go | 6 +- cmd/helm/helm.go | 93 +--- cmd/helm/init.go | 252 +-------- cmd/helm/init_test.go | 286 ----------- cmd/helm/installer/install.go | 374 -------------- cmd/helm/installer/install_test.go | 743 --------------------------- cmd/helm/installer/options.go | 164 ------ cmd/helm/installer/uninstall.go | 71 --- cmd/helm/installer/uninstall_test.go | 88 ---- cmd/helm/reset.go | 131 ----- cmd/helm/reset_test.go | 131 ----- 11 files changed, 19 insertions(+), 2320 deletions(-) delete mode 100644 cmd/helm/installer/install.go delete mode 100644 cmd/helm/installer/install_test.go delete mode 100644 cmd/helm/installer/options.go delete mode 100644 cmd/helm/installer/uninstall.go delete mode 100644 cmd/helm/installer/uninstall_test.go delete mode 100644 cmd/helm/reset.go delete mode 100644 cmd/helm/reset_test.go diff --git a/cmd/helm/get.go b/cmd/helm/get.go index 8c899eb37..aba508e34 100644 --- a/cmd/helm/get.go +++ b/cmd/helm/get.go @@ -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 } diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 4c7ca9290..28312ae96 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -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 -} diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 401b897c5..57da555a2 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -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 + skipRefresh bool + out io.Writer + home helmpath.Home } 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 - } - } -} diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 6a5767fca..42f0c0464 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -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()) - } - } - -} diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go deleted file mode 100644 index a45179a48..000000000 --- a/cmd/helm/installer/install.go +++ /dev/null @@ -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) } diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go deleted file mode 100644 index 80219505a..000000000 --- a/cmd/helm/installer/install_test.go +++ /dev/null @@ -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) - } - } - } -} diff --git a/cmd/helm/installer/options.go b/cmd/helm/installer/options.go deleted file mode 100644 index 13cf43dcc..000000000 --- a/cmd/helm/installer/options.go +++ /dev/null @@ -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) -} diff --git a/cmd/helm/installer/uninstall.go b/cmd/helm/installer/uninstall.go deleted file mode 100644 index 818827ddb..000000000 --- a/cmd/helm/installer/uninstall.go +++ /dev/null @@ -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 -} diff --git a/cmd/helm/installer/uninstall_test.go b/cmd/helm/installer/uninstall_test.go deleted file mode 100644 index 91b257d47..000000000 --- a/cmd/helm/installer/uninstall_test.go +++ /dev/null @@ -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)) - } -} diff --git a/cmd/helm/reset.go b/cmd/helm/reset.go deleted file mode 100644 index 9d3e17e03..000000000 --- a/cmd/helm/reset.go +++ /dev/null @@ -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 -} diff --git a/cmd/helm/reset_test.go b/cmd/helm/reset_test.go deleted file mode 100644 index ae6a00036..000000000 --- a/cmd/helm/reset_test.go +++ /dev/null @@ -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) - } -}