Skip to content

Commit 69fcdfa

Browse files
committed
test if the boolean role flags work
1 parent b35354f commit 69fcdfa

4 files changed

Lines changed: 168 additions & 51 deletions

File tree

src/main/java/it/aboutbits/postgresql/crd/role/RoleReconciler.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
import it.aboutbits.postgresql.core.PostgreSQLAuthenticationUtil;
1919
import it.aboutbits.postgresql.core.PostgreSQLContextFactory;
2020
import lombok.RequiredArgsConstructor;
21+
import lombok.extern.slf4j.Slf4j;
2122
import org.jspecify.annotations.NonNull;
2223

2324
import java.sql.SQLException;
2425
import java.util.List;
2526
import java.util.concurrent.TimeUnit;
2627
import java.util.stream.Collectors;
2728

29+
@Slf4j
2830
@RequiredArgsConstructor
2931
public class RoleReconciler
3032
extends BaseReconciler<Role, CRStatus>
@@ -40,6 +42,15 @@ public UpdateControl<Role> reconcile(
4042
var spec = resource.getSpec();
4143
var status = initializeStatus(resource);
4244

45+
var name = resource.getMetadata().getName();
46+
var namespace = resource.getMetadata().getNamespace();
47+
48+
log.info("Reconciling Role [resource={}/{}, status.phase={}]",
49+
namespace,
50+
name,
51+
status.getPhase()
52+
);
53+
4354
var clusterRef = spec.getClusterRef();
4455
var expectedFlags = spec.getFlags();
4556

@@ -74,7 +85,7 @@ public UpdateControl<Role> reconcile(
7485
password = KubernetesUtil.getSecretRefCredentials(
7586
kubernetesClient,
7687
passwordSecretRef,
77-
resource.getMetadata().getNamespace()
88+
namespace
7889
).password();
7990
} else {
8091
password = null;
@@ -91,6 +102,11 @@ public UpdateControl<Role> reconcile(
91102

92103
// Create the role if it doesn't exist yet
93104
if (!RoleUtil.roleExists(tx, spec)) {
105+
log.info("Creating Role [resource={}/{}]",
106+
namespace,
107+
name
108+
);
109+
94110
RoleUtil.createRole(
95111
tx,
96112
spec,
@@ -117,26 +133,42 @@ public UpdateControl<Role> reconcile(
117133
}
118134

119135
if (roleLoginMatches && passwordMatches && flagsMatch) {
136+
log.info("Role up-to-date [resource={}/{}]",
137+
namespace,
138+
name
139+
);
140+
120141
return UpdateControl.noUpdate();
121142
}
122143

123144
var changePassword = loginExpected && !passwordMatches;
124145

146+
log.info("Updating Role flags [resource={}/{}]",
147+
namespace,
148+
name
149+
);
150+
125151
RoleUtil.alterRole(
126152
tx,
127153
spec,
128154
changePassword,
129155
password
130156
);
131157

158+
log.info("Updating Role membership [resource={}/{}]",
159+
namespace,
160+
name
161+
);
162+
132163
RoleUtil.reconcileRoleMembership(
133164
tx,
134165
spec,
135166
expectedFlags,
136167
currentFlags
137168
);
138169

139-
status.setPhase(CRPhase.READY);
170+
status.setPhase(CRPhase.READY)
171+
.setMessage(null);
140172

141173
return UpdateControl.patchStatus(resource);
142174
});

src/main/java/it/aboutbits/postgresql/crd/role/RoleUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ private static Query buildCreateRole(
221221

222222
return query(
223223
"create role {0}{1}",
224-
name(roleName),
224+
role(roleName),
225225
optionsSql
226226
);
227227
}
@@ -288,7 +288,7 @@ private static Query buildAlterRole(
288288

289289
return query(
290290
"alter role {0} with {1}",
291-
name(roleName),
291+
role(roleName),
292292
joinWithSpaces(options)
293293
);
294294
}

src/main/resources/application-test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ quarkus:
22
operator-sdk:
33
activate-leader-election-for-profiles:
44
- test
5+
log:
6+
category:
7+
"org.jooq":
8+
level: DEBUG

src/test/java/it/aboutbits/postgresql/crd/role/RoleReconcilerTest.java

Lines changed: 128 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,24 @@
1313
import it.aboutbits.postgresql.core.SecretRef;
1414
import lombok.RequiredArgsConstructor;
1515
import org.jooq.DSLContext;
16+
import org.jooq.Field;
1617
import org.junit.jupiter.api.DisplayName;
1718
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.params.ParameterizedTest;
20+
import org.junit.jupiter.params.provider.Arguments;
21+
import org.junit.jupiter.params.provider.MethodSource;
1822

23+
import java.sql.SQLException;
1924
import java.time.OffsetDateTime;
2025
import java.time.ZoneOffset;
2126
import java.time.temporal.ChronoUnit;
2227
import java.util.concurrent.TimeUnit;
28+
import java.util.function.BiConsumer;
29+
import java.util.function.Predicate;
30+
import java.util.stream.Stream;
2331

2432
import static it.aboutbits.postgresql.core.KubernetesUtil.SECRET_DATA_BASIC_AUTH_PASSWORD_KEY;
33+
import static it.aboutbits.postgresql.core.infrastructure.persistence.Tables.PG_AUTHID;
2534
import static org.assertj.core.api.Assertions.assertThat;
2635
import static org.assertj.core.api.Assertions.within;
2736
import static org.awaitility.Awaitility.await;
@@ -161,7 +170,7 @@ void createRole_withMissingClusterConnection_setsPending() {
161170
@DisplayName(
162171
"When a Role (LOGIN) references a secret and that secret changes, it should trigger a re-reconciliation"
163172
)
164-
void secretChange_triggersReconciliation() throws Exception {
173+
void secretChange_triggersReconciliation() throws SQLException {
165174
// given
166175
var clusterConnection = given.one()
167176
.clusterConnection()
@@ -200,17 +209,11 @@ void secretChange_triggersReconciliation() throws Exception {
200209
// Wait for password to match because reconciliation might take a bit
201210
await().atMost(10, TimeUnit.SECONDS)
202211
.pollInterval(500, TimeUnit.MILLISECONDS)
203-
.until(() -> {
204-
try {
205-
return PostgreSQLAuthenticationUtil.passwordMatches(
206-
dsl,
207-
reconciled.getSpec(),
208-
initialPassword
209-
);
210-
} catch (Exception e) {
211-
return false;
212-
}
213-
});
212+
.until(() -> PostgreSQLAuthenticationUtil.passwordMatches(
213+
dsl,
214+
reconciled.getSpec(),
215+
initialPassword
216+
));
214217

215218
// when: update secret
216219
secret.getMetadata().setManagedFields(null);
@@ -226,24 +229,18 @@ void secretChange_triggersReconciliation() throws Exception {
226229
// then: password should eventually match the new one
227230
await().atMost(10, TimeUnit.SECONDS)
228231
.pollInterval(500, TimeUnit.MILLISECONDS)
229-
.until(() -> {
230-
try {
231-
return PostgreSQLAuthenticationUtil.passwordMatches(
232-
dsl,
233-
reconciled.getSpec(),
234-
newPassword
235-
);
236-
} catch (Exception e) {
237-
return false;
238-
}
239-
});
232+
.until(() -> PostgreSQLAuthenticationUtil.passwordMatches(
233+
dsl,
234+
reconciled.getSpec(),
235+
newPassword
236+
));
240237
}
241238

242239
@Test
243240
@DisplayName(
244241
"When a Role (LOGIN) changes its secret reference, it should trigger a re-reconciliation"
245242
)
246-
void secretRefChange_triggersReconciliation() throws Exception {
243+
void secretRefChange_triggersReconciliation() throws SQLException {
247244
// given
248245
var clusterConnection = given.one()
249246
.clusterConnection()
@@ -282,17 +279,11 @@ void secretRefChange_triggersReconciliation() throws Exception {
282279
var finalRole = reconciled;
283280
await().atMost(10, TimeUnit.SECONDS)
284281
.pollInterval(500, TimeUnit.MILLISECONDS)
285-
.until(() -> {
286-
try {
287-
return PostgreSQLAuthenticationUtil.passwordMatches(
288-
dsl,
289-
finalRole.getSpec(),
290-
initialPassword
291-
);
292-
} catch (Exception e) {
293-
return false;
294-
}
295-
});
282+
.until(() -> PostgreSQLAuthenticationUtil.passwordMatches(
283+
dsl,
284+
finalRole.getSpec(),
285+
initialPassword
286+
));
296287

297288
// when: update secret reference in the Role
298289
role.getSpec().setPasswordSecretRef(newSecretRef);
@@ -303,20 +294,110 @@ void secretRefChange_triggersReconciliation() throws Exception {
303294
var updatedRole = reconciled;
304295
await().atMost(10, TimeUnit.SECONDS)
305296
.pollInterval(500, TimeUnit.MILLISECONDS)
306-
.until(() -> {
307-
try {
308-
return PostgreSQLAuthenticationUtil.passwordMatches(
309-
dsl,
310-
updatedRole.getSpec(),
311-
newPassword
312-
);
313-
} catch (Exception e) {
314-
return false;
315-
}
316-
});
297+
.until(() -> PostgreSQLAuthenticationUtil.passwordMatches(
298+
dsl,
299+
updatedRole.getSpec(),
300+
newPassword
301+
));
302+
}
303+
304+
@ParameterizedTest
305+
@MethodSource("provideBooleanFlags")
306+
@DisplayName("When a boolean Role flag is toggled, it should be updated in the database")
307+
void roleFlag_togglesCorrectly(
308+
Field<Boolean> field,
309+
BiConsumer<RoleSpec.Flags, Boolean> setter
310+
) throws SQLException {
311+
// given
312+
var clusterConnection = given.one()
313+
.clusterConnection()
314+
.withName("test-role-flags")
315+
.returnFirst();
316+
317+
var dsl = postgreSQLContextFactory.getDSLContext(clusterConnection);
318+
319+
var roleName = "test-role-" + field.getName();
320+
321+
var role = buildRole(
322+
roleName,
323+
clusterConnection.getMetadata().getName(),
324+
/*login*/ false
325+
);
326+
327+
// 1. Enable flag (true)
328+
setter.accept(
329+
role.getSpec().getFlags(),
330+
true
331+
);
332+
333+
// when
334+
var reconciled = applyRole(role);
335+
var initialGeneration = reconciled.getStatus().getObservedGeneration();
336+
337+
// then
338+
assertThat(
339+
getRoleFlagValue(
340+
dsl,
341+
roleName,
342+
field
343+
)
344+
).isTrue();
345+
346+
// 2. Disable flag (false)
347+
setter.accept(
348+
role.getSpec().getFlags(),
349+
false
350+
);
351+
352+
// when
353+
applyRole(
354+
role,
355+
r -> r.getStatus().getObservedGeneration() == initialGeneration + 1
356+
);
357+
358+
// then
359+
assertThat(
360+
getRoleFlagValue(
361+
dsl,
362+
roleName,
363+
field
364+
)
365+
).isFalse();
366+
}
367+
368+
private Boolean getRoleFlagValue(
369+
DSLContext dsl,
370+
String roleName,
371+
Field<Boolean> field
372+
) {
373+
return dsl.select(field)
374+
.from(PG_AUTHID)
375+
.where(PG_AUTHID.ROLNAME.eq(roleName))
376+
.fetchSingleInto(field.getType());
377+
}
378+
379+
private static Stream<Arguments> provideBooleanFlags() {
380+
return Stream.of(
381+
Arguments.of(PG_AUTHID.ROLSUPER, (BiConsumer<RoleSpec.Flags, Boolean>) RoleSpec.Flags::setSuperuser),
382+
Arguments.of(PG_AUTHID.ROLCREATEDB, (BiConsumer<RoleSpec.Flags, Boolean>) RoleSpec.Flags::setCreatedb),
383+
Arguments.of(PG_AUTHID.ROLCREATEROLE, (BiConsumer<RoleSpec.Flags, Boolean>) RoleSpec.Flags::setCreaterole),
384+
Arguments.of(PG_AUTHID.ROLINHERIT, (BiConsumer<RoleSpec.Flags, Boolean>) RoleSpec.Flags::setInherit),
385+
Arguments.of(PG_AUTHID.ROLREPLICATION, (BiConsumer<RoleSpec.Flags, Boolean>) RoleSpec.Flags::setReplication),
386+
Arguments.of(PG_AUTHID.ROLBYPASSRLS, (BiConsumer<RoleSpec.Flags, Boolean>) RoleSpec.Flags::setBypassrls)
387+
);
317388
}
318389

319390
private Role applyRole(Role role) {
391+
return applyRole(
392+
role,
393+
r -> r.getStatus() != null
394+
);
395+
}
396+
397+
private Role applyRole(
398+
Role role,
399+
Predicate<Role> condition
400+
) {
320401
var namespace = kubernetesClient.getNamespace();
321402

322403
kubernetesClient.resources(Role.class)
@@ -328,7 +409,7 @@ private Role applyRole(Role role) {
328409
.inNamespace(namespace)
329410
.withName(role.getName())
330411
.waitUntilCondition(
331-
r -> r.getStatus() != null,
412+
condition,
332413
10,
333414
TimeUnit.SECONDS
334415
);

0 commit comments

Comments
 (0)