From 0d61233a1bb2fea8417c5874c360ef143fb5d54f Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Thu, 22 Jan 2026 16:59:08 +0100 Subject: [PATCH 01/21] configure profiles with related compose dev services to test against PostgreSQL 15 to 18 --- Makefile | 17 +++++++++++++++-- .../docker/compose-deviservices-test-pg15.yml | 19 +++++++++++++++++++ .../docker/compose-deviservices-test-pg16.yml | 19 +++++++++++++++++++ .../docker/compose-deviservices-test-pg17.yml | 19 +++++++++++++++++++ .../docker/compose-deviservices-test-pg18.yml | 19 +++++++++++++++++++ .../aboutbits/postgresql/core/Privilege.java | 4 ++-- .../DefaultPrivilegeObjectType.java | 5 ++--- .../DefaultPrivilegeSpec.java | 1 - .../postgresql/crd/grant/GrantObjectType.java | 5 ++--- .../postgresql/crd/grant/GrantSpec.java | 1 - .../main/resources/application-test-pg15.yml | 7 +++++++ .../main/resources/application-test-pg16.yml | 7 +++++++ .../main/resources/application-test-pg17.yml | 7 +++++++ .../main/resources/application-test-pg18.yml | 7 +++++++ operator/src/main/resources/application.yml | 2 +- 15 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 operator/src/main/docker/compose-deviservices-test-pg15.yml create mode 100644 operator/src/main/docker/compose-deviservices-test-pg16.yml create mode 100644 operator/src/main/docker/compose-deviservices-test-pg17.yml create mode 100644 operator/src/main/docker/compose-deviservices-test-pg18.yml create mode 100644 operator/src/main/resources/application-test-pg15.yml create mode 100644 operator/src/main/resources/application-test-pg16.yml create mode 100644 operator/src/main/resources/application-test-pg17.yml create mode 100644 operator/src/main/resources/application-test-pg18.yml diff --git a/Makefile b/Makefile index d54c07c..2c034c7 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,21 @@ run: generate-jooq: ./gradlew --console=colored :generated:jooqCodegen +# Latest PostgreSQL Version configured in application.yml test: - ./gradlew --console=colored :operator:clean :operator:test + ./gradlew --console=colored :operator:clean :operator:test --rerun-tasks + +test-pg18: + ./gradlew --console=colored :operator:clean :operator:test --rerun-tasks -Dquarkus.test.profile=test-pg18 + +test-pg17: + ./gradlew --console=colored :operator:clean :operator:test --rerun-tasks -Dquarkus.test.profile=test-pg17 + +test-pg16: + ./gradlew --console=colored :operator:clean :operator:test --rerun-tasks -Dquarkus.test.profile=test-pg16 + +test-pg15: + ./gradlew --console=colored :operator:clean :operator:test --rerun-tasks -Dquarkus.test.profile=test-pg15 # Flag targets as phony, to tell `make` that these are no file targets -.PHONY: init install run generate-jooq test +.PHONY: init install run generate-jooq test test-pg18 test-pg17 test-pg16 test-pg15 diff --git a/operator/src/main/docker/compose-deviservices-test-pg15.yml b/operator/src/main/docker/compose-deviservices-test-pg15.yml new file mode 100644 index 0000000..9774f50 --- /dev/null +++ b/operator/src/main/docker/compose-deviservices-test-pg15.yml @@ -0,0 +1,19 @@ +services: + db: + image: postgres:15 + command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + tmpfs: + - /var/lib/postgresql/data + healthcheck: + test: pg_isready -U root -d dummy + interval: 3s + timeout: 3s + retries: 3 + ports: + - "5432" + labels: + io.quarkus.devservices.compose.config_map.port.5432: quarkus.datasource.jdbc.port + environment: + - POSTGRES_USER=root + - POSTGRES_PASSWORD=password + - POSTGRES_DB=dummy diff --git a/operator/src/main/docker/compose-deviservices-test-pg16.yml b/operator/src/main/docker/compose-deviservices-test-pg16.yml new file mode 100644 index 0000000..7fafe58 --- /dev/null +++ b/operator/src/main/docker/compose-deviservices-test-pg16.yml @@ -0,0 +1,19 @@ +services: + db: + image: postgres:16 + command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + tmpfs: + - /var/lib/postgresql/data + healthcheck: + test: pg_isready -U root -d dummy + interval: 3s + timeout: 3s + retries: 3 + ports: + - "5432" + labels: + io.quarkus.devservices.compose.config_map.port.5432: quarkus.datasource.jdbc.port + environment: + - POSTGRES_USER=root + - POSTGRES_PASSWORD=password + - POSTGRES_DB=dummy diff --git a/operator/src/main/docker/compose-deviservices-test-pg17.yml b/operator/src/main/docker/compose-deviservices-test-pg17.yml new file mode 100644 index 0000000..5499609 --- /dev/null +++ b/operator/src/main/docker/compose-deviservices-test-pg17.yml @@ -0,0 +1,19 @@ +services: + db: + image: postgres:17 + command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + tmpfs: + - /var/lib/postgresql/data + healthcheck: + test: pg_isready -U root -d dummy + interval: 3s + timeout: 3s + retries: 3 + ports: + - "5432" + labels: + io.quarkus.devservices.compose.config_map.port.5432: quarkus.datasource.jdbc.port + environment: + - POSTGRES_USER=root + - POSTGRES_PASSWORD=password + - POSTGRES_DB=dummy diff --git a/operator/src/main/docker/compose-deviservices-test-pg18.yml b/operator/src/main/docker/compose-deviservices-test-pg18.yml new file mode 100644 index 0000000..7c7c75d --- /dev/null +++ b/operator/src/main/docker/compose-deviservices-test-pg18.yml @@ -0,0 +1,19 @@ +services: + db: + image: postgres:18 + command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + tmpfs: + - /var/lib/postgresql/18/docker + healthcheck: + test: pg_isready -U root -d dummy + interval: 3s + timeout: 3s + retries: 3 + ports: + - "5432" + labels: + io.quarkus.devservices.compose.config_map.port.5432: quarkus.datasource.jdbc.port + environment: + - POSTGRES_USER=root + - POSTGRES_PASSWORD=password + - POSTGRES_DB=dummy diff --git a/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java b/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java index edc4ae2..727092c 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java +++ b/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java @@ -23,8 +23,8 @@ public enum Privilege { CREATE, CONNECT, TEMPORARY, - USAGE, - MAINTAIN; + USAGE; + //MAINTAIN; // PostgreSQL 17+ @JsonValue public String toValue() { diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java index f444d73..40769b2 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java @@ -14,7 +14,6 @@ import static it.aboutbits.postgresql.core.Privilege.CREATE; import static it.aboutbits.postgresql.core.Privilege.DELETE; import static it.aboutbits.postgresql.core.Privilege.INSERT; -import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; import static it.aboutbits.postgresql.core.Privilege.REFERENCES; import static it.aboutbits.postgresql.core.Privilege.SELECT; import static it.aboutbits.postgresql.core.Privilege.TRIGGER; @@ -48,8 +47,8 @@ public enum DefaultPrivilegeObjectType { DELETE, TRUNCATE, REFERENCES, - TRIGGER, - MAINTAIN + TRIGGER + //MAINTAIN // PostgreSQL 17+ ) ), SEQUENCE( diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java index 51e8ef0..d57c165 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java @@ -102,7 +102,6 @@ public class DefaultPrivilegeSpec { /// - `trigger` /// - `create` /// - `usage` - /// - `maintain` @Required @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES) @ValidationRule( diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java index 05e1296..1a500da 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java @@ -15,7 +15,6 @@ import static it.aboutbits.postgresql.core.Privilege.CREATE; import static it.aboutbits.postgresql.core.Privilege.DELETE; import static it.aboutbits.postgresql.core.Privilege.INSERT; -import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; import static it.aboutbits.postgresql.core.Privilege.REFERENCES; import static it.aboutbits.postgresql.core.Privilege.SELECT; import static it.aboutbits.postgresql.core.Privilege.TEMPORARY; @@ -55,8 +54,8 @@ public enum GrantObjectType { DELETE, TRUNCATE, REFERENCES, - TRIGGER, - MAINTAIN + TRIGGER + //MAINTAIN // PostgreSQL 17+ ) ), SEQUENCE( diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java index 62ccafe..43e75b5 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java @@ -105,7 +105,6 @@ public class GrantSpec { /// - `connect` /// - `temporary` /// - `usage` - /// - `maintain` @Required @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES) @ValidationRule( diff --git a/operator/src/main/resources/application-test-pg15.yml b/operator/src/main/resources/application-test-pg15.yml new file mode 100644 index 0000000..3b0b868 --- /dev/null +++ b/operator/src/main/resources/application-test-pg15.yml @@ -0,0 +1,7 @@ +quarkus: + config: + profile: + parent: test + compose: + devservices: + files: src/main/docker/compose-deviservices-test-pg15.yml diff --git a/operator/src/main/resources/application-test-pg16.yml b/operator/src/main/resources/application-test-pg16.yml new file mode 100644 index 0000000..2fffb10 --- /dev/null +++ b/operator/src/main/resources/application-test-pg16.yml @@ -0,0 +1,7 @@ +quarkus: + config: + profile: + parent: test + compose: + devservices: + files: src/main/docker/compose-deviservices-test-pg16.yml diff --git a/operator/src/main/resources/application-test-pg17.yml b/operator/src/main/resources/application-test-pg17.yml new file mode 100644 index 0000000..eb841bf --- /dev/null +++ b/operator/src/main/resources/application-test-pg17.yml @@ -0,0 +1,7 @@ +quarkus: + config: + profile: + parent: test + compose: + devservices: + files: src/main/docker/compose-deviservices-test-pg17.yml diff --git a/operator/src/main/resources/application-test-pg18.yml b/operator/src/main/resources/application-test-pg18.yml new file mode 100644 index 0000000..aaa67d0 --- /dev/null +++ b/operator/src/main/resources/application-test-pg18.yml @@ -0,0 +1,7 @@ +quarkus: + config: + profile: + parent: test + compose: + devservices: + files: src/main/docker/compose-deviservices-test-pg18.yml diff --git a/operator/src/main/resources/application.yml b/operator/src/main/resources/application.yml index b3783d2..4d94465 100644 --- a/operator/src/main/resources/application.yml +++ b/operator/src/main/resources/application.yml @@ -16,7 +16,7 @@ quarkus: datasource: devservices: enabled: true - image-name: postgres:17.7 + image-name: postgres:18 username: root password: password reuse: false From 49842002e3e2af9e2cc203c1620bf0a2797a5542 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Thu, 22 Jan 2026 17:03:26 +0100 Subject: [PATCH 02/21] specify optimized tmpfs mount options --- operator/src/main/docker/compose-deviservices-test-pg15.yml | 2 +- operator/src/main/docker/compose-deviservices-test-pg16.yml | 2 +- operator/src/main/docker/compose-deviservices-test-pg17.yml | 2 +- operator/src/main/docker/compose-deviservices-test-pg18.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/operator/src/main/docker/compose-deviservices-test-pg15.yml b/operator/src/main/docker/compose-deviservices-test-pg15.yml index 9774f50..f74faad 100644 --- a/operator/src/main/docker/compose-deviservices-test-pg15.yml +++ b/operator/src/main/docker/compose-deviservices-test-pg15.yml @@ -3,7 +3,7 @@ services: image: postgres:15 command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" tmpfs: - - /var/lib/postgresql/data + - /var/lib/postgresql/data:rw,async,noatime healthcheck: test: pg_isready -U root -d dummy interval: 3s diff --git a/operator/src/main/docker/compose-deviservices-test-pg16.yml b/operator/src/main/docker/compose-deviservices-test-pg16.yml index 7fafe58..9747845 100644 --- a/operator/src/main/docker/compose-deviservices-test-pg16.yml +++ b/operator/src/main/docker/compose-deviservices-test-pg16.yml @@ -3,7 +3,7 @@ services: image: postgres:16 command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" tmpfs: - - /var/lib/postgresql/data + - /var/lib/postgresql/data:rw,async,noatime healthcheck: test: pg_isready -U root -d dummy interval: 3s diff --git a/operator/src/main/docker/compose-deviservices-test-pg17.yml b/operator/src/main/docker/compose-deviservices-test-pg17.yml index 5499609..f8bc33d 100644 --- a/operator/src/main/docker/compose-deviservices-test-pg17.yml +++ b/operator/src/main/docker/compose-deviservices-test-pg17.yml @@ -3,7 +3,7 @@ services: image: postgres:17 command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" tmpfs: - - /var/lib/postgresql/data + - /var/lib/postgresql/data:rw,async,noatime healthcheck: test: pg_isready -U root -d dummy interval: 3s diff --git a/operator/src/main/docker/compose-deviservices-test-pg18.yml b/operator/src/main/docker/compose-deviservices-test-pg18.yml index 7c7c75d..eca6af8 100644 --- a/operator/src/main/docker/compose-deviservices-test-pg18.yml +++ b/operator/src/main/docker/compose-deviservices-test-pg18.yml @@ -3,7 +3,7 @@ services: image: postgres:18 command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" tmpfs: - - /var/lib/postgresql/18/docker + - /var/lib/postgresql/18/docker:rw,async,noatime healthcheck: test: pg_isready -U root -d dummy interval: 3s From 27b0b57068db9cffaf9dd44c0dcd63529616523f Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Thu, 22 Jan 2026 17:14:27 +0100 Subject: [PATCH 03/21] adjust the GitHub test workflow to use a test matrix for PostgreSQL versions 15 to 18 --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 69731e3..852529c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,13 @@ on: jobs: test: - name: Tests + name: Tests (PostgreSQL ${{ matrix.postgres-version }}) runs-on: ubuntu-24.04 timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + postgres-version: [ 15, 16, 17, 18 ] steps: - uses: actions/checkout@v5 - uses: aboutbits/github-actions-java/setup-with-gradle@v4 @@ -20,6 +24,7 @@ jobs: --console=colored :operator:test --fail-fast + -Dquarkus.test.profile=test-pg${{ matrix.postgres-version }} env: GITHUB_USER_NAME: ${{ github.actor }} GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 14f473be81a9dc398d0156819cf7d27e80854537 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Fri, 23 Jan 2026 11:15:28 +0100 Subject: [PATCH 04/21] implement a release workflow --- .github/workflows/main.yml | 28 ++++++++++-- .github/workflows/release.yml | 85 +++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 30 ------------- build.gradle.kts | 20 +++++++-- gradle.properties | 3 +- gradle/libs.versions.toml | 7 +++ 6 files changed, 136 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 34aec92..d961e2e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,6 +10,28 @@ concurrency: jobs: test: - name: Tests - uses: ./.github/workflows/test.yml - secrets: inherit + name: Tests (PostgreSQL ${{ matrix.postgres-version }}) + runs-on: ubuntu-24.04 + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + postgres-version: [ 15, 16, 17, 18 ] + steps: + - uses: actions/checkout@v5 + with: + token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + - uses: aboutbits/github-actions-java/setup-with-gradle@v4 + with: + java-version: 25 + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Build & Test + run: >- + ./gradlew + --console=colored + :operator:test + --fail-fast + -Dquarkus.test.profile=test-pg${{ matrix.postgres-version }} + env: + GITHUB_USER_NAME: ${{ github.actor }} + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9a7fa34 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,85 @@ +name: Release + +on: + workflow_dispatch: + inputs: + increment: + description: "Version increment type" + type: choice + required: true + default: "Patch" + options: + - "Major" + - "Minor" + - "Patch" + - "Prerelease" + +env: + DOCKER_IMAGE: ghcr.io/${{ github.repository }}/app + +jobs: + build-and-publish: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + with: + token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + - uses: aboutbits/github-actions-base/git-setup@v2 + - uses: aboutbits/github-actions-java/setup-with-gradle@v4 + with: + java-version: 25 + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Increment version + run: ./gradlew --console=colored createRelease -Prelease.versionIncrementer=increment${{ github.event.inputs.increment }} + shell: bash + - name: Get next package version + id: nextVersion + run: echo "version=$(./gradlew currentVersion -q -Prelease.quiet)" >> $GITHUB_OUTPUT + shell: bash + - name: Build package + run: ./gradlew --console=colored build + - name: Build Docker image + uses: aboutbits/github-actions-docker/build-push@v1 + with: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + docker-image: ${{ env.DOCKER_IMAGE }} + docker-tag: ${{ steps.nextVersion.outputs.version }} + working-directory: './operator' + dockerfile: './operator/src/main/docker/Dockerfile.jvm' + - name: Push tag to remote + run: ./gradlew --console=colored pushRelease + shell: bash + - uses: aboutbits/github-actions-base/github-create-release@v2 + with: + tag-name: 'v${{ steps.nextVersion.outputs.version }}' + release-description: | + ## Installation + + **Docker Image:** + ```bash + docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.nextVersion.outputs.version }} + ``` + + **Helm Chart:** + ```bash + helm install postgresql-operator https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/postgresql-operator-${{ steps.nextVersion.outputs.version }}.tgz + ``` + + **Manually installing CRDs:** + ```bash + kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/clusterconnections.postgresql.aboutbits.it-v1.yml + kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/databases.postgresql.aboutbits.it-v1.yml + kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/schemas.postgresql.aboutbits.it-v1.yml + kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/roles.postgresql.aboutbits.it-v1.yml + kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/grants.postgresql.aboutbits.it-v1.yml + kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/defaultprivileges.postgresql.aboutbits.it-v1.yml + ``` + release-notes-generation: 'true' + - name: Upload Helm chart and CRD assets + env: + GH_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + run: | + gh release upload v${{ steps.nextVersion.outputs.version }} operator/build/helm/kubernetes/*.tgz operator/build/kubernetes/*.postgresql.aboutbits.it-v1.yml + shell: bash diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 852529c..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Tests - -on: - workflow_call: - -jobs: - test: - name: Tests (PostgreSQL ${{ matrix.postgres-version }}) - runs-on: ubuntu-24.04 - timeout-minutes: 5 - strategy: - fail-fast: false - matrix: - postgres-version: [ 15, 16, 17, 18 ] - steps: - - uses: actions/checkout@v5 - - uses: aboutbits/github-actions-java/setup-with-gradle@v4 - with: - java-version: 25 - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - name: Build & Test - run: >- - ./gradlew - --console=colored - :operator:test - --fail-fast - -Dquarkus.test.profile=test-pg${{ matrix.postgres-version }} - env: - GITHUB_USER_NAME: ${{ github.actor }} - GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/build.gradle.kts b/build.gradle.kts index 3259d05..bf2fd10 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,22 +8,36 @@ plugins { java checkstyle id("io.quarkus").apply(false) + alias(libs.plugins.axionReleasePlugin) alias(libs.plugins.errorPronePlugin) alias(libs.plugins.jooqPlugin).apply(false) } description = "AboutBits PostgreSQL Operator" +scmVersion { + checks { + aheadOfRemote = true + snapshotDependencies = false + uncommittedChanges = false + } + releaseBranchNames = setOf("main") + releaseOnlyOnReleaseBranches = true + versionCreator("simple") +} + +version = scmVersion.version + allprojects { group = "it.aboutbits.postgresql" - version = "0.0.1-SNAPSHOT" + version = rootProject.version tasks.withType().configureEach { dependsOn(":checkstyleExtractConfig") reports { - html.required.set(false) - xml.required.set(false) + html.required = false + xml.required = false } } } diff --git a/gradle.properties b/gradle.properties index 47b7308..5284165 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,7 @@ # Gradle properties org.gradle.caching=true -org.gradle.parallel=true +# TODO: Set to true when https://github.com/quarkusio/quarkus/issues/49115 is fixed +org.gradle.parallel=false org.gradle.logging.level=INFO # Quarkus diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec170fb..070ef9f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,9 @@ ## AboutBits Libraries ## checkstyleConfig = "2.0.0" +# Axion Release Plugin # +axionReleasePlugin = "1.21.1" + ## Libraries ## jooq = "3.20.10" jSpecify = "1.0.0" @@ -19,6 +22,10 @@ errorPronePlugin = "4.4.0" nullAway = "0.13.0" [plugins] +# https://github.com/allegro/axion-release-plugin +# https://axion-release-plugin.readthedocs.io/ +axionReleasePlugin = { id = "pl.allegro.tech.build.axion-release", version.ref = "axionReleasePlugin" } + # https://github.com/tbroyer/gradle-errorprone-plugin # https://mvnrepository.com/artifact/net.ltgt.errorprone/net.ltgt.errorprone.gradle.plugin errorPronePlugin = { id = "net.ltgt.errorprone", version.ref = "errorPronePlugin" } From af2420f90002f2a45a4eac62b099684362fc81ff Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Fri, 23 Jan 2026 11:18:57 +0100 Subject: [PATCH 05/21] remove the container image app suffix --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a7fa34..77093e8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ on: - "Prerelease" env: - DOCKER_IMAGE: ghcr.io/${{ github.repository }}/app + DOCKER_IMAGE: ghcr.io/${{ github.repository }} jobs: build-and-publish: From e80d4ee3aa058f5a7e132dd482f62bb06b7029c1 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Fri, 23 Jan 2026 11:22:34 +0100 Subject: [PATCH 06/21] fix typo --- ...services-test-pg15.yml => compose-devservices-test-pg15.yml} | 0 ...services-test-pg16.yml => compose-devservices-test-pg16.yml} | 0 ...services-test-pg17.yml => compose-devservices-test-pg17.yml} | 0 ...services-test-pg18.yml => compose-devservices-test-pg18.yml} | 0 operator/src/main/resources/application-test-pg15.yml | 2 +- operator/src/main/resources/application-test-pg16.yml | 2 +- operator/src/main/resources/application-test-pg17.yml | 2 +- operator/src/main/resources/application-test-pg18.yml | 2 +- 8 files changed, 4 insertions(+), 4 deletions(-) rename operator/src/main/docker/{compose-deviservices-test-pg15.yml => compose-devservices-test-pg15.yml} (100%) rename operator/src/main/docker/{compose-deviservices-test-pg16.yml => compose-devservices-test-pg16.yml} (100%) rename operator/src/main/docker/{compose-deviservices-test-pg17.yml => compose-devservices-test-pg17.yml} (100%) rename operator/src/main/docker/{compose-deviservices-test-pg18.yml => compose-devservices-test-pg18.yml} (100%) diff --git a/operator/src/main/docker/compose-deviservices-test-pg15.yml b/operator/src/main/docker/compose-devservices-test-pg15.yml similarity index 100% rename from operator/src/main/docker/compose-deviservices-test-pg15.yml rename to operator/src/main/docker/compose-devservices-test-pg15.yml diff --git a/operator/src/main/docker/compose-deviservices-test-pg16.yml b/operator/src/main/docker/compose-devservices-test-pg16.yml similarity index 100% rename from operator/src/main/docker/compose-deviservices-test-pg16.yml rename to operator/src/main/docker/compose-devservices-test-pg16.yml diff --git a/operator/src/main/docker/compose-deviservices-test-pg17.yml b/operator/src/main/docker/compose-devservices-test-pg17.yml similarity index 100% rename from operator/src/main/docker/compose-deviservices-test-pg17.yml rename to operator/src/main/docker/compose-devservices-test-pg17.yml diff --git a/operator/src/main/docker/compose-deviservices-test-pg18.yml b/operator/src/main/docker/compose-devservices-test-pg18.yml similarity index 100% rename from operator/src/main/docker/compose-deviservices-test-pg18.yml rename to operator/src/main/docker/compose-devservices-test-pg18.yml diff --git a/operator/src/main/resources/application-test-pg15.yml b/operator/src/main/resources/application-test-pg15.yml index 3b0b868..4ac9513 100644 --- a/operator/src/main/resources/application-test-pg15.yml +++ b/operator/src/main/resources/application-test-pg15.yml @@ -4,4 +4,4 @@ quarkus: parent: test compose: devservices: - files: src/main/docker/compose-deviservices-test-pg15.yml + files: src/main/docker/compose-devservices-test-pg15.yml diff --git a/operator/src/main/resources/application-test-pg16.yml b/operator/src/main/resources/application-test-pg16.yml index 2fffb10..0d02355 100644 --- a/operator/src/main/resources/application-test-pg16.yml +++ b/operator/src/main/resources/application-test-pg16.yml @@ -4,4 +4,4 @@ quarkus: parent: test compose: devservices: - files: src/main/docker/compose-deviservices-test-pg16.yml + files: src/main/docker/compose-devservices-test-pg16.yml diff --git a/operator/src/main/resources/application-test-pg17.yml b/operator/src/main/resources/application-test-pg17.yml index eb841bf..6aec7b5 100644 --- a/operator/src/main/resources/application-test-pg17.yml +++ b/operator/src/main/resources/application-test-pg17.yml @@ -4,4 +4,4 @@ quarkus: parent: test compose: devservices: - files: src/main/docker/compose-deviservices-test-pg17.yml + files: src/main/docker/compose-devservices-test-pg17.yml diff --git a/operator/src/main/resources/application-test-pg18.yml b/operator/src/main/resources/application-test-pg18.yml index aaa67d0..145b608 100644 --- a/operator/src/main/resources/application-test-pg18.yml +++ b/operator/src/main/resources/application-test-pg18.yml @@ -4,4 +4,4 @@ quarkus: parent: test compose: devservices: - files: src/main/docker/compose-deviservices-test-pg18.yml + files: src/main/docker/compose-devservices-test-pg18.yml From 85104716f9ab8ffbd48af99de9cc391a60af2543 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Fri, 23 Jan 2026 11:25:48 +0100 Subject: [PATCH 07/21] fix the image name in the Helm chart values.yml --- operator/src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operator/src/main/resources/application.yml b/operator/src/main/resources/application.yml index 4d94465..9a2bf85 100644 --- a/operator/src/main/resources/application.yml +++ b/operator/src/main/resources/application.yml @@ -45,8 +45,8 @@ quarkus: # Container Image config for Kubernetes Helm # container-image: registry: ghcr.io - group: aboutbits/postgresql-operator - name: app + group: aboutbits + name: postgresql-operator tag: ${quarkus.application.version} helm: app-version: ${quarkus.application.version} From 62b8137da8ce081b1bab9f726dc35c13cc640611 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Fri, 23 Jan 2026 11:49:34 +0100 Subject: [PATCH 08/21] enable Gradle configuration cache as Quarkus is compatible with it --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index 5284165..eaf57c1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,6 @@ # Gradle properties org.gradle.caching=true +org.gradle.configuration-cache=true # TODO: Set to true when https://github.com/quarkusio/quarkus/issues/49115 is fixed org.gradle.parallel=false org.gradle.logging.level=INFO From 1d16cfcc54cb4223d9e9df7cf814d4d7726f20e1 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Fri, 23 Jan 2026 13:50:58 +0100 Subject: [PATCH 09/21] update to Quarkus 3.30.8 --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index eaf57c1..97efcd9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,9 +7,9 @@ org.gradle.logging.level=INFO # Quarkus quarkusPluginId=io.quarkus -quarkusPluginVersion=3.30.7 +quarkusPluginVersion=3.30.8 # https://mvnrepository.com/artifact/io.quarkus.platform/quarkus-bom quarkusPlatformGroupId=io.quarkus.platform quarkusPlatformArtifactId=quarkus-bom -quarkusPlatformVersion=3.30.7 +quarkusPlatformVersion=3.30.8 systemProp.quarkus.analytics.disabled=true From cc6665480dbf62a80e04883a11961ddc63403f0a Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 10:46:43 +0100 Subject: [PATCH 10/21] restructure release description --- .github/workflows/release.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77093e8..ab2840e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,17 +57,12 @@ jobs: release-description: | ## Installation - **Docker Image:** - ```bash - docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.nextVersion.outputs.version }} - ``` - - **Helm Chart:** + ### Helm Chart ```bash helm install postgresql-operator https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/postgresql-operator-${{ steps.nextVersion.outputs.version }}.tgz ``` - **Manually installing CRDs:** + ### Manual CRD Installation ```bash kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/clusterconnections.postgresql.aboutbits.it-v1.yml kubectl apply -f https://github.com/${{ github.repository }}/releases/download/v${{ steps.nextVersion.outputs.version }}/databases.postgresql.aboutbits.it-v1.yml From dc5e0fab846831e876aa614b585659d7031dbcb7 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 10:48:45 +0100 Subject: [PATCH 11/21] error out if an unsupported privilege is used and conditionally reflect this in the tests --- .../aboutbits/postgresql/core/Privilege.java | 34 +++-- .../DefaultPrivilegeObjectType.java | 5 +- .../DefaultPrivilegeReconciler.java | 32 +++++ .../postgresql/crd/grant/GrantObjectType.java | 5 +- .../postgresql/crd/grant/GrantReconciler.java | 30 +++- .../creator/ClusterConnectionCreate.java | 1 + .../DefaultPrivilegeReconcilerErrorTest.java | 104 ++++++++++++++ .../DefaultPrivilegeReconcilerTest.java | 29 +++- .../crd/grant/GrantReconcilerErrorTest.java | 131 ++++++++++++++++++ .../crd/grant/GrantReconcilerTest.java | 42 +++++- 10 files changed, 386 insertions(+), 27 deletions(-) create mode 100644 operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java create mode 100644 operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java diff --git a/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java b/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java index 727092c..a0735b0 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java +++ b/operator/src/main/java/it/aboutbits/postgresql/core/Privilege.java @@ -1,8 +1,12 @@ package it.aboutbits.postgresql.core; import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; import org.jooq.impl.DSL; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Locale; @@ -12,19 +16,25 @@ * */ @NullMarked +@Getter +@Accessors(fluent = true) +@RequiredArgsConstructor public enum Privilege { - SELECT, - INSERT, - UPDATE, - DELETE, - TRUNCATE, - REFERENCES, - TRIGGER, - CREATE, - CONNECT, - TEMPORARY, - USAGE; - //MAINTAIN; // PostgreSQL 17+ + SELECT(null), + INSERT(null), + UPDATE(null), + DELETE(null), + TRUNCATE(null), + REFERENCES(null), + TRIGGER(null), + CREATE(null), + CONNECT(null), + TEMPORARY(null), + USAGE(null), + MAINTAIN(17); + + @Nullable + private final Integer minimumPostgresVersion; @JsonValue public String toValue() { diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java index 40769b2..f444d73 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeObjectType.java @@ -14,6 +14,7 @@ import static it.aboutbits.postgresql.core.Privilege.CREATE; import static it.aboutbits.postgresql.core.Privilege.DELETE; import static it.aboutbits.postgresql.core.Privilege.INSERT; +import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; import static it.aboutbits.postgresql.core.Privilege.REFERENCES; import static it.aboutbits.postgresql.core.Privilege.SELECT; import static it.aboutbits.postgresql.core.Privilege.TRIGGER; @@ -47,8 +48,8 @@ public enum DefaultPrivilegeObjectType { DELETE, TRUNCATE, REFERENCES, - TRIGGER - //MAINTAIN // PostgreSQL 17+ + TRIGGER, + MAINTAIN ) ), SEQUENCE( diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconciler.java b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconciler.java index 83ce268..11a2698 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconciler.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconciler.java @@ -10,6 +10,7 @@ import it.aboutbits.postgresql.core.CRPhase; import it.aboutbits.postgresql.core.CRStatus; import it.aboutbits.postgresql.core.PostgreSQLContextFactory; +import it.aboutbits.postgresql.core.Privilege; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jooq.DSLContext; @@ -18,6 +19,8 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; @NullMarked @Slf4j @@ -209,7 +212,36 @@ private UpdateControl reconcileInTransaction( ) { var spec = resource.getSpec(); + var name = resource.getMetadata().getName(); + var namespace = resource.getMetadata().getNamespace(); + var expectedPrivileges = Set.copyOf(spec.getPrivileges()); + + int databaseMajorVersion = tx.connectionResult(connection -> + connection.getMetaData().getDatabaseMajorVersion() + ); + + var unsupportedPrivileges = expectedPrivileges.stream() + .filter(privilege -> privilege.minimumPostgresVersion() != null + && databaseMajorVersion < privilege.minimumPostgresVersion() + ) + .collect(Collectors.toMap( + Function.identity(), + Privilege::minimumPostgresVersion + )); + + if (!unsupportedPrivileges.isEmpty()) { + status.setPhase(CRPhase.ERROR) + .setMessage("The following privileges require a newer PostgreSQL version (current: %d): %s [resource=%s/%s]".formatted( + databaseMajorVersion, + unsupportedPrivileges, + getResourceNamespaceOrOwn(resource, namespace), + name + )); + + return UpdateControl.patchStatus(resource); + } + var currentDefaultPrivileges = defaultPrivilegeService.determineCurrentDefaultPrivileges(tx, spec); // Calculate Revokes: Current - Expected diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java index 1a500da..05e1296 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantObjectType.java @@ -15,6 +15,7 @@ import static it.aboutbits.postgresql.core.Privilege.CREATE; import static it.aboutbits.postgresql.core.Privilege.DELETE; import static it.aboutbits.postgresql.core.Privilege.INSERT; +import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; import static it.aboutbits.postgresql.core.Privilege.REFERENCES; import static it.aboutbits.postgresql.core.Privilege.SELECT; import static it.aboutbits.postgresql.core.Privilege.TEMPORARY; @@ -54,8 +55,8 @@ public enum GrantObjectType { DELETE, TRUNCATE, REFERENCES, - TRIGGER - //MAINTAIN // PostgreSQL 17+ + TRIGGER, + MAINTAIN ) ), SEQUENCE( diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantReconciler.java b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantReconciler.java index 56efdd7..d5adb51 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantReconciler.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantReconciler.java @@ -10,6 +10,7 @@ import it.aboutbits.postgresql.core.CRPhase; import it.aboutbits.postgresql.core.CRStatus; import it.aboutbits.postgresql.core.PostgreSQLContextFactory; +import it.aboutbits.postgresql.core.Privilege; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jooq.DSLContext; @@ -21,6 +22,8 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; import static org.jooq.impl.DSL.quotedName; @@ -230,9 +233,34 @@ private UpdateControl reconcileInTransaction( Collections.emptySet() ) ); + var isAllMode = expectedObjects.isEmpty(); + var expectedPrivileges = Set.copyOf(spec.getPrivileges()); - var isAllMode = expectedObjects.isEmpty(); + int databaseMajorVersion = tx.connectionResult(connection -> + connection.getMetaData().getDatabaseMajorVersion() + ); + + var unsupportedPrivileges = expectedPrivileges.stream() + .filter(privilege -> privilege.minimumPostgresVersion() != null + && databaseMajorVersion < privilege.minimumPostgresVersion() + ) + .collect(Collectors.toMap( + Function.identity(), + Privilege::minimumPostgresVersion + )); + + if (!unsupportedPrivileges.isEmpty()) { + status.setPhase(CRPhase.ERROR) + .setMessage("The following privileges require a newer PostgreSQL version (current: %d): %s [resource=%s/%s]".formatted( + databaseMajorVersion, + unsupportedPrivileges, + getResourceNamespaceOrOwn(resource, namespace), + name + )); + + return UpdateControl.patchStatus(resource); + } var currentObjectPrivileges = grantService.determineCurrentObjectPrivileges(tx, spec); var ownershipMap = grantService.determineObjectExistenceAndOwnership(tx, spec); diff --git a/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/ClusterConnectionCreate.java b/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/ClusterConnectionCreate.java index b19063a..11185df 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/ClusterConnectionCreate.java +++ b/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/ClusterConnectionCreate.java @@ -22,6 +22,7 @@ @Accessors(fluent = true, chain = true) public class ClusterConnectionCreate extends TestDataCreator { private final Given given; + private final KubernetesClient kubernetesClient; private final Given.DBConnectionDetails dbConnectionDetails; diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java new file mode 100644 index 0000000..063e45a --- /dev/null +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java @@ -0,0 +1,104 @@ +package it.aboutbits.postgresql.crd.defaultprivilege; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.quarkus.test.junit.QuarkusTest; +import it.aboutbits.postgresql._support.testdata.persisted.Given; +import it.aboutbits.postgresql.core.CRPhase; +import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection; +import it.aboutbits.postgresql.crd.database.Database; +import it.aboutbits.postgresql.crd.role.Role; +import it.aboutbits.postgresql.crd.schema.Schema; +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; + +import java.util.concurrent.TimeUnit; + +import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; +import static it.aboutbits.postgresql.crd.defaultprivilege.DefaultPrivilegeObjectType.TABLE; +import static org.assertj.core.api.Assertions.assertThat; + +@QuarkusTest +@RequiredArgsConstructor +class DefaultPrivilegeReconcilerErrorTest { + private final Given given; + + private final KubernetesClient kubernetesClient; + + @BeforeEach + void resetEnvironment() { + deleteResources(DefaultPrivilege.class); + deleteResources(Schema.class); + deleteResources(Database.class); + deleteResources(Role.class); + deleteResources(ClusterConnection.class); + + // Create the default connection "test-cluster-connection" used by DefaultPrivilegeCreate defaults + given.one().clusterConnection() + .withName("test-cluster-connection") + .returnFirst(); + } + + private void deleteResources(Class resourceClass) { + kubernetesClient.resources(resourceClass) + .withTimeout(5, TimeUnit.SECONDS) + .delete(); + } + + @Test + @EnabledIfSystemProperty( + named = "quarkus.test.profile", + matches = "test-pg(15|16)", + disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" + ) + @DisplayName("Test unsupported MAINTAIN table privilege error") + void testUnsupportedMaintainTablePrivilegeError() { + // given + var clusterConnectionMain = given.one() + .clusterConnection() + .returnFirst(); + + var database = given.one() + .database() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + var clusterConnectionDb = given.one() + .clusterConnection() + .withDatabase(database.getSpec().getName()) + .returnFirst(); + + var schema = given.one() + .schema() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .returnFirst(); + + var role = given.one() + .role() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + // when + var defaultPrivilege = given.one() + .defaultPrivilege() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .withDatabase(database.getSpec().getName()) + .withSchema(schema.getSpec().getName()) + .withRole(role.getSpec().getName()) + .withObjectType(TABLE) + .withPrivileges(MAINTAIN) + .returnFirst(); + + // then + assertThat(defaultPrivilege) + .isNotNull() + .extracting(DefaultPrivilege::getStatus) + .satisfies(status -> { + assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); + assertThat(status.getMessage()).startsWith("The following privileges require a newer PostgreSQL version (current: 16): {MAINTAIN=17}"); + }); + } +} diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java index d644854..037f7d2 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -28,6 +29,8 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Stream; import static it.aboutbits.postgresql.core.Privilege.CREATE; import static it.aboutbits.postgresql.core.Privilege.SELECT; @@ -43,8 +46,10 @@ @RequiredArgsConstructor class DefaultPrivilegeReconcilerTest { private final Given given; + private final DefaultPrivilegeService defaultPrivilegeService; private final PostgreSQLContextFactory postgreSQLContextFactory; + private final KubernetesClient kubernetesClient; @BeforeEach @@ -438,15 +443,19 @@ void defaultPrivilegeOnSchema() { @Nested class TableTests { - @Test + @ParameterizedTest + @MethodSource("provideAllSupportedPrivileges") @DisplayName("Should grant and revoke default privileges on table") - void defaultPrivilegeOnTable() { + void defaultPrivilegeOnTable( + List allSupportedPrivileges + ) { // given var now = OffsetDateTime.now(ZoneOffset.UTC); var clusterConnectionMain = given.one() .clusterConnection() .returnFirst(); + var database = given.one() .database() .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) @@ -498,7 +507,7 @@ void defaultPrivilegeOnTable() { // given: grant all default privileges change var spec = defaultPrivilege.getSpec(); - var expectedPrivileges = TABLE.privileges(); + var expectedPrivileges = allSupportedPrivileges; var initialGeneration = defaultPrivilege.getStatus().getObservedGeneration(); spec.setPrivileges(expectedPrivileges); @@ -544,6 +553,19 @@ void defaultPrivilegeOnTable() { defaultPrivilege ); } + + static Stream> provideAllSupportedPrivileges() { + var profile = System.getProperty("quarkus.test.profile", ""); + var matcher = Pattern.compile("test-pg(\\d+)").matcher(profile); + int version = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0; + + return Stream.of(TABLE.privilegesSet().stream() + .filter(privilege -> privilege.minimumPostgresVersion() == null + || privilege.minimumPostgresVersion() <= version + ) + .toList() + ); + } } @Nested @@ -557,6 +579,7 @@ void defaultPrivilegeOnSequence() { var clusterConnectionMain = given.one() .clusterConnection() .returnFirst(); + var database = given.one() .database() .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java new file mode 100644 index 0000000..deffeff --- /dev/null +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java @@ -0,0 +1,131 @@ +package it.aboutbits.postgresql.crd.grant; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.quarkus.test.junit.QuarkusTest; +import it.aboutbits.postgresql._support.testdata.persisted.Given; +import it.aboutbits.postgresql.core.CRPhase; +import it.aboutbits.postgresql.core.PostgreSQLContextFactory; +import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection; +import it.aboutbits.postgresql.crd.database.Database; +import it.aboutbits.postgresql.crd.role.Role; +import it.aboutbits.postgresql.crd.schema.Schema; +import lombok.RequiredArgsConstructor; +import org.jooq.impl.SQLDataType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; + +import java.util.concurrent.TimeUnit; + +import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; +import static it.aboutbits.postgresql.crd.grant.GrantObjectType.TABLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.jooq.impl.DSL.quotedName; + +@QuarkusTest +@RequiredArgsConstructor +class GrantReconcilerErrorTest { + private final Given given; + + private final PostgreSQLContextFactory postgreSQLContextFactory; + + private final KubernetesClient kubernetesClient; + + @BeforeEach + void resetEnvironment() { + deleteResources(Grant.class); + deleteResources(Schema.class); + deleteResources(Database.class); + deleteResources(Role.class); + deleteResources(ClusterConnection.class); + + // Create the default connection "test-cluster-connection" used by GrantCreate defaults + given.one().clusterConnection() + .withName("test-cluster-connection") + .returnFirst(); + } + + private void deleteResources(Class resourceClass) { + kubernetesClient.resources(resourceClass) + .withTimeout(5, TimeUnit.SECONDS) + .delete(); + } + + @Test + @EnabledIfSystemProperty( + named = "quarkus.test.profile", + matches = "test-pg(15|16)", + disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" + ) + @DisplayName("Test unsupported MAINTAIN table privilege error") + void testUnsupportedMaintainTablePrivilegeError() { + // given + var clusterConnectionMain = given.one() + .clusterConnection() + .returnFirst(); + + var database = given.one() + .database() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + var clusterConnectionDb = given.one() + .clusterConnection() + .withDatabase(database.getSpec().getName()) + .returnFirst(); + + var schema = given.one() + .schema() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .returnFirst(); + + var role = given.one() + .role() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + var tableName = "test_table"; + createTable( + clusterConnectionDb, + database.getSpec().getName(), + schema.getSpec().getName(), + tableName + ); + + // when + var grant = given.one() + .grant() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .withDatabase(database.getSpec().getName()) + .withSchema(schema.getSpec().getName()) + .withRole(role.getSpec().getName()) + .withObjectType(TABLE) + .withObjects(tableName) + .withPrivileges(MAINTAIN) + .returnFirst(); + + // then + assertThat(grant) + .isNotNull() + .extracting(Grant::getStatus) + .satisfies(status -> { + assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); + assertThat(status.getMessage()).startsWith("The following privileges require a newer PostgreSQL version (current: 16): {MAINTAIN=17}"); + }); + } + + private void createTable( + ClusterConnection clusterConnection, + String databaseName, + String schemaName, + String tableName + ) { + try (var dsl = postgreSQLContextFactory.getDSLContext(clusterConnection, databaseName)) { + dsl.createTable(quotedName(schemaName, tableName)) + .column("id", SQLDataType.INTEGER) + .execute(); + } + } +} diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java index 744a541..c749724 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java @@ -12,6 +12,7 @@ import it.aboutbits.postgresql.core.Privilege; import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection; import it.aboutbits.postgresql.crd.database.Database; +import it.aboutbits.postgresql.crd.defaultprivilege.DefaultPrivilegeObjectType; import it.aboutbits.postgresql.crd.role.Role; import it.aboutbits.postgresql.crd.schema.Schema; import lombok.RequiredArgsConstructor; @@ -22,6 +23,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -30,6 +32,8 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Stream; import static it.aboutbits.postgresql.core.Privilege.CONNECT; import static it.aboutbits.postgresql.core.Privilege.CREATE; @@ -48,8 +52,10 @@ @RequiredArgsConstructor class GrantReconcilerTest { private final Given given; + private final GrantService grantService; private final PostgreSQLContextFactory postgreSQLContextFactory; + private final KubernetesClient kubernetesClient; @BeforeEach @@ -549,15 +555,19 @@ void grantOnSchema() { @Nested class TableTests { - @Test + @ParameterizedTest + @MethodSource("provideAllSupportedPrivileges") @DisplayName("Should grant and revoke privileges on table") - void grantOnTable() { + void grantOnTable( + List allSupportedPrivileges + ) { // given var now = OffsetDateTime.now(ZoneOffset.UTC); var clusterConnectionMain = given.one() .clusterConnection() .returnFirst(); + var database = given.one() .database() .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) @@ -619,7 +629,7 @@ void grantOnTable() { // given: grant all privileges change var spec = grant.getSpec(); - var expectedPrivileges = TABLE.privileges(); + var expectedPrivileges = allSupportedPrivileges; var initialGeneration = grant.getStatus().getObservedGeneration(); spec.setPrivileges(expectedPrivileges); @@ -669,15 +679,19 @@ void grantOnTable() { ); } - @Test + @ParameterizedTest + @MethodSource("provideAllSupportedPrivileges") @DisplayName("Should grant and revoke privileges on all tables") - void grantOnAllTables() { + void grantOnAllTables( + List allSupportedPrivileges + ) { // given var now = OffsetDateTime.now(ZoneOffset.UTC); var clusterConnectionMain = given.one() .clusterConnection() .returnFirst(); + var database = given.one() .database() .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) @@ -752,10 +766,10 @@ void grantOnAllTables() { // given: grant all privileges change var spec = grant.getSpec(); - var expectedPrivileges = TABLE.privileges(); + var expectedPrivileges = allSupportedPrivileges; var initialGeneration = grant.getStatus().getObservedGeneration(); - spec.setPrivileges(expectedPrivileges); + spec.setPrivileges(allSupportedPrivileges); // when applyGrant( @@ -818,6 +832,19 @@ void grantOnAllTables() { tableName2 ); } + + static Stream> provideAllSupportedPrivileges() { + var profile = System.getProperty("quarkus.test.profile", ""); + var matcher = Pattern.compile("test-pg(\\d+)").matcher(profile); + int version = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0; + + return Stream.of(DefaultPrivilegeObjectType.TABLE.privilegesSet().stream() + .filter(privilege -> privilege.minimumPostgresVersion() == null + || privilege.minimumPostgresVersion() <= version + ) + .toList() + ); + } } @Nested @@ -831,6 +858,7 @@ void grantOnSequence() { var clusterConnectionMain = given.one() .clusterConnection() .returnFirst(); + var database = given.one() .database() .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) From de4bab92034b9da16dc155661550fe6735f0154b Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:03:38 +0100 Subject: [PATCH 12/21] move the unsupported privilege test to the CRDValidation tests and do not hardcode PostgreSQL 16 in the assertion --- .../persisted/creator/GrantCreate.java | 1 - .../DefaultPrivilegeReconcilerErrorTest.java | 104 -------------- .../DefaultPrivilegeReconcilerTest.java | 80 +++++++++-- .../crd/grant/GrantReconcilerErrorTest.java | 131 ------------------ .../crd/grant/GrantReconcilerTest.java | 89 ++++++++++-- 5 files changed, 141 insertions(+), 264 deletions(-) delete mode 100644 operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java delete mode 100644 operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java diff --git a/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/GrantCreate.java b/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/GrantCreate.java index cefd0e8..750060e 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/GrantCreate.java +++ b/operator/src/test/java/it/aboutbits/postgresql/_support/testdata/persisted/creator/GrantCreate.java @@ -124,7 +124,6 @@ protected Grant create(int index) { spec.setRole(getRole()); spec.setObjectType(withObjectType); - spec.setObjects(withObjects); if (withObjectType != GrantObjectType.DATABASE || withSchema != null diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java deleted file mode 100644 index 063e45a..0000000 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerErrorTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package it.aboutbits.postgresql.crd.defaultprivilege; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.quarkus.test.junit.QuarkusTest; -import it.aboutbits.postgresql._support.testdata.persisted.Given; -import it.aboutbits.postgresql.core.CRPhase; -import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection; -import it.aboutbits.postgresql.crd.database.Database; -import it.aboutbits.postgresql.crd.role.Role; -import it.aboutbits.postgresql.crd.schema.Schema; -import lombok.RequiredArgsConstructor; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; - -import java.util.concurrent.TimeUnit; - -import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; -import static it.aboutbits.postgresql.crd.defaultprivilege.DefaultPrivilegeObjectType.TABLE; -import static org.assertj.core.api.Assertions.assertThat; - -@QuarkusTest -@RequiredArgsConstructor -class DefaultPrivilegeReconcilerErrorTest { - private final Given given; - - private final KubernetesClient kubernetesClient; - - @BeforeEach - void resetEnvironment() { - deleteResources(DefaultPrivilege.class); - deleteResources(Schema.class); - deleteResources(Database.class); - deleteResources(Role.class); - deleteResources(ClusterConnection.class); - - // Create the default connection "test-cluster-connection" used by DefaultPrivilegeCreate defaults - given.one().clusterConnection() - .withName("test-cluster-connection") - .returnFirst(); - } - - private void deleteResources(Class resourceClass) { - kubernetesClient.resources(resourceClass) - .withTimeout(5, TimeUnit.SECONDS) - .delete(); - } - - @Test - @EnabledIfSystemProperty( - named = "quarkus.test.profile", - matches = "test-pg(15|16)", - disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" - ) - @DisplayName("Test unsupported MAINTAIN table privilege error") - void testUnsupportedMaintainTablePrivilegeError() { - // given - var clusterConnectionMain = given.one() - .clusterConnection() - .returnFirst(); - - var database = given.one() - .database() - .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) - .returnFirst(); - - var clusterConnectionDb = given.one() - .clusterConnection() - .withDatabase(database.getSpec().getName()) - .returnFirst(); - - var schema = given.one() - .schema() - .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) - .returnFirst(); - - var role = given.one() - .role() - .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) - .returnFirst(); - - // when - var defaultPrivilege = given.one() - .defaultPrivilege() - .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) - .withDatabase(database.getSpec().getName()) - .withSchema(schema.getSpec().getName()) - .withRole(role.getSpec().getName()) - .withObjectType(TABLE) - .withPrivileges(MAINTAIN) - .returnFirst(); - - // then - assertThat(defaultPrivilege) - .isNotNull() - .extracting(DefaultPrivilege::getStatus) - .satisfies(status -> { - assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); - assertThat(status.getMessage()).startsWith("The following privileges require a newer PostgreSQL version (current: 16): {MAINTAIN=17}"); - }); - } -} diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java index 037f7d2..8e8f1e0 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -33,6 +34,7 @@ import java.util.stream.Stream; import static it.aboutbits.postgresql.core.Privilege.CREATE; +import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; import static it.aboutbits.postgresql.core.Privilege.SELECT; import static it.aboutbits.postgresql.core.Privilege.USAGE; import static it.aboutbits.postgresql.crd.defaultprivilege.DefaultPrivilegeObjectType.SCHEMA; @@ -308,10 +310,7 @@ void failWhenDatabaseHasSchema() { @Test @DisplayName("Should reconcile to ERROR when privileges are invalid for objectType") void errorWhenInvalidPrivileges() { - // given - var now = OffsetDateTime.now(ZoneOffset.UTC); - - // when + // given / when var defaultPrivilege = given.one() .defaultPrivilege() .withObjectType(SCHEMA) @@ -319,18 +318,71 @@ void errorWhenInvalidPrivileges() { .withPrivileges(SELECT) .returnFirst(); - var expectedStatus = new CRStatus() - .setName("") - .setPhase(CRPhase.ERROR) - .setMessage("DefaultPrivilege contains invalid privileges for the specified objectType") - .setObservedGeneration(1L); + // then + assertThat(defaultPrivilege) + .isNotNull() + .extracting(DefaultPrivilege::getStatus) + .satisfies(status -> { + assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); + assertThat(status.getMessage()).startsWith("DefaultPrivilege contains invalid privileges for the specified objectType"); + }); + } + + @Test + @EnabledIfSystemProperty( + named = "quarkus.test.profile", + matches = "test-pg(15|16)", + disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" + ) + @DisplayName( + "Should reconcile to ERROR when the PostgreSQL version does not support the MAINTAIN table privilege") + void errorWhenUnsupportedMaintainTablePrivilege() { + // given + var clusterConnectionMain = given.one() + .clusterConnection() + .returnFirst(); + + var database = given.one() + .database() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + var clusterConnectionDb = given.one() + .clusterConnection() + .withDatabase(database.getSpec().getName()) + .returnFirst(); + + var schema = given.one() + .schema() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .returnFirst(); + + var role = given.one() + .role() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + // when + var defaultPrivilege = given.one() + .defaultPrivilege() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .withDatabase(database.getSpec().getName()) + .withSchema(schema.getSpec().getName()) + .withRole(role.getSpec().getName()) + .withObjectType(TABLE) + .withPrivileges(MAINTAIN) + .returnFirst(); // then - assertThatDefaultPrivilegeHasStatus( - defaultPrivilege, - expectedStatus, - now - ); + assertThat(defaultPrivilege) + .isNotNull() + .extracting(DefaultPrivilege::getStatus) + .satisfies(status -> { + assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); + assertThat(status.getMessage()) + .startsWith("The following privileges require a newer PostgreSQL version (current:") + .contains("{MAINTAIN=17}"); + }); } } } diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java deleted file mode 100644 index deffeff..0000000 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerErrorTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package it.aboutbits.postgresql.crd.grant; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.quarkus.test.junit.QuarkusTest; -import it.aboutbits.postgresql._support.testdata.persisted.Given; -import it.aboutbits.postgresql.core.CRPhase; -import it.aboutbits.postgresql.core.PostgreSQLContextFactory; -import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection; -import it.aboutbits.postgresql.crd.database.Database; -import it.aboutbits.postgresql.crd.role.Role; -import it.aboutbits.postgresql.crd.schema.Schema; -import lombok.RequiredArgsConstructor; -import org.jooq.impl.SQLDataType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; - -import java.util.concurrent.TimeUnit; - -import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; -import static it.aboutbits.postgresql.crd.grant.GrantObjectType.TABLE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.jooq.impl.DSL.quotedName; - -@QuarkusTest -@RequiredArgsConstructor -class GrantReconcilerErrorTest { - private final Given given; - - private final PostgreSQLContextFactory postgreSQLContextFactory; - - private final KubernetesClient kubernetesClient; - - @BeforeEach - void resetEnvironment() { - deleteResources(Grant.class); - deleteResources(Schema.class); - deleteResources(Database.class); - deleteResources(Role.class); - deleteResources(ClusterConnection.class); - - // Create the default connection "test-cluster-connection" used by GrantCreate defaults - given.one().clusterConnection() - .withName("test-cluster-connection") - .returnFirst(); - } - - private void deleteResources(Class resourceClass) { - kubernetesClient.resources(resourceClass) - .withTimeout(5, TimeUnit.SECONDS) - .delete(); - } - - @Test - @EnabledIfSystemProperty( - named = "quarkus.test.profile", - matches = "test-pg(15|16)", - disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" - ) - @DisplayName("Test unsupported MAINTAIN table privilege error") - void testUnsupportedMaintainTablePrivilegeError() { - // given - var clusterConnectionMain = given.one() - .clusterConnection() - .returnFirst(); - - var database = given.one() - .database() - .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) - .returnFirst(); - - var clusterConnectionDb = given.one() - .clusterConnection() - .withDatabase(database.getSpec().getName()) - .returnFirst(); - - var schema = given.one() - .schema() - .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) - .returnFirst(); - - var role = given.one() - .role() - .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) - .returnFirst(); - - var tableName = "test_table"; - createTable( - clusterConnectionDb, - database.getSpec().getName(), - schema.getSpec().getName(), - tableName - ); - - // when - var grant = given.one() - .grant() - .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) - .withDatabase(database.getSpec().getName()) - .withSchema(schema.getSpec().getName()) - .withRole(role.getSpec().getName()) - .withObjectType(TABLE) - .withObjects(tableName) - .withPrivileges(MAINTAIN) - .returnFirst(); - - // then - assertThat(grant) - .isNotNull() - .extracting(Grant::getStatus) - .satisfies(status -> { - assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); - assertThat(status.getMessage()).startsWith("The following privileges require a newer PostgreSQL version (current: 16): {MAINTAIN=17}"); - }); - } - - private void createTable( - ClusterConnection clusterConnection, - String databaseName, - String schemaName, - String tableName - ) { - try (var dsl = postgreSQLContextFactory.getDSLContext(clusterConnection, databaseName)) { - dsl.createTable(quotedName(schemaName, tableName)) - .column("id", SQLDataType.INTEGER) - .execute(); - } - } -} diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java index c749724..6a95008 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -37,6 +38,7 @@ import static it.aboutbits.postgresql.core.Privilege.CONNECT; import static it.aboutbits.postgresql.core.Privilege.CREATE; +import static it.aboutbits.postgresql.core.Privilege.MAINTAIN; import static it.aboutbits.postgresql.core.Privilege.SELECT; import static it.aboutbits.postgresql.core.Privilege.USAGE; import static it.aboutbits.postgresql.crd.grant.GrantObjectType.DATABASE; @@ -304,10 +306,7 @@ void failWhenSchemaHasObjects() { @Test @DisplayName("Should reconcile to ERROR when privileges are invalid for objectType") void errorWhenInvalidPrivileges() { - // given - var now = OffsetDateTime.now(ZoneOffset.UTC); - - // when + // given / when var grant = given.one() .grant() .withObjectType(SCHEMA) @@ -315,18 +314,80 @@ void errorWhenInvalidPrivileges() { .withPrivileges(SELECT) .returnFirst(); - var expectedStatus = new CRStatus() - .setName("") - .setPhase(CRPhase.ERROR) - .setMessage("Grant contains invalid privileges for the specified objectType") - .setObservedGeneration(1L); - // then - assertThatGrantHasStatus( - grant, - expectedStatus, - now + assertThat(grant) + .isNotNull() + .extracting(Grant::getStatus) + .satisfies(status -> { + assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); + assertThat(status.getMessage()).startsWith("Grant contains invalid privileges for the specified objectType"); + }); + } + + @Test + @EnabledIfSystemProperty( + named = "quarkus.test.profile", + matches = "test-pg(15|16)", + disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" + ) + @DisplayName( + "Should reconcile to ERROR when the PostgreSQL version does not support the MAINTAIN table privilege") + void errorWhenUnsupportedMaintainTablePrivilege() { + // given + var clusterConnectionMain = given.one() + .clusterConnection() + .returnFirst(); + + var database = given.one() + .database() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + var clusterConnectionDb = given.one() + .clusterConnection() + .withDatabase(database.getSpec().getName()) + .returnFirst(); + + var schema = given.one() + .schema() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .returnFirst(); + + var role = given.one() + .role() + .withClusterConnectionName(clusterConnectionMain.getMetadata().getName()) + .returnFirst(); + + var tableName = "test_table"; + createTable( + clusterConnectionDb, + database.getSpec().getName(), + schema.getSpec().getName(), + tableName ); + + // when + var grant = given.one() + .grant() + .withClusterConnectionName(clusterConnectionDb.getMetadata().getName()) + .withDatabase(database.getSpec().getName()) + .withSchema(schema.getSpec().getName()) + .withRole(role.getSpec().getName()) + .withObjectType(TABLE) + .withObjects(tableName) + .withPrivileges(MAINTAIN) + .returnFirst(); + + // then + assertThat(grant) + .isNotNull() + .extracting(Grant::getStatus) + .satisfies(status -> { + assertThat(status.getPhase()).isEqualTo(CRPhase.ERROR); + assertThat(status.getMessage()) + .startsWith("The following privileges require a newer PostgreSQL version (current:") + .contains("{MAINTAIN=17}"); + }); } } } From becb9c1d8fab0ed43d1787e3bc6016fb4031a003 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:17:19 +0100 Subject: [PATCH 13/21] add the maintain privilege again to the Javadoc --- .../postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java | 1 + .../main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java | 1 + .../crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java | 3 ++- .../it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java index d57c165..51e8ef0 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeSpec.java @@ -102,6 +102,7 @@ public class DefaultPrivilegeSpec { /// - `trigger` /// - `create` /// - `usage` + /// - `maintain` @Required @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES) @ValidationRule( diff --git a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java index 43e75b5..62ccafe 100644 --- a/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java +++ b/operator/src/main/java/it/aboutbits/postgresql/crd/grant/GrantSpec.java @@ -105,6 +105,7 @@ public class GrantSpec { /// - `connect` /// - `temporary` /// - `usage` + /// - `maintain` @Required @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES) @ValidationRule( diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java index 8e8f1e0..e7079d7 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/defaultprivilege/DefaultPrivilegeReconcilerTest.java @@ -335,7 +335,8 @@ void errorWhenInvalidPrivileges() { disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" ) @DisplayName( - "Should reconcile to ERROR when the PostgreSQL version does not support the MAINTAIN table privilege") + "Should reconcile to ERROR when the PostgreSQL version does not support the MAINTAIN table privilege" + ) void errorWhenUnsupportedMaintainTablePrivilege() { // given var clusterConnectionMain = given.one() diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java index 6a95008..c4d2f39 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java @@ -331,7 +331,8 @@ void errorWhenInvalidPrivileges() { disabledReason = "PostgreSQL 15 and 16 do not support the MAINTAIN privilege" ) @DisplayName( - "Should reconcile to ERROR when the PostgreSQL version does not support the MAINTAIN table privilege") + "Should reconcile to ERROR when the PostgreSQL version does not support the MAINTAIN table privilege" + ) void errorWhenUnsupportedMaintainTablePrivilege() { // given var clusterConnectionMain = given.one() From e602f5a6b9488bae36e9f866762309b0971072a0 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:19:53 +0100 Subject: [PATCH 14/21] use aboutbits/java-checkstyle-config version 2.0.0-RC1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 070ef9f..9f0ceb1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] ## AboutBits Libraries ## -checkstyleConfig = "2.0.0" +checkstyleConfig = "2.0.0-RC1" # Axion Release Plugin # axionReleasePlugin = "1.21.1" From f3c00a60b84395af52b5353491a4ad04a393c5a5 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:22:38 +0100 Subject: [PATCH 15/21] update to AssertJ 3.27.7 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9f0ceb1..143080a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ quarkiverse-helm = "1.2.7" scram-client = "3.2" ## Testing ## -assertj = "3.27.6" +assertj = "3.27.7" checkstyle = "13.0.0" datafaker = "2.5.3" errorProne = "2.46.0" From 446b269d4aa9ccc428e75846a5502f58166090ee Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:31:57 +0100 Subject: [PATCH 16/21] run the test matrix also when running the release workflow and on main when something is pushed --- .github/workflows/main.yml | 31 +++--------------------------- .github/workflows/release.yml | 9 +++++++-- .github/workflows/test.yml | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d961e2e..61be5ee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,6 @@ name: Test -on: - pull_request: - types: [ opened, reopened, synchronize ] +on: push concurrency: group: ${{ github.ref }} @@ -10,28 +8,5 @@ concurrency: jobs: test: - name: Tests (PostgreSQL ${{ matrix.postgres-version }}) - runs-on: ubuntu-24.04 - timeout-minutes: 5 - strategy: - fail-fast: false - matrix: - postgres-version: [ 15, 16, 17, 18 ] - steps: - - uses: actions/checkout@v5 - with: - token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} - - uses: aboutbits/github-actions-java/setup-with-gradle@v4 - with: - java-version: 25 - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - name: Build & Test - run: >- - ./gradlew - --console=colored - :operator:test - --fail-fast - -Dquarkus.test.profile=test-pg${{ matrix.postgres-version }} - env: - GITHUB_USER_NAME: ${{ github.actor }} - GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ./.github/workflows/test.yml + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab2840e..8eaa3f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,12 @@ env: DOCKER_IMAGE: ghcr.io/${{ github.repository }} jobs: - build-and-publish: + test: + uses: ./.github/workflows/test.yml + secrets: inherit + + build-and-release: + needs: test runs-on: ubuntu-24.04 timeout-minutes: 15 steps: @@ -38,7 +43,7 @@ jobs: run: echo "version=$(./gradlew currentVersion -q -Prelease.quiet)" >> $GITHUB_OUTPUT shell: bash - name: Build package - run: ./gradlew --console=colored build + run: ./gradlew --console=colored build -x test - name: Build Docker image uses: aboutbits/github-actions-docker/build-push@v1 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f2d5d14 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Test + +on: + workflow_call: + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Tests (PostgreSQL ${{ matrix.postgres-version }}) + runs-on: ubuntu-24.04 + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + postgres-version: [ 15, 16, 17, 18 ] + steps: + - uses: actions/checkout@v6 + with: + token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + - uses: aboutbits/github-actions-java/setup-with-gradle@v4 + with: + java-version: 25 + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Build & Test + run: >- + ./gradlew + --console=colored + :operator:test + --fail-fast + -Dquarkus.test.profile=test-pg${{ matrix.postgres-version }} + env: + GITHUB_USER_NAME: ${{ github.actor }} + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 10283d2e122c616e96797af913cfe03ff8f3b024 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:36:02 +0100 Subject: [PATCH 17/21] fix the workflow in PRs --- .github/workflows/main.yml | 5 ++++- .github/workflows/test.yml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 61be5ee..34aec92 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,8 @@ name: Test -on: push +on: + pull_request: + types: [ opened, reopened, synchronize ] concurrency: group: ${{ github.ref }} @@ -8,5 +10,6 @@ concurrency: jobs: test: + name: Tests uses: ./.github/workflows/test.yml secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2d5d14..c7a4b7e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Test +name: Tests on: workflow_call: From 3c80bff4d53e87d430c9d60ab244ea921c15551a Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:37:57 +0100 Subject: [PATCH 18/21] remove the concurrency block from the reused test workflow --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c7a4b7e..475903f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,10 +3,6 @@ name: Tests on: workflow_call: -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - jobs: test: name: Tests (PostgreSQL ${{ matrix.postgres-version }}) From 3ec0efab5f6781949aadb852b6cc94f6f6b7ee02 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 11:52:58 +0100 Subject: [PATCH 19/21] specify the About Bits GmbH MIT license --- LICENSE | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..21586f5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright About Bits GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 011ba49a6a7a560984e16fba88ccfa470f153884 Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 12:11:43 +0100 Subject: [PATCH 20/21] use the exec-form in the compose files to properly handle SIGTERM --- operator/src/main/docker/compose-devservices-test-pg15.yml | 2 +- operator/src/main/docker/compose-devservices-test-pg16.yml | 2 +- operator/src/main/docker/compose-devservices-test-pg17.yml | 2 +- operator/src/main/docker/compose-devservices-test-pg18.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/operator/src/main/docker/compose-devservices-test-pg15.yml b/operator/src/main/docker/compose-devservices-test-pg15.yml index f74faad..dd15797 100644 --- a/operator/src/main/docker/compose-devservices-test-pg15.yml +++ b/operator/src/main/docker/compose-devservices-test-pg15.yml @@ -1,7 +1,7 @@ services: db: image: postgres:15 - command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + command: [ "postgres", "-c", "checkpoint_timeout=10min", "-c", "fsync=off", "-c", "full_page_writes=off", "-c", "max_wal_size=2GB", "-c", "synchronous_commit=off" ] tmpfs: - /var/lib/postgresql/data:rw,async,noatime healthcheck: diff --git a/operator/src/main/docker/compose-devservices-test-pg16.yml b/operator/src/main/docker/compose-devservices-test-pg16.yml index 9747845..9079a7f 100644 --- a/operator/src/main/docker/compose-devservices-test-pg16.yml +++ b/operator/src/main/docker/compose-devservices-test-pg16.yml @@ -1,7 +1,7 @@ services: db: image: postgres:16 - command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + command: [ "postgres", "-c", "checkpoint_timeout=10min", "-c", "fsync=off", "-c", "full_page_writes=off", "-c", "max_wal_size=2GB", "-c", "synchronous_commit=off" ] tmpfs: - /var/lib/postgresql/data:rw,async,noatime healthcheck: diff --git a/operator/src/main/docker/compose-devservices-test-pg17.yml b/operator/src/main/docker/compose-devservices-test-pg17.yml index f8bc33d..5ab9bfc 100644 --- a/operator/src/main/docker/compose-devservices-test-pg17.yml +++ b/operator/src/main/docker/compose-devservices-test-pg17.yml @@ -1,7 +1,7 @@ services: db: image: postgres:17 - command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + command: [ "postgres", "-c", "checkpoint_timeout=10min", "-c", "fsync=off", "-c", "full_page_writes=off", "-c", "max_wal_size=2GB", "-c", "synchronous_commit=off" ] tmpfs: - /var/lib/postgresql/data:rw,async,noatime healthcheck: diff --git a/operator/src/main/docker/compose-devservices-test-pg18.yml b/operator/src/main/docker/compose-devservices-test-pg18.yml index eca6af8..be71bf8 100644 --- a/operator/src/main/docker/compose-devservices-test-pg18.yml +++ b/operator/src/main/docker/compose-devservices-test-pg18.yml @@ -1,7 +1,7 @@ services: db: image: postgres:18 - command: "postgres -c checkpoint_timeout=10min -c fsync=off -c full_page_writes=off -c max_wal_size=2GB -c synchronous_commit=off" + command: [ "postgres", "-c", "checkpoint_timeout=10min", "-c", "fsync=off", "-c", "full_page_writes=off", "-c", "max_wal_size=2GB", "-c", "synchronous_commit=off" ] tmpfs: - /var/lib/postgresql/18/docker:rw,async,noatime healthcheck: From c2a28cfb50c0ce1ce59518ca722818dd23b74e0e Mon Sep 17 00:00:00 2001 From: Thomas Sapelza Date: Mon, 26 Jan 2026 12:21:11 +0100 Subject: [PATCH 21/21] reference the correct ObjectType TABLE constant --- .../it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java index c4d2f39..45b9bef 100644 --- a/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java +++ b/operator/src/test/java/it/aboutbits/postgresql/crd/grant/GrantReconcilerTest.java @@ -12,7 +12,6 @@ import it.aboutbits.postgresql.core.Privilege; import it.aboutbits.postgresql.crd.clusterconnection.ClusterConnection; import it.aboutbits.postgresql.crd.database.Database; -import it.aboutbits.postgresql.crd.defaultprivilege.DefaultPrivilegeObjectType; import it.aboutbits.postgresql.crd.role.Role; import it.aboutbits.postgresql.crd.schema.Schema; import lombok.RequiredArgsConstructor; @@ -900,7 +899,7 @@ static Stream> provideAllSupportedPrivileges() { var matcher = Pattern.compile("test-pg(\\d+)").matcher(profile); int version = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0; - return Stream.of(DefaultPrivilegeObjectType.TABLE.privilegesSet().stream() + return Stream.of(TABLE.privilegesSet().stream() .filter(privilege -> privilege.minimumPostgresVersion() == null || privilege.minimumPostgresVersion() <= version )