Skip to content

Commit 7b33e59

Browse files
Merge pull request #2113 from hasheddan/keep-those-clusterroles
🐛 Only delete ClusterRoles and ClusterRoleBindings when Workspace finalizer is removed
2 parents 32fac8a + 434cbed commit 7b33e59

File tree

8 files changed

+119
-46
lines changed

8 files changed

+119
-46
lines changed

pkg/apis/tenancy/v1alpha1/helper/helper.go

+13
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ import (
2424
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2525

2626
"github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1"
27+
"github.com/kcp-dev/kcp/pkg/apis/tenancy/v1beta1"
2728
)
2829

30+
// IsValidCluster indicates whether a cluster is valid based on whether it
31+
// adheres to logical cluster naming requirements and is rooted at root or
32+
// system.
2933
func IsValidCluster(cluster logicalcluster.Name) bool {
3034
if !cluster.IsValid() {
3135
return false
@@ -34,9 +38,18 @@ func IsValidCluster(cluster logicalcluster.Name) bool {
3438
return cluster.HasPrefix(v1alpha1.RootCluster) || cluster.HasPrefix(logicalcluster.New("system"))
3539
}
3640

41+
// QualifiedObjectName builds a fully qualified identifier for an object
42+
// consisting of its logical cluster, namespace if applicable, and object
43+
// metadata name.
3744
func QualifiedObjectName(obj metav1.Object) string {
3845
if len(obj.GetNamespace()) > 0 {
3946
return fmt.Sprintf("%s|%s/%s", logicalcluster.From(obj), obj.GetNamespace(), obj.GetName())
4047
}
4148
return fmt.Sprintf("%s|%s", logicalcluster.From(obj), obj.GetName())
4249
}
50+
51+
// WorkspaceLabelSelector builds a label selector for objects associated with a
52+
// given workspace.
53+
func WorkspaceLabelSelector(name string) string {
54+
return fmt.Sprintf("%s=%s", v1beta1.WorkspaceNameLabel, name)
55+
}

pkg/apis/tenancy/v1alpha1/helper/helper_test.go

+47-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"testing"
2121

2222
"github.com/kcp-dev/logicalcluster/v2"
23+
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2325
)
2426

2527
func TestIsValidCluster(t *testing.T) {
@@ -60,7 +62,51 @@ func TestIsValidCluster(t *testing.T) {
6062
for _, tt := range tests {
6163
t.Run(tt.workspace, func(t *testing.T) {
6264
if got := IsValidCluster(logicalcluster.New(tt.workspace)); got != tt.valid {
63-
t.Errorf("isValid(%q) = %v, want %v", tt.workspace, got, tt.valid)
65+
t.Errorf("IsValidCluster(%q) = %v, want %v", tt.workspace, got, tt.valid)
66+
}
67+
})
68+
}
69+
}
70+
71+
func TestQualifiedObjectName(t *testing.T) {
72+
tests := []struct {
73+
obj metav1.Object
74+
name string
75+
}{
76+
{&metav1.ObjectMeta{
77+
Name: "cool-name",
78+
Annotations: map[string]string{
79+
logicalcluster.AnnotationKey: "cool-cluster",
80+
},
81+
}, "cool-cluster|cool-name"},
82+
{&metav1.ObjectMeta{
83+
Name: "cool-name",
84+
Namespace: "cool-namespace",
85+
Annotations: map[string]string{
86+
logicalcluster.AnnotationKey: "cool-cluster",
87+
},
88+
}, "cool-cluster|cool-namespace/cool-name"},
89+
}
90+
for _, tt := range tests {
91+
t.Run(tt.obj.GetName(), func(t *testing.T) {
92+
if got := QualifiedObjectName(tt.obj); got != tt.name {
93+
t.Errorf("QualifiedObjectName(%v) = %s, want %s", tt.obj, got, tt.name)
94+
}
95+
})
96+
}
97+
}
98+
99+
func TestWorkspaceLabelSelector(t *testing.T) {
100+
tests := []struct {
101+
ws string
102+
selector string
103+
}{
104+
{"cool-ws", "workspaces.kcp.dev/name=cool-ws"},
105+
}
106+
for _, tt := range tests {
107+
t.Run(tt.ws, func(t *testing.T) {
108+
if got := WorkspaceLabelSelector(tt.ws); got != tt.selector {
109+
t.Errorf("WorkspaceLabelSelector(%s) = %s, want %s", tt.ws, got, tt.selector)
64110
}
65111
})
66112
}

pkg/apis/tenancy/v1beta1/types.go

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import (
2323
conditionsv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/third_party/conditions/apis/conditions/v1alpha1"
2424
)
2525

26+
// WorkspaceNameLabel is a label indicating the workspace in which the given
27+
// object resides.
28+
const WorkspaceNameLabel = "workspaces.kcp.dev/name"
29+
2630
// Workspace defines a generic Kubernetes-cluster-like endpoint, with standard Kubernetes
2731
// discovery APIs, OpenAPI and resource API endpoints.
2832
//

pkg/reconciler/tenancy/clusterworkspacedeletion/clusterworkspace_deletion_controller.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ import (
3333
"k8s.io/apimachinery/pkg/types"
3434
"k8s.io/apimachinery/pkg/util/runtime"
3535
"k8s.io/apimachinery/pkg/util/wait"
36+
kubernetesclient "k8s.io/client-go/kubernetes"
3637
"k8s.io/client-go/metadata"
3738
"k8s.io/client-go/tools/cache"
3839
"k8s.io/client-go/util/workqueue"
3940
"k8s.io/klog/v2"
4041

4142
tenancyv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1"
43+
"github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1/helper"
4244
kcpclient "github.com/kcp-dev/kcp/pkg/client/clientset/versioned"
4345
tenancyinformers "github.com/kcp-dev/kcp/pkg/client/informers/externalversions/tenancy/v1alpha1"
4446
tenancylisters "github.com/kcp-dev/kcp/pkg/client/listers/tenancy/v1alpha1"
@@ -50,7 +52,13 @@ const (
5052
controllerName = "kcp-clusterworkspacedeletion"
5153
)
5254

55+
var (
56+
background = metav1.DeletePropagationBackground
57+
backgroudDeletion = metav1.DeleteOptions{PropagationPolicy: &background}
58+
)
59+
5360
func NewController(
61+
kubeClusterClient kubernetesclient.ClusterInterface,
5462
kcpClusterClient kcpclient.Interface,
5563
metadataClusterClient metadata.Interface,
5664
workspaceInformer tenancyinformers.ClusterWorkspaceInformer,
@@ -60,6 +68,7 @@ func NewController(
6068

6169
c := &Controller{
6270
queue: queue,
71+
kubeClusterClient: kubeClusterClient,
6372
kcpClusterClient: kcpClusterClient,
6473
metadataClusterClient: metadataClusterClient,
6574
workspaceLister: workspaceInformer.Lister(),
@@ -87,6 +96,7 @@ func NewController(
8796
type Controller struct {
8897
queue workqueue.RateLimitingInterface
8998

99+
kubeClusterClient kubernetesclient.ClusterInterface
90100
kcpClusterClient kcpclient.Interface
91101
metadataClusterClient metadata.Interface
92102

@@ -248,9 +258,23 @@ func (c *Controller) finalizeWorkspace(ctx context.Context, workspace *tenancyv1
248258
if workspace.Finalizers[i] == deletion.WorkspaceFinalizer {
249259
workspace.Finalizers = append(workspace.Finalizers[:i], workspace.Finalizers[i+1:]...)
250260

261+
clusterName := logicalcluster.From(workspace)
262+
listOpts := metav1.ListOptions{
263+
LabelSelector: helper.WorkspaceLabelSelector(workspace.Name),
264+
}
265+
266+
// TODO(hasheddan): ClusterRole and ClusterRoleBinding cleanup
267+
// should be handled by garbage collection when the controller is
268+
// implemented.
269+
if err := c.kubeClusterClient.Cluster(clusterName).RbacV1().ClusterRoles().DeleteCollection(ctx, backgroudDeletion, listOpts); err != nil && !apierrors.IsNotFound(err) {
270+
return fmt.Errorf("could not delete clusterroles for workspace %s: %w", clusterName, err)
271+
}
272+
if err := c.kubeClusterClient.Cluster(clusterName).RbacV1().ClusterRoleBindings().DeleteCollection(ctx, backgroudDeletion, listOpts); err != nil && !apierrors.IsNotFound(err) {
273+
return fmt.Errorf("could not delete clusterrolebindings for workspace %s: %w", clusterName, err)
274+
}
251275
logger.V(2).Info("removing finalizer from ClusterWorkspace")
252276
_, err := c.kcpClusterClient.TenancyV1alpha1().ClusterWorkspaces().Update(
253-
logicalcluster.WithCluster(ctx, logicalcluster.From(workspace)), workspace, metav1.UpdateOptions{})
277+
logicalcluster.WithCluster(ctx, clusterName), workspace, metav1.UpdateOptions{})
254278
return err
255279
}
256280
}

pkg/reconciler/tenancy/clusterworkspacedeletion/deletion/workspace_resource_deletor.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (d *workspacedResourcesDeleter) deleteCollection(ctx context.Context, clust
169169
opts := metav1.DeleteOptions{PropagationPolicy: &background}
170170
if err := d.metadataClusterClient.Resource(gvr).DeleteCollection(
171171
logicalcluster.WithCluster(ctx, clusterName), opts, metav1.ListOptions{}); err != nil {
172-
logger.V(5).Error(err, "unexpected deleteColection error")
172+
logger.V(5).Error(err, "unexpected deleteCollection error")
173173
return true, err
174174
}
175175

pkg/server/controllers.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,12 @@ func (s *Server) installWorkspaceDeletionController(ctx context.Context, config
312312
}
313313
return discoveryClient.ServerPreferredResources()
314314
}
315-
315+
kubeClusterClient, err := kubernetesclient.NewClusterForConfig(config)
316+
if err != nil {
317+
return err
318+
}
316319
workspaceDeletionController := clusterworkspacedeletion.NewController(
320+
kubeClusterClient,
317321
kcpClusterClient,
318322
metadataClusterClient,
319323
s.KcpSharedInformerFactory.Tenancy().V1alpha1().ClusterWorkspaces(),

pkg/virtual/workspaces/registry/rest.go

+6-24
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ import (
5353
workspaceutil "github.com/kcp-dev/kcp/pkg/virtual/workspaces/util"
5454
)
5555

56-
const (
57-
WorkspaceNameLabel string = "workspaces.kcp.dev/name"
58-
)
59-
6056
// FilteredClusterWorkspaces allows to list and watch ClusterWorkspaces
6157
// filtered by authorizaation, i.e. a user only sees those object he has access to.
6258
type FilteredClusterWorkspaces interface {
@@ -354,7 +350,7 @@ func (s *REST) Create(ctx context.Context, obj runtime.Object, createValidation
354350
ObjectMeta: metav1.ObjectMeta{
355351
Name: ownerRoleBindingName,
356352
Labels: map[string]string{
357-
WorkspaceNameLabel: workspace.Name,
353+
tenancyv1beta1.WorkspaceNameLabel: workspace.Name,
358354
},
359355
},
360356
RoleRef: rbacv1.RoleRef{
@@ -388,7 +384,7 @@ func (s *REST) Create(ctx context.Context, obj runtime.Object, createValidation
388384
ObjectMeta: metav1.ObjectMeta{
389385
Name: ownerRoleBindingName,
390386
Labels: map[string]string{
391-
WorkspaceNameLabel: workspace.Name,
387+
tenancyv1beta1.WorkspaceNameLabel: workspace.Name,
392388
},
393389
},
394390
Rules: rules,
@@ -413,26 +409,12 @@ func (s *REST) Delete(ctx context.Context, name string, deleteValidation rest.Va
413409
logger := klog.FromContext(ctx).WithValues("parent", orgClusterName, "name", name)
414410
ctx = klog.NewContext(ctx, logger)
415411

416-
errorToReturn := s.kcpClusterClient.Cluster(orgClusterName).TenancyV1alpha1().ClusterWorkspaces().Delete(ctx, name, *options)
417-
if errorToReturn != nil && !kerrors.IsNotFound(errorToReturn) {
418-
return nil, false, errorToReturn
419-
}
420-
if kerrors.IsNotFound(errorToReturn) {
421-
errorToReturn = kerrors.NewNotFound(tenancyv1beta1.Resource("workspaces"), name)
422-
}
423-
workspaceNameLabelSelector := fmt.Sprintf("%s=%s", WorkspaceNameLabel, name)
424-
if err := s.kubeClusterClient.Cluster(orgClusterName).RbacV1().ClusterRoles().DeleteCollection(ctx, *options, metav1.ListOptions{
425-
LabelSelector: workspaceNameLabelSelector,
426-
}); err != nil {
427-
logger.Error(err, "could not delete clusterroles")
428-
}
429-
if err := s.kubeClusterClient.Cluster(orgClusterName).RbacV1().ClusterRoleBindings().DeleteCollection(ctx, *options, metav1.ListOptions{
430-
LabelSelector: workspaceNameLabelSelector,
431-
}); err != nil {
432-
logger.Error(err, "could not delete clusterrolebindings")
412+
err := s.kcpClusterClient.Cluster(orgClusterName).TenancyV1alpha1().ClusterWorkspaces().Delete(ctx, name, *options)
413+
if kerrors.IsNotFound(err) {
414+
err = kerrors.NewNotFound(tenancyv1beta1.Resource("workspaces"), name)
433415
}
434416

435-
return nil, false, errorToReturn
417+
return nil, false, err
436418
}
437419

438420
type withProjection struct {

0 commit comments

Comments
 (0)