mirror of https://github.com/helm/helm
Merge pull request #1850 from frodenas/deinit
feat(helm): Add command to uninstall Tillerpull/1888/head
commit
d41f093331
@ -0,0 +1,83 @@
|
||||
/*
|
||||
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 (
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
kerrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
|
||||
"k8s.io/helm/pkg/kube"
|
||||
)
|
||||
|
||||
// Uninstall uses kubernetes client to uninstall tiller
|
||||
func Uninstall(kubeClient internalclientset.Interface, kubeCmd *kube.Client, namespace string, verbose bool) error {
|
||||
if _, err := kubeClient.Core().Services(namespace).Get("tiller-deploy"); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
} else if err := deleteService(kubeClient.Core(), namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
if obj, err := kubeClient.Extensions().Deployments(namespace).Get("tiller-deploy"); err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
} else if err := deleteDeployment(kubeCmd, namespace, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteService deletes the Tiller Service resource
|
||||
func deleteService(client internalversion.ServicesGetter, namespace string) error {
|
||||
return client.Services(namespace).Delete("tiller-deploy", &api.DeleteOptions{})
|
||||
}
|
||||
|
||||
// deleteDeployment deletes the Tiller Deployment resource
|
||||
// We need to use the kubeCmd reaper instead of the kube API because GC for deployment dependents
|
||||
// is not yet supported at the k8s server level (<= 1.5)
|
||||
func deleteDeployment(kubeCmd *kube.Client, namespace string, obj *extensions.Deployment) error {
|
||||
obj.Kind = "Deployment"
|
||||
obj.APIVersion = "extensions/v1beta1"
|
||||
buf, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader := strings.NewReader(string(buf))
|
||||
infos, err := kubeCmd.Build(namespace, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, info := range infos {
|
||||
reaper, err := kubeCmd.Reaper(info.Mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = reaper.Stop(info.Namespace, info.Name, 0, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/meta"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
testcore "k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
|
||||
"k8s.io/helm/pkg/kube"
|
||||
)
|
||||
|
||||
type fakeReaper struct {
|
||||
namespace string
|
||||
name string
|
||||
}
|
||||
|
||||
func (r *fakeReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error {
|
||||
r.namespace = namespace
|
||||
r.name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeReaperFactory struct {
|
||||
cmdutil.Factory
|
||||
reaper kubectl.Reaper
|
||||
}
|
||||
|
||||
func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, error) {
|
||||
return f.reaper, nil
|
||||
}
|
||||
|
||||
func TestUninstall(t *testing.T) {
|
||||
existingService := service(api.NamespaceDefault)
|
||||
existingDeployment := deployment(api.NamespaceDefault, "image", false)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingService, nil
|
||||
})
|
||||
fc.AddReactor("delete", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, nil
|
||||
})
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
|
||||
f, _, _, _ := cmdtesting.NewAPIFactory()
|
||||
r := &fakeReaper{}
|
||||
rf := &fakeReaperFactory{Factory: f, reaper: r}
|
||||
kc := &kube.Client{Factory: rf}
|
||||
|
||||
if err := Uninstall(fc, kc, api.NamespaceDefault, false); 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))
|
||||
}
|
||||
|
||||
if r.namespace != api.NamespaceDefault {
|
||||
t.Errorf("unexpected reaper namespace: %s", r.name)
|
||||
}
|
||||
|
||||
if r.name != "tiller-deploy" {
|
||||
t.Errorf("unexpected reaper name: %s", r.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninstall_serviceNotFound(t *testing.T) {
|
||||
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewNotFound(api.Resource("services"), "1")
|
||||
})
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingDeployment, nil
|
||||
})
|
||||
|
||||
f, _, _, _ := cmdtesting.NewAPIFactory()
|
||||
r := &fakeReaper{}
|
||||
rf := &fakeReaperFactory{Factory: f, reaper: r}
|
||||
kc := &kube.Client{Factory: rf}
|
||||
|
||||
if err := Uninstall(fc, kc, api.NamespaceDefault, false); 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))
|
||||
}
|
||||
|
||||
if r.namespace != api.NamespaceDefault {
|
||||
t.Errorf("unexpected reaper namespace: %s", r.name)
|
||||
}
|
||||
|
||||
if r.name != "tiller-deploy" {
|
||||
t.Errorf("unexpected reaper name: %s", r.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninstall_deploymentNotFound(t *testing.T) {
|
||||
existingService := service(api.NamespaceDefault)
|
||||
|
||||
fc := &fake.Clientset{}
|
||||
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, existingService, nil
|
||||
})
|
||||
fc.AddReactor("delete", "services", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, nil
|
||||
})
|
||||
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewNotFound(api.Resource("deployments"), "1")
|
||||
})
|
||||
|
||||
f, _, _, _ := cmdtesting.NewAPIFactory()
|
||||
r := &fakeReaper{}
|
||||
rf := &fakeReaperFactory{Factory: f, reaper: r}
|
||||
kc := &kube.Client{Factory: rf}
|
||||
|
||||
if err := Uninstall(fc, kc, api.NamespaceDefault, false); 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))
|
||||
}
|
||||
|
||||
if r.namespace != "" {
|
||||
t.Errorf("unexpected reaper namespace: %s", r.name)
|
||||
}
|
||||
|
||||
if r.name != "" {
|
||||
t.Errorf("unexpected reaper name: %s", r.name)
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
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/helmpath"
|
||||
"k8s.io/helm/cmd/helm/installer"
|
||||
"k8s.io/helm/pkg/helm"
|
||||
"k8s.io/helm/pkg/kube"
|
||||
"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
|
||||
kubeCmd *kube.Client
|
||||
}
|
||||
|
||||
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,
|
||||
PersistentPreRunE: setupConnection,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return errors.New("This command does not accept arguments")
|
||||
}
|
||||
|
||||
d.namespace = tillerNamespace
|
||||
d.home = helmpath.Home(homePath())
|
||||
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")
|
||||
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 := getKubeClient(kubeContext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get kubernetes client: %s", err)
|
||||
}
|
||||
d.kubeClient = c
|
||||
}
|
||||
if d.kubeCmd == nil {
|
||||
d.kubeCmd = getKubeCmd(kubeContext)
|
||||
}
|
||||
|
||||
res, err := d.client.ListReleases(
|
||||
helm.ReleaseListStatuses([]release.Status_Code{release.Status_DEPLOYED}),
|
||||
)
|
||||
if err != nil {
|
||||
return prettyError(err)
|
||||
}
|
||||
|
||||
if len(res.Releases) > 0 && !d.force {
|
||||
return fmt.Errorf("There are still %d deployed releases (Tip: use --force).", len(res.Releases))
|
||||
}
|
||||
|
||||
if err := installer.Uninstall(d.kubeClient, d.kubeCmd, d.namespace, flagDebug); 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
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
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/api"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
|
||||
"k8s.io/helm/cmd/helm/helmpath"
|
||||
"k8s.io/helm/pkg/proto/hapi/release"
|
||||
)
|
||||
|
||||
func TestResetCmd(t *testing.T) {
|
||||
home, err := ioutil.TempDir("", "helm_home")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(home)
|
||||
|
||||
var buf bytes.Buffer
|
||||
c := &fakeReleaseClient{}
|
||||
fc := fake.NewSimpleClientset()
|
||||
cmd := &resetCmd{
|
||||
out: &buf,
|
||||
home: helmpath.Home(home),
|
||||
client: c,
|
||||
kubeClient: fc,
|
||||
namespace: api.NamespaceDefault,
|
||||
}
|
||||
if err := cmd.run(); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
actions := fc.Actions()
|
||||
if len(actions) != 2 {
|
||||
t.Errorf("Expected 2 actions, got %d", len(actions))
|
||||
}
|
||||
if !actions[0].Matches("get", "services") {
|
||||
t.Errorf("unexpected action: %v, expected get service", actions[1])
|
||||
}
|
||||
if !actions[1].Matches("get", "deployments") {
|
||||
t.Errorf("unexpected action: %v, expected get deployment", actions[0])
|
||||
}
|
||||
expected := "Tiller (the helm server side component) has been uninstalled from your Kubernetes Cluster."
|
||||
if !strings.Contains(buf.String(), expected) {
|
||||
t.Errorf("expected %q, got %q", expected, buf.String())
|
||||
}
|
||||
if _, err := os.Stat(home); err != nil {
|
||||
t.Errorf("Helm home directory %s does not exists", home)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetCmd_removeHelmHome(t *testing.T) {
|
||||
home, err := ioutil.TempDir("", "helm_home")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(home)
|
||||
|
||||
var buf bytes.Buffer
|
||||
c := &fakeReleaseClient{}
|
||||
fc := fake.NewSimpleClientset()
|
||||
cmd := &resetCmd{
|
||||
removeHelmHome: true,
|
||||
out: &buf,
|
||||
home: helmpath.Home(home),
|
||||
client: c,
|
||||
kubeClient: fc,
|
||||
namespace: api.NamespaceDefault,
|
||||
}
|
||||
if err := cmd.run(); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
actions := fc.Actions()
|
||||
if len(actions) != 2 {
|
||||
t.Errorf("Expected 2 actions, got %d", len(actions))
|
||||
}
|
||||
if !actions[0].Matches("get", "services") {
|
||||
t.Errorf("unexpected action: %v, expected get service", actions[1])
|
||||
}
|
||||
if !actions[1].Matches("get", "deployments") {
|
||||
t.Errorf("unexpected action: %v, expected get deployment", actions[0])
|
||||
}
|
||||
expected := "Tiller (the helm server side component) has been uninstalled from your Kubernetes Cluster."
|
||||
if !strings.Contains(buf.String(), expected) {
|
||||
t.Errorf("expected %q, got %q", expected, buf.String())
|
||||
}
|
||||
if _, err := os.Stat(home); err == nil {
|
||||
t.Errorf("Helm home directory %s already exists", home)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReset_deployedReleases(t *testing.T) {
|
||||
home, err := ioutil.TempDir("", "helm_home")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(home)
|
||||
|
||||
var buf bytes.Buffer
|
||||
resp := []*release.Release{
|
||||
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
|
||||
}
|
||||
c := &fakeReleaseClient{
|
||||
rels: resp,
|
||||
}
|
||||
fc := fake.NewSimpleClientset()
|
||||
cmd := &resetCmd{
|
||||
out: &buf,
|
||||
home: helmpath.Home(home),
|
||||
client: c,
|
||||
kubeClient: fc,
|
||||
namespace: api.NamespaceDefault,
|
||||
}
|
||||
err = cmd.run()
|
||||
expected := "There are still 1 deployed releases (Tip: use --force)"
|
||||
if !strings.Contains(err.Error(), expected) {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(home); err != nil {
|
||||
t.Errorf("Helm home directory %s does not exists", home)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReset_forceFlag(t *testing.T) {
|
||||
home, err := ioutil.TempDir("", "helm_home")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(home)
|
||||
|
||||
var buf bytes.Buffer
|
||||
resp := []*release.Release{
|
||||
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
|
||||
}
|
||||
c := &fakeReleaseClient{
|
||||
rels: resp,
|
||||
}
|
||||
fc := fake.NewSimpleClientset()
|
||||
cmd := &resetCmd{
|
||||
force: true,
|
||||
out: &buf,
|
||||
home: helmpath.Home(home),
|
||||
client: c,
|
||||
kubeClient: fc,
|
||||
namespace: api.NamespaceDefault,
|
||||
}
|
||||
if err := cmd.run(); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
actions := fc.Actions()
|
||||
if len(actions) != 2 {
|
||||
t.Errorf("Expected 2 actions, got %d", len(actions))
|
||||
}
|
||||
if !actions[0].Matches("get", "services") {
|
||||
t.Errorf("unexpected action: %v, expected get service", actions[1])
|
||||
}
|
||||
if !actions[1].Matches("get", "deployments") {
|
||||
t.Errorf("unexpected action: %v, expected get deployment", actions[0])
|
||||
}
|
||||
expected := "Tiller (the helm server side component) has been uninstalled from your Kubernetes Cluster."
|
||||
if !strings.Contains(buf.String(), expected) {
|
||||
t.Errorf("expected %q, got %q", expected, buf.String())
|
||||
}
|
||||
if _, err := os.Stat(home); err != nil {
|
||||
t.Errorf("Helm home directory %s does not exists", home)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue