11package it .aboutbits .postgresql .crd .grant ;
22
33import io .fabric8 .kubernetes .client .KubernetesClient ;
4+ import io .javaoperatorsdk .operator .api .reconciler .Cleaner ;
45import io .javaoperatorsdk .operator .api .reconciler .Context ;
6+ import io .javaoperatorsdk .operator .api .reconciler .DeleteControl ;
57import io .javaoperatorsdk .operator .api .reconciler .Reconciler ;
68import io .javaoperatorsdk .operator .api .reconciler .UpdateControl ;
79import it .aboutbits .postgresql .core .BaseReconciler ;
1416import org .jspecify .annotations .NullMarked ;
1517
1618import java .util .ArrayList ;
19+ import java .util .Collections ;
1720import java .util .HashSet ;
21+ import java .util .Objects ;
1822import java .util .Set ;
1923import java .util .concurrent .TimeUnit ;
2024
2529@ RequiredArgsConstructor
2630public class GrantReconciler
2731 extends BaseReconciler <Grant , CRStatus >
28- implements Reconciler <Grant > {
32+ implements Reconciler <Grant >, Cleaner < Grant > {
2933 private final GrantService grantService ;
3034
3135 private final KubernetesClient kubernetesClient ;
@@ -114,6 +118,84 @@ public UpdateControl<Grant> reconcile(
114118 return updateControl ;
115119 }
116120
121+ @ Override
122+ public DeleteControl cleanup (
123+ Grant resource ,
124+ Context <Grant > context
125+ ) throws Exception {
126+ var spec = resource .getSpec ();
127+ var status = initializeStatus (resource );
128+
129+ var name = resource .getMetadata ().getName ();
130+ var namespace = resource .getMetadata ().getNamespace ();
131+
132+ log .info (
133+ "Deleting Grant [resource={}/{}, status.phase={}]" ,
134+ namespace ,
135+ name ,
136+ status .getPhase ()
137+ );
138+
139+ if (status .getPhase () != CRPhase .DELETING ) {
140+ status .setPhase (CRPhase .DELETING )
141+ .setMessage ("Grant deletion in progress" );
142+ }
143+
144+ var clusterRef = spec .getClusterRef ();
145+
146+ var clusterConnectionOptional = getReferencedClusterConnection (
147+ kubernetesClient ,
148+ resource ,
149+ clusterRef
150+ );
151+
152+ if (clusterConnectionOptional .isEmpty ()) {
153+ status .setMessage ("The specified ClusterConnection no longer exists or is not ready yet [resource=%s/%s]" .formatted (
154+ getResourceNamespaceOrOwn (resource , clusterRef .getNamespace ()),
155+ clusterRef .getName ()
156+ ));
157+
158+ return DeleteControl .noFinalizerRemoval ()
159+ .rescheduleAfter (60 , TimeUnit .SECONDS );
160+ }
161+
162+ var clusterConnection = clusterConnectionOptional .get ();
163+
164+ try (var dsl = contextFactory .getDSLContext (clusterConnection )) {
165+ dsl .transaction (cfg -> {
166+ var tx = cfg .dsl ();
167+
168+ var currentObjectPrivileges = grantService .determineCurrentObjectPrivileges (tx , spec );
169+
170+ for (var objectPrivileges : currentObjectPrivileges .entrySet ()) {
171+ var object = objectPrivileges .getKey ();
172+ var privileges = objectPrivileges .getValue ();
173+
174+ grantService .revoke (
175+ dsl ,
176+ spec ,
177+ object ,
178+ privileges
179+ );
180+ }
181+ });
182+
183+ return DeleteControl .defaultDelete ();
184+ } catch (Exception e ) {
185+ log .error (
186+ "Failed to delete Grant [resource={}/{}, status.phase={}]" ,
187+ namespace ,
188+ name ,
189+ status .getPhase ()
190+ );
191+
192+ status .setMessage ("Deletion failed: %s" .formatted (e .getMessage ()));
193+
194+ return DeleteControl .noFinalizerRemoval ()
195+ .rescheduleAfter (60 , TimeUnit .SECONDS );
196+ }
197+ }
198+
117199 @ Override
118200 protected CRStatus newStatus () {
119201 return new CRStatus ();
@@ -133,13 +215,18 @@ private UpdateControl<Grant> reconcileInTransaction(
133215 var schema = spec .getSchema ();
134216 var objectType = spec .getObjectType ();
135217
136- var expectedObjects = new HashSet <>(spec .getObjects ());
218+ var expectedObjects = new HashSet <>(
219+ Objects .requireNonNullElse (
220+ spec .getObjects (),
221+ Collections .emptySet ()
222+ )
223+ );
137224 var expectedPrivileges = new HashSet <>(spec .getPrivileges ());
138225
139226 var isAllMode = expectedObjects .isEmpty ();
140227
141- var currentObjectPrivileges = grantService .determineCurrentObjectPrivileges (tx , resource );
142- var ownershipMap = grantService .determineObjectExistenceAndOwnership (tx , resource );
228+ var currentObjectPrivileges = grantService .determineCurrentObjectPrivileges (tx , spec );
229+ var ownershipMap = grantService .determineObjectExistenceAndOwnership (tx , spec );
143230
144231 // Classify objects in a single pass
145232 var missingObjects = new ArrayList <String >();
@@ -183,7 +270,7 @@ private UpdateControl<Grant> reconcileInTransaction(
183270 if (!privilegesToRevoke .isEmpty ()) {
184271 grantService .revoke (
185272 tx ,
186- resource ,
273+ spec ,
187274 object ,
188275 privilegesToRevoke
189276 );
@@ -202,7 +289,7 @@ private UpdateControl<Grant> reconcileInTransaction(
202289 if (!privilegesToGrant .isEmpty ()) {
203290 grantService .grant (
204291 tx ,
205- resource ,
292+ spec ,
206293 object ,
207294 privilegesToGrant
208295 );
@@ -214,7 +301,7 @@ private UpdateControl<Grant> reconcileInTransaction(
214301 if (isAllMode ) {
215302 grantService .grantOnAll (
216303 tx ,
217- resource ,
304+ spec ,
218305 expectedPrivileges
219306 );
220307 }
@@ -238,7 +325,7 @@ private UpdateControl<Grant> reconcileInTransaction(
238325 if (!privilegesToRevoke .isEmpty ()) {
239326 grantService .revoke (
240327 tx ,
241- resource ,
328+ spec ,
242329 object ,
243330 privilegesToRevoke
244331 );
0 commit comments