Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup additional resources during uninstall #820

Merged
merged 9 commits into from
Nov 5, 2021
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()
kschoche marked this conversation as resolved.
Show resolved Hide resolved
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