Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a121886
fix flaky tests and properly clean up resources
ThoSap Jan 26, 2026
1f7bd23
disable some slow and flaky tests (when running the whole test suite)…
ThoSap Jan 26, 2026
454a4b1
add additional CRD CEL String length validations
ThoSap Jan 26, 2026
3423a1e
add additional null checks as this check is sometimes flaky on CI
ThoSap Jan 26, 2026
d400d37
update NullAway to 0.13.1
ThoSap Jan 27, 2026
7649ee5
fix some disabled tests and test data creators
ThoSap Jan 27, 2026
491097a
use assertThatExceptionOfType instead of assertThatThrownBy
ThoSap Jan 27, 2026
793901a
fix a flaky test by cleaning additional CR
ThoSap Jan 27, 2026
950fe50
fix creators
ThoSap Jan 27, 2026
b8b6612
add comment why the manual forced cleanup was done
ThoSap Jan 27, 2026
e9448d1
add comment why the manual forced cleanup was done
ThoSap Jan 27, 2026
58ce8f8
fix oopsie
ThoSap Jan 27, 2026
9308661
extract resetEnvironment to a TestUtils method and always reset all C…
ThoSap Jan 27, 2026
8d6b540
speed up the tests (extreme facepalm) 🤦🏻
ThoSap Jan 27, 2026
423723f
remove test --fail-fast from the Makefile
ThoSap Jan 27, 2026
b785a48
0 seconds leads to PATCH race conditions
ThoSap Jan 27, 2026
0792764
100 ms also leads to PATCH race conditions
ThoSap Jan 27, 2026
9d6a45e
fix flaky tests due to a blank database name, which fails in the test…
ThoSap Jan 27, 2026
fc72b1d
Revert "100 ms also leads to PATCH race conditions"
ThoSap Jan 27, 2026
958e0f0
the TestUtil should be a final class with a private default constructor
ThoSap Jan 27, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
build-and-release:
needs: test
runs-on: ubuntu-24.04
timeout-minutes: 15
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
test:
name: Tests (PostgreSQL ${{ matrix.postgres-version }})
runs-on: ubuntu-24.04
timeout-minutes: 5
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ checkstyle = "13.0.0"
datafaker = "2.5.3"
errorProne = "2.46.0"
errorPronePlugin = "4.4.0"
nullAway = "0.13.0"
nullAway = "0.13.1"

[plugins]
# https://github.com/allegro/axion-release-plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
public class ClusterReference {
@Required
@ValidationRule(
value = "self.size() > 0",
value = "self.trim().size() > 0",
message = "The ClusterReference name must not be empty."
)
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
public class SecretRef {
@Required
@ValidationRule(
value = "self.size() > 0",
value = "self.trim().size() > 0",
message = "The SecretRef name must not be empty."
)
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ public UpdateControl<ClusterConnection> reconcile(
) {
var status = initializeStatus(resource);

var name = resource.getMetadata().getName();
var namespace = resource.getMetadata().getNamespace();

log.info(
"Reconciling ClusterConnection [resource={}/{}, status.phase={}]",
namespace,
name,
status.getPhase()
);

try (var dsl = contextFactory.getDSLContext(resource)) {
var version = dsl.fetchSingle("select version()").into(String.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
public class ClusterConnectionSpec {
@Required
@ValidationRule(
value = "self.size() > 0",
value = "self.trim().size() > 0",
message = "The ClusterConnection host must not be empty."
)
private String host = "";
Expand All @@ -30,7 +30,7 @@ public class ClusterConnectionSpec {

@Required
@ValidationRule(
value = "self.size() > 0",
value = "self.trim().size() > 0",
message = "The ClusterConnection database must not be empty."
)
private String database = "postgres";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public DeleteControl cleanup(
context.getClient().resource(resource).patchStatus();

return DeleteControl.noFinalizerRemoval()
.rescheduleAfter(1, TimeUnit.SECONDS);
.rescheduleAfter(100, TimeUnit.MILLISECONDS);
}

// We do not actually delete the database if the reclaimPolicy is set to RETAIN, we only delete the CR instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class DatabaseSpec {
message = "The Database name is immutable. Allowing to rename the Database name using 'alter database <old_name> rename to <new_name>' would add unwanted side-effects to the Operator."
)
@ValidationRule(
value = "self.size() > 0",
value = "self.trim().size() > 0",
message = "The Database name must not be empty."
)
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public DeleteControl cleanup(
context.getClient().resource(resource).patchStatus();

return DeleteControl.noFinalizerRemoval()
.rescheduleAfter(1, TimeUnit.SECONDS);
.rescheduleAfter(100, TimeUnit.MILLISECONDS);
}

var clusterRef = spec.getClusterRef();
Expand All @@ -164,9 +164,10 @@ public DeleteControl cleanup(
.rescheduleAfter(60, TimeUnit.SECONDS);
}

var database = spec.getDatabase();
var clusterConnection = clusterConnectionOptional.get();

try (var dsl = contextFactory.getDSLContext(clusterConnection)) {
try (var dsl = contextFactory.getDSLContext(clusterConnection, database)) {
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup method now connects to a specific database rather than the default connection. If the database doesn't exist or is inaccessible during cleanup, this could cause the cleanup to fail. Consider handling potential connection failures gracefully.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already handle any errors in the CRD and show the error in the CRStatus and logs.
I don't think there's a graceful way to handle a missing database when it was deleted from the RDBMS.

dsl.transaction(cfg -> {
var tx = cfg.dsl();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public Set<Privilege> determineCurrentDefaultPrivileges(
DSLContext tx,
DefaultPrivilegeSpec spec
) {
var owner = spec.getOwner();
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The owner variable is extracted but not used in the subsequent code logic. The owner filter condition is added later in the WHERE clause but references spec.getOwner() directly. This variable declaration appears redundant.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@ThoSap ThoSap Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WTF is wrong with you! 😂😂😂😂
What is the newly added usage a few lines below then?

var role = spec.getRole();
var schema = spec.getSchema();

Expand All @@ -65,6 +66,11 @@ public Set<Privilege> determineCurrentDefaultPrivileges(
.from(PG_DEFAULT_ACL)
.crossJoin(Routines.aclexplode(PG_DEFAULT_ACL.DEFACLACL))
.where(
PG_DEFAULT_ACL.DEFACLROLE.eq(field(
ROLE_OID_SQL,
OID_DATA_TYPE,
val(owner)
)),
PG_DEFAULT_ACL.DEFACLOBJTYPE.eq(objectType.objectTypeChar()),
objectType == SCHEMA
? noCondition()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public DeleteControl cleanup(
context.getClient().resource(resource).patchStatus();

return DeleteControl.noFinalizerRemoval()
.rescheduleAfter(1, TimeUnit.SECONDS);
.rescheduleAfter(100, TimeUnit.MILLISECONDS);
}

var clusterRef = spec.getClusterRef();
Expand Down Expand Up @@ -213,7 +213,7 @@ protected CRStatus newStatus() {
return new CRStatus();
}

@SuppressWarnings("java:S3776")
@SuppressWarnings({"checkstyle:MethodLength", "java:S3776"})
private UpdateControl<Grant> reconcileInTransaction(
DSLContext tx,
Grant resource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public DeleteControl cleanup(
context.getClient().resource(resource).patchStatus();

return DeleteControl.noFinalizerRemoval()
.rescheduleAfter(1, TimeUnit.SECONDS);
.rescheduleAfter(100, TimeUnit.MILLISECONDS);
}

var clusterRef = spec.getClusterRef();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class RoleSpec {
message = "The Role name is immutable. Allowing to rename the Role name using 'alter role <old_name> rename to <new_name>' would add unwanted side-effects to the Operator."
)
@ValidationRule(
value = "self.size() > 0",
value = "self.trim().size() > 0",
message = "The Role name must not be empty."
)
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public DeleteControl cleanup(
context.getClient().resource(resource).patchStatus();

return DeleteControl.noFinalizerRemoval()
.rescheduleAfter(1, TimeUnit.SECONDS);
.rescheduleAfter(100, TimeUnit.MILLISECONDS);
}

// We do not actually delete the schema if the reclaimPolicy is set to RETAIN, we only delete the CR instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class SchemaSpec {
message = "The Schema name is immutable. Allowing to rename the Schema name using 'alter schema <old_name> rename to <new_name>' would add unwanted side-effects to the Operator."
)
@ValidationRule(
value = "self.size() > 0",
value = "self.trim().size() > 0",
message = "The Schema name must not be empty."
)
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import io.fabric8.kubernetes.client.KubernetesClient;
import io.quarkus.test.junit.QuarkusTest;
import it.aboutbits.postgresql._support.testdata.base.TestUtil;
import it.aboutbits.postgresql._support.testdata.persisted.Given;
import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection;
import jakarta.inject.Inject;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;
Expand All @@ -12,7 +12,6 @@
import org.junit.jupiter.api.Test;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -30,10 +29,8 @@ class PostgreSQLInstanceReadinessCheckTest {
KubernetesClient kubernetesClient;

@BeforeEach
void cleanUp() {
kubernetesClient.resources(ClusterConnection.class)
.withTimeout(5, TimeUnit.SECONDS)
.delete();
void resetEnvironment() {
TestUtil.resetEnvironment(kubernetesClient);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public static String randomKubernetesNameSuffix(String name) {
}

var separator = "-";
var suffixLength = maxLength - name.length() - separator.length();
var availableSpace = maxLength - name.length() - separator.length();
var suffixLength = Math.min(7, availableSpace);
Comment on lines +65 to +66
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If availableSpace is negative (when name + separator exceeds maxLength), this will result in a negative value being passed to the regex pattern on line 68, causing a runtime exception. Add validation to handle cases where the name is too long.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@ThoSap ThoSap Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you talking about, that logic is already present a few lines higher...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad BOT!


return name + separator + FAKER.regexify("[a-z0-9]{%d}".formatted(suffixLength));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package it.aboutbits.postgresql._support.testdata.base;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection;
import it.aboutbits.postgresql.crd.database.Database;
import it.aboutbits.postgresql.crd.defaultprivilege.DefaultPrivilege;
import it.aboutbits.postgresql.crd.grant.Grant;
import it.aboutbits.postgresql.crd.role.Role;
import it.aboutbits.postgresql.crd.schema.Schema;
import org.jspecify.annotations.NullMarked;

import java.util.concurrent.TimeUnit;

import static org.awaitility.Awaitility.await;

@NullMarked
public final class TestUtil {
public static void resetEnvironment(KubernetesClient kubernetesClient) {
// Reverse Dependency Deletion
deleteResource(kubernetesClient, DefaultPrivilege.class);
deleteResource(kubernetesClient, Grant.class);
deleteResource(kubernetesClient, Schema.class);
deleteResource(kubernetesClient, Role.class);
deleteResource(kubernetesClient, Database.class);
deleteResource(kubernetesClient, ClusterConnection.class);
}

public static void deleteResource(
KubernetesClient kubernetesClient,
Class<? extends HasMetadata> resourceClass
) {
// Async call to the Kubernetes API Server that triggers the Operator Controllers
kubernetesClient.resources(resourceClass).delete();

// Wait for the Operator Controller to finish the CR cleanup
await().atMost(5, TimeUnit.SECONDS)
.pollInterval(100, TimeUnit.MILLISECONDS)
.until(() -> kubernetesClient.resources(resourceClass).list().getItems().isEmpty());
}

private TestUtil() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,15 @@ public RoleCreate role() {
public DatabaseCreate database() {
return new DatabaseCreate(
numberOfItems,
given,
kubernetesClient
);
}

public SchemaCreate schema() {
return new SchemaCreate(
numberOfItems,
given,
kubernetesClient
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ protected ClusterConnection create(int index) {
.inNamespace(namespace)
.withName(name)
.waitUntilCondition(
clusterConnection -> clusterConnection.getStatus() != null,
clusterConnection -> clusterConnection != null && clusterConnection.getStatus() != null,
5,
TimeUnit.SECONDS
);
Expand All @@ -116,38 +116,48 @@ private String getNamespace() {
return withNamespace;
}

return kubernetesClient.getNamespace();
withNamespace = kubernetesClient.getNamespace();

return withNamespace;
}

private String getName() {
if (withName != null) {
return withName;
}

return randomKubernetesNameSuffix("test-cluster-connection");
withName = randomKubernetesNameSuffix("test-cluster-connection");

return withName;
}

private String getHost() {
if (withHost != null) {
return withHost;
}

return "localhost";
withHost = "localhost";

return withHost;
}

private int getPort() {
if (withPort != null) {
return withPort;
}

return dbConnectionDetails.port();
withPort = dbConnectionDetails.port();

return withPort;
}

private String getDatabase() {
return Objects.requireNonNullElse(
withDatabase = Objects.requireNonNullElse(
withDatabase,
"postgres"
);

return withDatabase;
}

private SecretRef getAdminSecretRef() {
Expand Down
Loading