Skip to content

Commit

Permalink
Cleanup additional resources during uninstall (#820)
Browse files Browse the repository at this point in the history
* delete any jobs, cluster roles, cluster roles bindings during uninstall
* augment the uninstall tests w negative test

Co-authored-by: Kyle Schochenmaier <kschoche@gmail.com>
Co-authored-by: David Yu <dyu@hashicorp.com>
  • Loading branch information
3 people authored Nov 5, 2021
1 parent a3d023d commit 26b49ed
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
IMPROVEMENTS:
* Control Plane
* TLS: Support PKCS1 and PKCS8 private keys for Consul certificate authority. [[GH-843](https://github.com/hashicorp/consul-k8s/pull/843)]
* CLI
* Delete jobs, cluster roles, and cluster role bindings on `uninstall`. [[GH-820](https://github.com/hashicorp/consul-k8s/pull/820)]

BUG FIXES:
* Control Plane
Expand Down
104 changes: 102 additions & 2 deletions cli/cmd/uninstall/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,11 @@ func (c *Command) Run(args []string) int {
c.UI.Output("Name: %s", foundReleaseName, terminal.WithInfoStyle())
c.UI.Output("Namespace %s", foundReleaseNamespace, terminal.WithInfoStyle())
}
// Prompt with a warning for approval before deleting PVCs, Secrets, Service Accounts, Roles, and Role Bindings.
// Prompt with a warning for approval before deleting PVCs, Secrets, Service Accounts, Roles, Role Bindings,
// Jobs, Cluster Roles, and Cluster Role Bindings.
if !c.flagAutoApprove {
confirmation, err := c.UI.Input(&terminal.Input{
Prompt: fmt.Sprintf("WARNING: Proceed with deleting PVCs, Secrets, Service Accounts, Roles, and Role Bindings for the following installation? \n\n Name: %s \n Namespace: %s \n\n Only approve if all data from this installation can be deleted. (y/N)", foundReleaseName, foundReleaseNamespace),
Prompt: fmt.Sprintf("WARNING: Proceed with deleting PVCs, Secrets, Service Accounts, Roles, Role Bindings, Jobs, Cluster Roles, and Cluster Role Bindings for the following installation? \n\n Name: %s \n Namespace: %s \n\n Only approve if all data from this installation can be deleted. (y/N)", foundReleaseName, foundReleaseNamespace),
Style: terminal.WarningStyle,
Secret: false,
})
Expand Down Expand Up @@ -299,6 +300,21 @@ func (c *Command) Run(args []string) int {
return 1
}

if err := c.deleteJobs(foundReleaseName, foundReleaseNamespace); err != nil {
c.UI.Output(err.Error(), terminal.WithErrorStyle())
return 1
}

if err := c.deleteClusterRoles(foundReleaseName); err != nil {
c.UI.Output(err.Error(), terminal.WithErrorStyle())
return 1
}

if err := c.deleteClusterRoleBindings(foundReleaseName); err != nil {
c.UI.Output(err.Error(), terminal.WithErrorStyle())
return 1
}

return 0
}

Expand Down Expand Up @@ -476,3 +492,87 @@ func (c *Command) deleteRoleBindings(foundReleaseName, foundReleaseNamespace str
}
return nil
}

// deleteJobs deletes jobs that have the label release={{foundReleaseName}}.
func (c *Command) deleteJobs(foundReleaseName, foundReleaseNamespace string) error {
var jobNames []string
jobSelector := metav1.ListOptions{LabelSelector: fmt.Sprintf("release=%s", foundReleaseName)}
jobs, err := c.kubernetes.BatchV1().Jobs(foundReleaseNamespace).List(c.Ctx, jobSelector)
if err != nil {
return fmt.Errorf("deleteJobs: %s", err)
}
if len(jobs.Items) == 0 {
c.UI.Output("No Consul jobs found.", terminal.WithSuccessStyle())
return nil
}
for _, job := range jobs.Items {
err := c.kubernetes.BatchV1().Jobs(foundReleaseNamespace).Delete(c.Ctx, job.Name, metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("deleteJobs: error deleting Job %q: %s", job.Name, err)
}
jobNames = append(jobNames, job.Name)
}
if len(jobNames) > 0 {
for _, job := range jobNames {
c.UI.Output("Deleted Jobs => %s", job, terminal.WithSuccessStyle())
}
c.UI.Output("Consul jobs deleted.", terminal.WithSuccessStyle())
}
return nil
}

// deleteClusterRoles deletes clusterRoles that have the label release={{foundReleaseName}}.
func (c *Command) deleteClusterRoles(foundReleaseName string) error {
var clusterRolesNames []string
clusterRolesSelector := metav1.ListOptions{LabelSelector: fmt.Sprintf("release=%s", foundReleaseName)}
clusterRoles, err := c.kubernetes.RbacV1().ClusterRoles().List(c.Ctx, clusterRolesSelector)
if err != nil {
return fmt.Errorf("deleteClusterRoles: %s", err)
}
if len(clusterRoles.Items) == 0 {
c.UI.Output("No Consul cluster roles found.", terminal.WithSuccessStyle())
return nil
}
for _, clusterRole := range clusterRoles.Items {
err := c.kubernetes.RbacV1().ClusterRoles().Delete(c.Ctx, clusterRole.Name, metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("deleteClusterRoles: error deleting cluster role %q: %s", clusterRole.Name, err)
}
clusterRolesNames = append(clusterRolesNames, clusterRole.Name)
}
if len(clusterRolesNames) > 0 {
for _, clusterRole := range clusterRolesNames {
c.UI.Output("Deleted cluster role => %s", clusterRole, terminal.WithSuccessStyle())
}
c.UI.Output("Consul cluster roles deleted.", terminal.WithSuccessStyle())
}
return nil
}

// deleteClusterRoleBindings deletes clusterrolebindings that have the label release={{foundReleaseName}}.
func (c *Command) deleteClusterRoleBindings(foundReleaseName string) error {
var clusterRoleBindingsNames []string
clusterRoleBindingsSelector := metav1.ListOptions{LabelSelector: fmt.Sprintf("release=%s", foundReleaseName)}
clusterRoleBindings, err := c.kubernetes.RbacV1().ClusterRoleBindings().List(c.Ctx, clusterRoleBindingsSelector)
if err != nil {
return fmt.Errorf("deleteClusterRoleBindings: %s", err)
}
if len(clusterRoleBindings.Items) == 0 {
c.UI.Output("No Consul cluster role bindings found.", terminal.WithSuccessStyle())
return nil
}
for _, clusterRoleBinding := range clusterRoleBindings.Items {
err := c.kubernetes.RbacV1().ClusterRoleBindings().Delete(c.Ctx, clusterRoleBinding.Name, metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("deleteClusterRoleBindings: error deleting cluster role binding %q: %s", clusterRoleBinding.Name, err)
}
clusterRoleBindingsNames = append(clusterRoleBindingsNames, clusterRoleBinding.Name)
}
if len(clusterRoleBindingsNames) > 0 {
for _, clusterRoleBinding := range clusterRoleBindingsNames {
c.UI.Output("Deleted cluster role binding => %s", clusterRoleBinding, terminal.WithSuccessStyle())
}
c.UI.Output("Consul cluster role bindings deleted.", terminal.WithSuccessStyle())
}
return nil
}
177 changes: 173 additions & 4 deletions cli/cmd/uninstall/uninstall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/hashicorp/consul-k8s/cli/cmd/common"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -52,6 +53,7 @@ func TestDeletePVCs(t *testing.T) {
pvcs, err := c.kubernetes.CoreV1().PersistentVolumeClaims("default").List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, pvcs.Items, 1)
require.Equal(t, pvcs.Items[0].Name, pvc3.Name)
}

func TestDeleteSecrets(t *testing.T) {
Expand All @@ -73,15 +75,26 @@ func TestDeleteSecrets(t *testing.T) {
},
},
}
secret3 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "unrelated-test-secret3",
Labels: map[string]string{
"release": "unrelated",
},
},
}
_, err := c.kubernetes.CoreV1().Secrets("default").Create(context.Background(), secret, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.CoreV1().Secrets("default").Create(context.Background(), secret2, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.CoreV1().Secrets("default").Create(context.Background(), secret3, metav1.CreateOptions{})
require.NoError(t, err)
err = c.deleteSecrets("consul", "default")
require.NoError(t, err)
secrets, err := c.kubernetes.CoreV1().Secrets("default").List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, secrets.Items, 0)
require.Len(t, secrets.Items, 1)
require.Equal(t, secrets.Items[0].Name, secret3.Name)
}

func TestDeleteServiceAccounts(t *testing.T) {
Expand All @@ -103,15 +116,26 @@ func TestDeleteServiceAccounts(t *testing.T) {
},
},
}
sa3 := &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-sa3",
Labels: map[string]string{
"release": "unrelated",
},
},
}
_, err := c.kubernetes.CoreV1().ServiceAccounts("default").Create(context.Background(), sa, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.CoreV1().ServiceAccounts("default").Create(context.Background(), sa2, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.CoreV1().ServiceAccounts("default").Create(context.Background(), sa3, metav1.CreateOptions{})
require.NoError(t, err)
err = c.deleteServiceAccounts("consul", "default")
require.NoError(t, err)
sas, err := c.kubernetes.CoreV1().ServiceAccounts("default").List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, sas.Items, 0)
require.Len(t, sas.Items, 1)
require.Equal(t, sas.Items[0].Name, sa3.Name)
}

func TestDeleteRoles(t *testing.T) {
Expand All @@ -133,15 +157,26 @@ func TestDeleteRoles(t *testing.T) {
},
},
}
role3 := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-role3",
Labels: map[string]string{
"release": "unrelated",
},
},
}
_, err := c.kubernetes.RbacV1().Roles("default").Create(context.Background(), role, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().Roles("default").Create(context.Background(), role2, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().Roles("default").Create(context.Background(), role3, metav1.CreateOptions{})
require.NoError(t, err)
err = c.deleteRoles("consul", "default")
require.NoError(t, err)
roles, err := c.kubernetes.RbacV1().Roles("default").List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, roles.Items, 0)
require.Len(t, roles.Items, 1)
require.Equal(t, roles.Items[0].Name, role3.Name)
}

func TestDeleteRoleBindings(t *testing.T) {
Expand All @@ -163,15 +198,149 @@ func TestDeleteRoleBindings(t *testing.T) {
},
},
}
rolebinding3 := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-role3",
Labels: map[string]string{
"release": "unrelated",
},
},
}
_, err := c.kubernetes.RbacV1().RoleBindings("default").Create(context.Background(), rolebinding, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().RoleBindings("default").Create(context.Background(), rolebinding2, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().RoleBindings("default").Create(context.Background(), rolebinding3, metav1.CreateOptions{})
require.NoError(t, err)
err = c.deleteRoleBindings("consul", "default")
require.NoError(t, err)
rolebindings, err := c.kubernetes.RbacV1().RoleBindings("default").List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, rolebindings.Items, 0)
require.Len(t, rolebindings.Items, 1)
require.Equal(t, rolebindings.Items[0].Name, rolebinding3.Name)
}

func TestDeleteJobs(t *testing.T) {
c := getInitializedCommand(t)
c.kubernetes = fake.NewSimpleClientset()
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-job1",
Labels: map[string]string{
"release": "consul",
},
},
}
job2 := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-job2",
Labels: map[string]string{
"release": "consul",
},
},
}
job3 := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-job3",
Labels: map[string]string{
"release": "unrelated",
},
},
}
_, err := c.kubernetes.BatchV1().Jobs("default").Create(context.Background(), job, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.BatchV1().Jobs("default").Create(context.Background(), job2, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.BatchV1().Jobs("default").Create(context.Background(), job3, metav1.CreateOptions{})
require.NoError(t, err)
err = c.deleteJobs("consul", "default")
require.NoError(t, err)
jobs, err := c.kubernetes.BatchV1().Jobs("default").List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, jobs.Items, 1)
require.Equal(t, jobs.Items[0].Name, job3.Name)
}

func TestDeleteClusterRoles(t *testing.T) {
c := getInitializedCommand(t)
c.kubernetes = fake.NewSimpleClientset()
clusterrole := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-clusterrole1",
Labels: map[string]string{
"release": "consul",
},
},
}
clusterrole2 := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-clusterrole2",
Labels: map[string]string{
"release": "consul",
},
},
}
clusterrole3 := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-clusterrole3",
Labels: map[string]string{
"release": "unrelated",
},
},
}
_, err := c.kubernetes.RbacV1().ClusterRoles().Create(context.Background(), clusterrole, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().ClusterRoles().Create(context.Background(), clusterrole2, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().ClusterRoles().Create(context.Background(), clusterrole3, metav1.CreateOptions{})
require.NoError(t, err)
err = c.deleteClusterRoles("consul")
require.NoError(t, err)
clusterroles, err := c.kubernetes.RbacV1().ClusterRoles().List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, clusterroles.Items, 1)
require.Equal(t, clusterroles.Items[0].Name, clusterrole3.Name)
}

func TestDeleteClusterRoleBindings(t *testing.T) {
c := getInitializedCommand(t)
c.kubernetes = fake.NewSimpleClientset()
clusterrolebinding := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-clusterrolebinding1",
Labels: map[string]string{
"release": "consul",
},
},
}
clusterrolebinding2 := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-clusterrolebinding2",
Labels: map[string]string{
"release": "consul",
},
},
}
clusterrolebinding3 := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "consul-test-clusterrolebinding3",
Labels: map[string]string{
"release": "unrelated",
},
},
}
_, err := c.kubernetes.RbacV1().ClusterRoleBindings().Create(context.Background(), clusterrolebinding, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().ClusterRoleBindings().Create(context.Background(), clusterrolebinding2, metav1.CreateOptions{})
require.NoError(t, err)
_, err = c.kubernetes.RbacV1().ClusterRoleBindings().Create(context.Background(), clusterrolebinding3, metav1.CreateOptions{})
require.NoError(t, err)
err = c.deleteClusterRoleBindings("consul")
require.NoError(t, err)
clusterrolebindings, err := c.kubernetes.RbacV1().ClusterRoleBindings().List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, clusterrolebindings.Items, 1)
require.Equal(t, clusterrolebindings.Items[0].Name, clusterrolebinding3.Name)
}

// getInitializedCommand sets up a command struct for tests.
Expand Down

0 comments on commit 26b49ed

Please sign in to comment.