Skip to content

Commit 5bf1ea6

Browse files
authored
feat: allow to skip the workload cluster management (#171)
1 parent 5bb9bab commit 5bf1ea6

File tree

2 files changed

+180
-46
lines changed

2 files changed

+180
-46
lines changed

lib/clusteraccess/clusteraccess.go

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type Reconciler interface {
5353
// WithWorkloadScheme sets the scheme for the Workload Kubernetes client.
5454
WithWorkloadScheme(scheme *runtime.Scheme) Reconciler
5555

56+
// SkipWorkloadCluster disables the request of a Workload cluster.
57+
SkipWorkloadCluster() Reconciler
58+
5659
// MCPCluster creates a Cluster for the MCP AccessRequest.
5760
// This function will only be successful if the MCP AccessRequest is granted and Reconcile returned without an error
5861
// and a reconcile.Result with no RequeueAfter value.
@@ -90,6 +93,7 @@ type reconcilerImpl struct {
9093
workloadRoleRefs []commonapi.RoleRef
9194
mcpScheme *runtime.Scheme
9295
workloadScheme *runtime.Scheme
96+
skipWorkloadCluster bool
9397
}
9498

9599
// NewClusterAccessReconciler creates a new ClusterAccessReconciler with the given parameters.
@@ -106,6 +110,7 @@ func NewClusterAccessReconciler(platformClusterClient client.Client, controllerN
106110
workloadRoleRefs: []commonapi.RoleRef{},
107111
mcpScheme: runtime.NewScheme(),
108112
workloadScheme: runtime.NewScheme(),
113+
skipWorkloadCluster: false,
109114
}
110115
}
111116

@@ -144,6 +149,11 @@ func (r *reconcilerImpl) WithWorkloadScheme(scheme *runtime.Scheme) Reconciler {
144149
return r
145150
}
146151

152+
func (r *reconcilerImpl) SkipWorkloadCluster() Reconciler {
153+
r.skipWorkloadCluster = true
154+
return r
155+
}
156+
147157
func (r *reconcilerImpl) MCPCluster(ctx context.Context, request reconcile.Request) (*clusters.Cluster, error) {
148158
platformNamespace, err := libutils.StableMCPNamespace(request.Name, request.Namespace)
149159
if err != nil {
@@ -245,47 +255,49 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, request reconcile.Reques
245255
return reconcile.Result{RequeueAfter: r.retryInterval}, nil
246256
}
247257

248-
// Create or update the ClusterRequest for the Workload cluster and wait until it is ready.
258+
if !r.skipWorkloadCluster {
259+
// Create or update the ClusterRequest for the Workload cluster and wait until it is ready.
249260

250-
log.Debug("Create and wait for Workload cluster request", "clusterRequestName", requestNameWorkload, "clusterRequestNamespace", requestNamespace)
261+
log.Debug("Create and wait for Workload cluster request", "clusterRequestName", requestNameWorkload, "clusterRequestNamespace", requestNamespace)
251262

252-
workloadRequest, err := ensureClusterRequest(ctx, r.platformClusterClient, requestNameWorkload, requestNamespace, clustersv1alpha1.PURPOSE_WORKLOAD, metadata)
253-
if err != nil {
254-
return reconcile.Result{}, fmt.Errorf("failed to create or update Workload ClusterRequest: %w", err)
255-
}
263+
workloadRequest, err := ensureClusterRequest(ctx, r.platformClusterClient, requestNameWorkload, requestNamespace, clustersv1alpha1.PURPOSE_WORKLOAD, metadata)
264+
if err != nil {
265+
return reconcile.Result{}, fmt.Errorf("failed to create or update Workload ClusterRequest: %w", err)
266+
}
256267

257-
if workloadRequest.Status.IsDenied() {
258-
return reconcile.Result{}, fmt.Errorf("workload ClusterRequest denied")
259-
}
268+
if workloadRequest.Status.IsDenied() {
269+
return reconcile.Result{}, fmt.Errorf("workload ClusterRequest denied")
270+
}
260271

261-
if !workloadRequest.Status.IsGranted() {
262-
log.Debug("Workload ClusterRequest is not yet granted",
263-
"clusterRequestName", requestNameWorkload, "clusterRequestNamespace", requestNamespace, "requestPhase", workloadRequest.Status.Phase)
264-
return reconcile.Result{RequeueAfter: r.retryInterval}, nil
265-
}
272+
if !workloadRequest.Status.IsGranted() {
273+
log.Debug("Workload ClusterRequest is not yet granted",
274+
"clusterRequestName", requestNameWorkload, "clusterRequestNamespace", requestNamespace, "requestPhase", workloadRequest.Status.Phase)
275+
return reconcile.Result{RequeueAfter: r.retryInterval}, nil
276+
}
266277

267-
// Create or update the AccessRequest for the Workload cluster.
278+
// Create or update the AccessRequest for the Workload cluster.
268279

269-
log.Debug("Create and wait for Workload cluster access request", "accessRequestName", requestNameWorkload, "accessRequestNamespace", requestNamespace)
280+
log.Debug("Create and wait for Workload cluster access request", "accessRequestName", requestNameWorkload, "accessRequestNamespace", requestNamespace)
270281

271-
workloadAccessRequest, err := ensureAccessRequest(ctx, r.platformClusterClient,
272-
requestNameWorkload, requestNamespace, &commonapi.ObjectReference{
273-
Name: requestNameWorkload,
274-
Namespace: requestNamespace,
275-
}, nil, r.workloadPermissions, r.workloadRoleRefs, metadata)
282+
workloadAccessRequest, err := ensureAccessRequest(ctx, r.platformClusterClient,
283+
requestNameWorkload, requestNamespace, &commonapi.ObjectReference{
284+
Name: requestNameWorkload,
285+
Namespace: requestNamespace,
286+
}, nil, r.workloadPermissions, r.workloadRoleRefs, metadata)
276287

277-
if err != nil {
278-
return reconcile.Result{}, fmt.Errorf("failed to create or update Workload AccessRequest: %w", err)
279-
}
288+
if err != nil {
289+
return reconcile.Result{}, fmt.Errorf("failed to create or update Workload AccessRequest: %w", err)
290+
}
280291

281-
if workloadAccessRequest.Status.IsDenied() {
282-
return reconcile.Result{}, fmt.Errorf("workload AccessRequest denied")
283-
}
292+
if workloadAccessRequest.Status.IsDenied() {
293+
return reconcile.Result{}, fmt.Errorf("workload AccessRequest denied")
294+
}
284295

285-
if !workloadAccessRequest.Status.IsGranted() {
286-
log.Debug("Workload AccessRequest is not yet granted",
287-
"accessRequestName", requestNameMCP, "accessRequestNamespace", requestNamespace, "requestPhase", workloadAccessRequest.Status.Phase)
288-
return reconcile.Result{RequeueAfter: r.retryInterval}, nil
296+
if !workloadAccessRequest.Status.IsGranted() {
297+
log.Debug("Workload AccessRequest is not yet granted",
298+
"accessRequestName", requestNameMCP, "accessRequestNamespace", requestNamespace, "requestPhase", workloadAccessRequest.Status.Phase)
299+
return reconcile.Result{RequeueAfter: r.retryInterval}, nil
300+
}
289301
}
290302

291303
return reconcile.Result{}, nil
@@ -300,16 +312,22 @@ func (r *reconcilerImpl) ReconcileDelete(ctx context.Context, request reconcile.
300312
requestNameMCP := StableRequestName(r.controllerName, request) + requestSuffixMCP
301313
requestNameWorkload := StableRequestName(r.controllerName, request) + requestSuffixWorkload
302314

303-
// Delete the Workload AccessRequest if it exists
304-
workloadAccessDeleted, err := deleteAccessRequest(ctx, r.platformClusterClient, requestNameWorkload, requestNamespace)
305-
if err != nil {
306-
return reconcile.Result{}, fmt.Errorf("failed to delete Workload AccessRequest: %w", err)
307-
}
315+
workloadAccessDeleted := true
316+
workloadClusterDeleted := true
317+
318+
if !r.skipWorkloadCluster {
319+
// Delete the Workload AccessRequest if it exists
320+
workloadAccessDeleted, err = deleteAccessRequest(ctx, r.platformClusterClient, requestNameWorkload, requestNamespace)
321+
if err != nil {
322+
return reconcile.Result{}, fmt.Errorf("failed to delete Workload AccessRequest: %w", err)
323+
}
324+
325+
// Delete the Workload ClusterRequest if it exists
326+
workloadClusterDeleted, err = deleteClusterRequest(ctx, r.platformClusterClient, requestNameWorkload, requestNamespace)
327+
if err != nil {
328+
return reconcile.Result{}, fmt.Errorf("failed to delete Workload ClusterRequest: %w", err)
329+
}
308330

309-
// Delete the Workload ClusterRequest if it exists
310-
workloadClusterDeleted, err := deleteClusterRequest(ctx, r.platformClusterClient, requestNameWorkload, requestNamespace)
311-
if err != nil {
312-
return reconcile.Result{}, fmt.Errorf("failed to delete Workload ClusterRequest: %w", err)
313331
}
314332

315333
// Delete the MCP AccessRequest if it exists

lib/clusteraccess/clusteraccess_test.go

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestUtils(t *testing.T) {
3737
RunSpecs(t, "ClusterAccess Test Suite")
3838
}
3939

40-
func buildTestEnvironmentReconcile(testdataDir string, objectsWitStatus ...client.Object) *testutils.Environment {
40+
func buildTestEnvironmentReconcile(testdataDir string, skipWorkloadCluster bool, objectsWitStatus ...client.Object) *testutils.Environment {
4141
scheme := runtime.NewScheme()
4242
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
4343
utilruntime.Must(clustersv1alpha1.AddToScheme(scheme))
@@ -74,6 +74,9 @@ func buildTestEnvironmentReconcile(testdataDir string, objectsWitStatus ...clien
7474
WithWorkloadPermissions(permissions).
7575
WithWorkloadRoleRefs(roleRefs).
7676
WithRetryInterval(1 * time.Second)
77+
if skipWorkloadCluster {
78+
r.SkipWorkloadCluster()
79+
}
7780
return r
7881
}).
7982
WithDynamicObjectsWithStatus(objectsWitStatus...).
@@ -88,7 +91,7 @@ func (dr *deleteReconciler) Reconcile(ctx context.Context, req reconcile.Request
8891
return dr.r.ReconcileDelete(ctx, req)
8992
}
9093

91-
func buildTestEnvironmentDelete(testdataDir string, objectsWitStatus ...client.Object) *testutils.Environment {
94+
func buildTestEnvironmentDelete(testdataDir string, skipWorkloadCluster bool, objectsWitStatus ...client.Object) *testutils.Environment {
9295
scheme := runtime.NewScheme()
9396
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
9497
utilruntime.Must(clustersv1alpha1.AddToScheme(scheme))
@@ -100,6 +103,10 @@ func buildTestEnvironmentDelete(testdataDir string, objectsWitStatus ...client.O
100103
r := clusteraccess.NewClusterAccessReconciler(c, controllerName)
101104
r.WithRetryInterval(1 * time.Second)
102105

106+
if skipWorkloadCluster {
107+
r.SkipWorkloadCluster()
108+
}
109+
103110
dr := &deleteReconciler{
104111
r: r,
105112
}
@@ -122,13 +129,15 @@ func buildTestEnvironmentNoReconcile(testdataDir string, objectsWitStatus ...cli
122129
Build()
123130
}
124131

132+
const (
133+
expectedRequestNamespace = "mcp--80158a25-6874-80a6-a75d-94f57da600c0"
134+
)
135+
125136
var _ = Describe("ClusterAccessReconciler", func() {
126137
Context("Reconcile", func() {
127138
It("should create MCP-/Workload ClusterRequests/AccessRequests", func() {
128139
var reconcileResult reconcile.Result
129140

130-
expectedRequestNamespace := "mcp--80158a25-6874-80a6-a75d-94f57da600c0"
131-
132141
request := reconcile.Request{
133142
NamespacedName: client.ObjectKey{
134143
Name: "instance",
@@ -157,7 +166,7 @@ var _ = Describe("ClusterAccessReconciler", func() {
157166
},
158167
}
159168

160-
env := buildTestEnvironmentReconcile("test-01", accessRequestMCP, clusterRequestWorkload, accessRequestWorkload)
169+
env := buildTestEnvironmentReconcile("test-01", false, accessRequestMCP, clusterRequestWorkload, accessRequestWorkload)
161170

162171
reconcileResult = env.ShouldReconcile(request, "reconcilerImpl should not return an error")
163172
Expect(reconcileResult.RequeueAfter).ToNot(BeZero(), "reconcile should requeue after a delay")
@@ -244,6 +253,85 @@ var _ = Describe("ClusterAccessReconciler", func() {
244253
Expect(workloadCluster).ToNot(BeNil(), "should return a valid Workload cluster")
245254
})
246255

256+
It("should create MCP-/Workload ClusterRequests/AccessRequests without Workload Cluster", func() {
257+
var reconcileResult reconcile.Result
258+
259+
request := reconcile.Request{
260+
NamespacedName: client.ObjectKey{
261+
Name: "instance",
262+
Namespace: "test",
263+
},
264+
}
265+
266+
accessRequestMCP := &clustersv1alpha1.AccessRequest{
267+
ObjectMeta: metav1.ObjectMeta{
268+
Name: clusteraccess.StableRequestName(controllerName, request) + "--mcp",
269+
Namespace: expectedRequestNamespace,
270+
},
271+
}
272+
273+
env := buildTestEnvironmentReconcile("test-01", true, accessRequestMCP)
274+
275+
reconcileResult = env.ShouldReconcile(request, "reconcilerImpl should not return an error")
276+
Expect(reconcileResult.RequeueAfter).ToNot(BeZero(), "reconcile should requeue after a delay")
277+
278+
// reconcile now waits until the request namespace is being created
279+
// the format if the request namespace is "ob-<onboarding-namespace>"
280+
// create the expected request namespace
281+
requestNamespace := &corev1.Namespace{
282+
ObjectMeta: metav1.ObjectMeta{
283+
Name: expectedRequestNamespace,
284+
},
285+
}
286+
287+
Expect(env.Client().Create(env.Ctx, requestNamespace)).To(Succeed())
288+
289+
// reconcile again to process the request
290+
env.ShouldReconcile(request, "reconcilerImpl should not return an error")
291+
292+
// there should be an access request for the MCP cluster created
293+
Expect(env.Client().Get(env.Ctx, client.ObjectKeyFromObject(accessRequestMCP), accessRequestMCP)).To(Succeed())
294+
295+
// set the access request status to "Granted"
296+
accessRequestMCP.Status = clustersv1alpha1.AccessRequestStatus{
297+
Status: commonapi.Status{
298+
Phase: clustersv1alpha1.REQUEST_GRANTED,
299+
},
300+
}
301+
Expect(env.Client().Status().Update(env.Ctx, accessRequestMCP)).To(Succeed())
302+
303+
// reconcile again to process the granted access request
304+
env.ShouldReconcile(request, "reconcilerImpl should not return an error")
305+
306+
// set the secret reference for the MCP access request
307+
accessRequestMCP.Status.SecretRef = &commonapi.ObjectReference{
308+
Name: "mcp-access",
309+
Namespace: expectedRequestNamespace,
310+
}
311+
Expect(env.Client().Status().Update(env.Ctx, accessRequestMCP)).To(Succeed())
312+
313+
// reconcile again to process the granted access request
314+
env.ShouldReconcile(request, "reconcilerImpl should not return an error")
315+
316+
// cast to ClusterAccessReconciler to access the reconcilerImpl methods
317+
reconciler, ok := env.Reconciler().(clusteraccess.Reconciler) // nolint:staticcheck
318+
Expect(ok).To(BeTrue(), "reconcilerImpl should be of type ClusterAccessReconciler")
319+
320+
mcpCluster, err := reconciler.MCPCluster(env.Ctx, request)
321+
Expect(err).ToNot(HaveOccurred(), "should not return an error when getting MCP cluster")
322+
Expect(mcpCluster).ToNot(BeNil(), "should return a valid MCP cluster")
323+
324+
_, err = reconciler.WorkloadCluster(env.Ctx, request)
325+
Expect(err).To(HaveOccurred(), "should return an error when trying to get the Workload cluster")
326+
327+
accessRequestList := &clustersv1alpha1.AccessRequestList{}
328+
Expect(env.Client().List(env.Ctx, accessRequestList, client.InNamespace(expectedRequestNamespace))).To(Succeed())
329+
Expect(accessRequestList.Items).To(HaveLen(1), "there should be only one access request (for the MCP cluster)")
330+
clusterRequestList := &clustersv1alpha1.ClusterRequestList{}
331+
Expect(env.Client().List(env.Ctx, clusterRequestList, client.InNamespace(expectedRequestNamespace))).To(Succeed())
332+
Expect(clusterRequestList.Items).To(BeEmpty(), "there should be no cluster request (for the Workload cluster)")
333+
})
334+
247335
Context("Delete", func() {
248336
It("should delete MCP-/Workload ClusterRequests/AccessRequests", func() {
249337
var reconcileResult reconcile.Result
@@ -278,7 +366,7 @@ var _ = Describe("ClusterAccessReconciler", func() {
278366
},
279367
}
280368

281-
env := buildTestEnvironmentDelete("test-02")
369+
env := buildTestEnvironmentDelete("test-02", false)
282370

283371
reconcileResult = env.ShouldReconcile(request, "reconcilerImpl should not return an error")
284372
Expect(reconcileResult.RequeueAfter).To(BeZero(), "reconcile should requeue after a delay")
@@ -288,6 +376,34 @@ var _ = Describe("ClusterAccessReconciler", func() {
288376
Expect(env.Client().Get(env.Ctx, client.ObjectKeyFromObject(clusterRequestWorkload), clusterRequestWorkload)).ToNot(Succeed(), "cluster request for Workload cluster should not exist")
289377
Expect(env.Client().Get(env.Ctx, client.ObjectKeyFromObject(accessRequestWorkload), accessRequestWorkload)).ToNot(Succeed(), "access request for Workload cluster should not exist")
290378
})
379+
380+
It("should delete only MCP AccessRequest with skipWorkloadCluster", func() {
381+
var reconcileResult reconcile.Result
382+
383+
expectedRequestNamespace := "mcp--80158a25-6874-80a6-a75d-94f57da600c0"
384+
385+
request := reconcile.Request{
386+
NamespacedName: client.ObjectKey{
387+
Name: "instance",
388+
Namespace: "test",
389+
},
390+
}
391+
392+
accessRequestMCP := &clustersv1alpha1.AccessRequest{
393+
ObjectMeta: metav1.ObjectMeta{
394+
Name: clusteraccess.StableRequestName(controllerName, request) + "--mcp",
395+
Namespace: expectedRequestNamespace,
396+
},
397+
}
398+
399+
env := buildTestEnvironmentDelete("test-02", true)
400+
401+
reconcileResult = env.ShouldReconcile(request, "reconcilerImpl should not return an error")
402+
Expect(reconcileResult.RequeueAfter).To(BeZero(), "reconcile should requeue after a delay")
403+
404+
// access request should be deleted
405+
Expect(env.Client().Get(env.Ctx, client.ObjectKeyFromObject(accessRequestMCP), accessRequestMCP)).ToNot(Succeed(), "access request for MCP cluster should not exist")
406+
})
291407
})
292408
})
293409
})

0 commit comments

Comments
 (0)