Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions pkg/serviceprovider/apireconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,11 @@ func (r *APIReconciler[T, C]) Reconcile(ctx context.Context, req ctrl.Request) (
if err := r.onboardingCluster.Client().Get(ctx, req.NamespacedName, obj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Skip reconciliation if annotation is set
if obj.GetAnnotations()[apiconst.OperationAnnotation] == apiconst.OperationAnnotationValueIgnore {
skip, err := r.handleOperationAnnotation(ctx, obj)
if err != nil {
return ctrl.Result{}, err
}
if skip {
l.Info("Skipping resource due to ignore operation annotation")
return ctrl.Result{}, nil
}
Expand Down Expand Up @@ -221,6 +224,29 @@ func (r *APIReconciler[T, C]) Reconcile(ctx context.Context, req ctrl.Request) (
}, nil
}

func (r *APIReconciler[T, C]) handleOperationAnnotation(ctx context.Context, obj T) (bool, error) {
annotations := obj.GetAnnotations()
if annotations == nil {
return false, nil
}
op, ok := annotations[apiconst.OperationAnnotation]
if !ok {
return false, nil
}
l := logf.FromContext(ctx)
switch op {
case apiconst.OperationAnnotationValueIgnore:
return true, nil
case apiconst.OperationAnnotationValueReconcile:
l.Info("Reconciliation requested via annotation. Removing annotation and proceeding with reconciliation")
if err := controllerutil2.EnsureAnnotation(ctx, r.onboardingCluster.Client(), obj, apiconst.OperationAnnotation, "", true, controllerutil2.DELETE); err != nil {
l.Error(err, "Failed to remove operation annotation")
return false, err
}
}
return false, nil
}

func (r *APIReconciler[T, PC]) updateStatus(ctx context.Context, newObj T, oldObj T) error {
if equality.Semantic.DeepEqual(oldObj.GetStatus(), newObj.GetStatus()) {
return nil
Expand Down
58 changes: 58 additions & 0 deletions pkg/serviceprovider/apireconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,27 @@ func assertStatusUpdate(t *testing.T, c client.Client, req ctrl.Request, wantSta
assert.Equal(t, wantStatusPhase, status.Phase)
}

func assertReconcileAnnotationRemoved(t *testing.T, c client.Client, req ctrl.Request) {
t.Helper()
obj := &fakeApiImpl{}
obj.SetName(req.Name)
obj.SetNamespace(req.Namespace)
require.NoError(t, c.Get(context.Background(), client.ObjectKeyFromObject(obj), obj))
_, hasAnnotation := obj.GetAnnotations()[apiconst.OperationAnnotation]
assert.False(t, hasAnnotation, "Operation annotation should have been removed")
}

func assertIgnoreAnnotationPersisted(t *testing.T, c client.Client, req ctrl.Request) {
t.Helper()
obj := &fakeApiImpl{}
obj.SetName(req.Name)
obj.SetNamespace(req.Namespace)
require.NoError(t, c.Get(context.Background(), client.ObjectKeyFromObject(obj), obj))
_, hasAnnotation := obj.GetAnnotations()[apiconst.OperationAnnotation]
assert.True(t, hasAnnotation, "Operation annotation should have been persisted")
assert.Equal(t, apiconst.OperationAnnotationValueIgnore, obj.GetAnnotations()[apiconst.OperationAnnotation], "Operation annotation value should be 'ignore'")
}

var _ clusteraccess.Provider = FakeClusterAccessProvider{}
var _ Reconciler[*fakeApiImpl, *fakeProviderConfigImpl] = &MockServiceProviderReconciler{}

Expand Down Expand Up @@ -698,6 +719,33 @@ func TestAPIReconciler_Reconcile_Advanced(t *testing.T) {
wantReconciliation: false,
wantErr: false,
},
{
name: "Operation annotation reconcile -> remove annotation and proceed with reconciliation",
apiObj: &fakeApiImpl{
ObjectMeta: metav1.ObjectMeta{
Name: testObjectName,
Namespace: testNamespaceName,
Annotations: map[string]string{
apiconst.OperationAnnotation: apiconst.OperationAnnotationValueReconcile,
},
},
},
req: ctrl.Request{
NamespacedName: types.NamespacedName{
Name: testObjectName,
Namespace: testNamespaceName,
},
},
providerConfig: &fakeProviderConfigImpl{
ObjectMeta: metav1.ObjectMeta{
Name: testObjectName,
},
},
want: ctrl.Result{},
wantStatusPhase: StatusPhaseReady,
wantReconciliation: true,
wantErr: false,
},
{
name: "cluster access reconciler fails -> error and status update",
apiObj: &fakeApiImpl{
Expand Down Expand Up @@ -809,6 +857,16 @@ func TestAPIReconciler_Reconcile_Advanced(t *testing.T) {
Name: testWorkloadKubeconfig,
}, mockReconciler.clusterContext.WorkloadAccessSecretKey)
assertStatusUpdate(t, onboardingCluster.Client(), tt.req, tt.wantStatusPhase)

// For annotation reconcile case, verify the annotation was persisted
if tt.name == "Operation annotation ignore -> no reconciliation, no requeue" {
assertIgnoreAnnotationPersisted(t, onboardingCluster.Client(), tt.req)
}

// For annotation reconcile case, verify the annotation was removed
if tt.name == "Operation annotation reconcile -> remove annotation and proceed with reconciliation" {
assertReconcileAnnotationRemoved(t, onboardingCluster.Client(), tt.req)
}
})
}
}
Expand Down