Skip to content

Commit f2ffb4c

Browse files
committed
add some GrantReconciler tests
1 parent 18cec73 commit f2ffb4c

7 files changed

Lines changed: 991 additions & 75 deletions

File tree

operator/src/main/java/it/aboutbits/postgresql/crd/grant/Grant.java

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,6 @@
1010
import it.aboutbits.postgresql.core.Named;
1111
import org.jspecify.annotations.NullMarked;
1212

13-
import java.util.Locale;
14-
import java.util.stream.Collectors;
15-
16-
import static org.jooq.impl.DSL.grant;
17-
import static org.jooq.impl.DSL.privilege;
18-
import static org.jooq.impl.DSL.quotedName;
19-
import static org.jooq.impl.DSL.role;
20-
2113
@NullMarked
2214
@Version("v1")
2315
@Group("postgresql.aboutbits.it")
@@ -52,30 +44,6 @@ public class Grant
5244
@Override
5345
@JsonIgnore
5446
public String getName() {
55-
var spec = getSpec();
56-
57-
var privileges = spec.getPrivileges()
58-
.stream()
59-
.map(privilege -> privilege(privilege.name().toLowerCase(Locale.ROOT)))
60-
.toList();
61-
62-
var role = role(spec.getRole());
63-
var database = spec.getDatabase();
64-
var schema = spec.getSchema();
65-
66-
var statements = spec.getObjects().stream()
67-
.map(object -> {
68-
var on = quotedName(schema, object);
69-
70-
var statement = grant(privileges).on(on).to(role);
71-
72-
return statement.getSQL() + ";";
73-
})
74-
.collect(Collectors.joining("\n"));
75-
76-
return """
77-
Statement(s) executed on database "%s":
78-
%s\
79-
""".formatted(database, statements);
47+
return ""; // Come up with something that makes sense
8048
}
8149
}

operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantReconciler.java

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package it.aboutbits.postgresql.crd.grant;
22

33
import io.fabric8.kubernetes.client.KubernetesClient;
4+
import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
45
import io.javaoperatorsdk.operator.api.reconciler.Context;
6+
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
57
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
68
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
79
import it.aboutbits.postgresql.core.BaseReconciler;
@@ -14,7 +16,9 @@
1416
import org.jspecify.annotations.NullMarked;
1517

1618
import java.util.ArrayList;
19+
import java.util.Collections;
1720
import java.util.HashSet;
21+
import java.util.Objects;
1822
import java.util.Set;
1923
import java.util.concurrent.TimeUnit;
2024

@@ -25,7 +29,7 @@
2529
@RequiredArgsConstructor
2630
public 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
);

operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantService.java

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
import org.jspecify.annotations.NullMarked;
1010
import org.jspecify.annotations.Nullable;
1111

12+
import java.util.Collections;
1213
import java.util.HashMap;
1314
import java.util.HashSet;
1415
import java.util.Map;
16+
import java.util.Objects;
1517
import java.util.Set;
1618
import java.util.stream.Collectors;
1719

@@ -40,16 +42,14 @@ public class GrantService {
4042

4143
/// Determines all existing privileges for the specified `role`, when applicable `schema`, and the given `objectTyoe`.
4244
///
43-
/// @param tx The DSLContext for database operations.
44-
/// @param resource The Grant resource containing the specification details.
45+
/// @param tx The DSLContext for database operations.
46+
/// @param spec The GrantSpec containing the specification details.
4547
/// @return A map with object names as keys and lists of GrantPrivilege as values.
4648
@SuppressWarnings("checkstyle:MethodLength")
4749
public Map<String, Set<GrantPrivilege>> determineCurrentObjectPrivileges(
4850
DSLContext tx,
49-
Grant resource
51+
GrantSpec spec
5052
) {
51-
var spec = resource.getSpec();
52-
5353
var database = spec.getDatabase();
5454
var role = spec.getRole();
5555
var schema = spec.getSchema();
@@ -113,7 +113,7 @@ public Map<String, Set<GrantPrivilege>> determineCurrentObjectPrivileges(
113113
)
114114
.fetchGroups(
115115
PG_NAMESPACE.NSPNAME,
116-
r -> r.get(PG_NAMESPACE.NSPNAME, GrantPrivilege.class)
116+
r -> r.get(ACLEXPLODE.PRIVILEGE_TYPE, GrantPrivilege.class)
117117
);
118118

119119
/*
@@ -212,23 +212,24 @@ public Map<String, Set<GrantPrivilege>> determineCurrentObjectPrivileges(
212212
/// If the `objects` List is empty, no condition is applied for object filtering,
213213
/// and thus all objects from this `namespace`/`schema` are returned.
214214
///
215-
/// @param tx the DSLContext used to execute database operations
216-
/// @param resource the Grant object containing specifications about the target database objects and privileges
215+
/// @param tx the DSLContext used to execute database operations
216+
/// @param spec the GrantSpec object containing specifications about the target database objects and privileges
217217
/// @return a map where the keys represent object names and the values indicate ownership status,
218218
/// or `null` if the object does not exist
219219
@SuppressWarnings({"checkstyle:MethodLength", "java:S3776"})
220220
public Map<String, @Nullable Boolean> determineObjectExistenceAndOwnership(
221221
DSLContext tx,
222-
Grant resource
222+
GrantSpec spec
223223
) {
224-
var spec = resource.getSpec();
225-
226224
var database = spec.getDatabase();
227225
var role = spec.getRole();
228226
var schema = spec.getSchema();
229227

230228
var objectType = spec.getObjectType();
231-
var objects = spec.getObjects();
229+
var objects = Objects.requireNonNullElse(
230+
spec.getObjects(),
231+
Collections.<String>emptySet()
232+
);
232233

233234
var objectExistenceAndOwnershipMap = HashMap.<String, @Nullable Boolean>newHashMap(
234235
(objects.isEmpty() ? 1 : objects.size()) * spec.getPrivileges().size()
@@ -398,12 +399,10 @@ public Map<String, Set<GrantPrivilege>> determineCurrentObjectPrivileges(
398399

399400
public void grant(
400401
DSLContext tx,
401-
Grant resource,
402+
GrantSpec spec,
402403
String object,
403404
Set<GrantPrivilege> privilegesToGrant
404405
) {
405-
var spec = resource.getSpec();
406-
407406
var schema = spec.getSchema();
408407
var role = role(spec.getRole());
409408
var objectType = spec.getObjectType();
@@ -430,11 +429,9 @@ public void grant(
430429

431430
public void grantOnAll(
432431
DSLContext tx,
433-
Grant resource,
432+
GrantSpec spec,
434433
Set<GrantPrivilege> privilegesToGrant
435434
) {
436-
var spec = resource.getSpec();
437-
438435
var schema = quotedName(spec.getSchema());
439436
var role = role(spec.getRole());
440437
var objectType = spec.getObjectType();
@@ -462,12 +459,10 @@ public void grantOnAll(
462459

463460
public void revoke(
464461
DSLContext tx,
465-
Grant resource,
462+
GrantSpec spec,
466463
String object,
467464
Set<GrantPrivilege> privilegesToRevoke
468465
) {
469-
var spec = resource.getSpec();
470-
471466
var schema = spec.getSchema();
472467
var role = role(spec.getRole());
473468
var objectType = spec.getObjectType();

0 commit comments

Comments
 (0)