close
Skip to content

Add flyway-database-postgresql for grails-forge-analytics-postgres native deploy#15648

Merged
jamesfredley merged 1 commit into
apache:8.0.xfrom
jamesfredley:ci/forge-analytics-flyway-postgres
May 8, 2026
Merged

Add flyway-database-postgresql for grails-forge-analytics-postgres native deploy#15648
jamesfredley merged 1 commit into
apache:8.0.xfrom
jamesfredley:ci/forge-analytics-flyway-postgres

Conversation

@jamesfredley

Copy link
Copy Markdown
Contributor

Summary

After PR #15647 unblocked dockerBuildNative for grails-forge-analytics-postgres and the resulting native binary built and pushed cleanly, the Cloud Run revision still failed its startup probe (run 25578314349 / run 25578315293):

ERROR: (gcloud.run.deploy) The user-provided container failed to start and listen on the port
defined provided by the PORT=8080 environment variable within the allocated timeout.

That makes 8.0.x's Deploy to Google Cloud Run (the web-netty one) green again, but Deploy analytics to Google Cloud Run red - this is the third-and-final blocker on the broken Forge deploy pipeline.

Root cause

Reproducing the analytics native binary locally on Docker against a postgres:15.3 sidecar showed it crashing during datasource initialization, well before binding to :8080:

HikariPool-1 - Start completed.
Running migrations for database with qualifier [default]
ERROR io.micronaut.runtime.Micronaut - Error starting Micronaut server:
  Bean definition [javax.sql.DataSource] could not be loaded:
  Unsupported Database: PostgreSQL 15.3
Caused by: org.flywaydb.core.api.FlywayException: Unsupported Database: PostgreSQL 15.3
    at org.flywaydb.core.internal.database.DatabaseTypeRegister
        .getDatabaseTypeForConnection(DatabaseTypeRegister.java:131)

Flyway 10.x split the per-database DatabaseType SPI implementations out of flyway-core into dedicated modules. After the May-2026 adoption of micronaut-platform (commit b406076e87) pinned us to flyway-core:10.22.0, the analytics service no longer had a PostgreSQL DatabaseType on the runtime classpath, so Flyway's ServiceLoader<org.flywaydb.core.internal.database.DatabaseType> returned an empty match for the postgres connection, threw Unsupported Database, and the Micronaut bootstrap aborted before the HTTP server could bind.

Previously this was masked by the earlier dockerBuildNative failures - the build never produced an image to deploy, so we never saw the runtime error.

Fix

Add runtimeOnly 'org.flywaydb:flyway-database-postgresql' to grails-forge-analytics-postgres/build.gradle. micronaut-flyway-bom:7.9.2 already manages the matching org.flywaydb:flyway-database-postgresql:10.22.0 artifact, so no explicit version pin is needed:

+--- io.micronaut.flyway:micronaut-flyway-bom:7.9.2
|    +--- org.flywaydb:flyway-database-postgresql:10.22.0 (c)
|    +--- io.micronaut.flyway:micronaut-flyway:7.9.2 (c)
|    \--- org.flywaydb:flyway-core:10.22.0 (c)

Verification

Local repro on Docker 29.3.0 (post-fix image rebuilt with the same 21.0.2-ol9 GraalVM Community native-image container the deploy workflows use):

$ docker run -d --name analytics-pg -p 15432:5432 -e POSTGRES_USER=dbuser \
    -e POSTGRES_PASSWORD=theSecretPassword -e POSTGRES_DB=postgres postgres:15.3
$ docker run -d --name analytics-app -p 18082:8080 \
    --add-host=host.docker.internal:host-gateway \
    -e MICRONAUT_SERVER_PORT=8080 \
    -e DATASOURCES_DEFAULT_URL=jdbc:postgresql://host.docker.internal:15432/postgres \
    -e DATASOURCES_DEFAULT_USERNAME=dbuser \
    -e DATASOURCES_DEFAULT_PASSWORD=theSecretPassword \
    grails-forge-local:analytics
HikariPool-1 - Start completed.
Flyway - Database: jdbc:postgresql://...:5432/postgres (PostgreSQL 15.3)
Flyway - Creating Schema History table "public"."flyway_schema_history" ...
Flyway - Schema "public" is up to date. No migration necessary.
io.micronaut.runtime.Micronaut - Startup completed in 193ms.
  Server Running: http://...:8080

Native binary now binds to :8080 within the Cloud Run startup-probe window. The Cloud Run-side service config (Cloud SQL connection name, secrets, env vars) is unchanged from prior successful deploys (most recent: 2026-04-16 run 24524756128).

After PR apache#15647 unblocked dockerBuildNative for grails-forge-
analytics-postgres, the resulting native binary built and pushed
successfully but the Cloud Run revision failed its startup probe
(`The user-provided container failed to start and listen on the
port defined provided by the PORT=8080 environment variable
within the allocated timeout`).

Reproducing locally with a postgres:15.3 sidecar showed the
binary crashing during datasource initialization:

  ERROR io.micronaut.runtime.Micronaut - Error starting Micronaut
    server: Bean definition [javax.sql.DataSource] could not be
    loaded: Unsupported Database: PostgreSQL 15.3
  Caused by: org.flywaydb.core.api.FlywayException:
    Unsupported Database: PostgreSQL 15.3
        at org.flywaydb.core.internal.database.DatabaseTypeRegister
            .getDatabaseTypeForConnection(DatabaseTypeRegister.java:131)

Flyway 10.x split the per-database DatabaseType implementations
out of flyway-core into dedicated modules. After the May-2026
adoption of micronaut-platform pinned us to flyway-core 10.22.0,
the analytics service no longer had a PostgreSQL DatabaseType on
the runtime classpath, so Flyway's
`ServiceLoader<org.flywaydb.core.internal.database.DatabaseType>`
returned an empty match for the postgres connection and aborted
the schema migration. micronaut-flyway-bom:7.9.2 already manages
the matching `org.flywaydb:flyway-database-postgresql:10.22.0`
artifact, so adding it as `runtimeOnly` is sufficient.

Verified locally with a postgres:15.3 sidecar:

  HikariPool-1 - Start completed.
  Flyway - Database: jdbc:postgresql://...:5432/postgres (PostgreSQL 15.3)
  Flyway - Creating Schema History table "public"."flyway_schema_history"
  Flyway - Schema "public" is up to date. No migration necessary.
  io.micronaut.runtime.Micronaut - Startup completed in 193ms.
    Server Running: http://...:8080

Native binary now binds to PORT=8080 and serves HTTP within the
Cloud Run startup probe window.

Assisted-by: claude-code:claude-4.7-opus
Copilot AI review requested due to automatic review settings May 8, 2026 21:12
@jamesfredley jamesfredley merged commit 7438653 into apache:8.0.x May 8, 2026
29 of 30 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request fixes native startup failures of the grails-forge-analytics-postgres Cloud Run deployment by ensuring Flyway can recognize PostgreSQL at runtime when using Flyway 10.x (where database support is split into per-database modules).

Changes:

  • Add the PostgreSQL Flyway database module (org.flywaydb:flyway-database-postgresql) to the runtime classpath.
  • Rely on BOM-managed versions for the PostgreSQL JDBC driver and Logback runtime dependency (removing explicit version usage in this module).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

implementation 'io.micronaut.flyway:micronaut-flyway'
implementation 'io.micronaut.sql:micronaut-jdbc-hikari'
implementation "org.postgresql:postgresql:$postgresqlVersion"
implementation "org.postgresql:postgresql"
Comment on lines +47 to 49
runtimeOnly 'org.flywaydb:flyway-database-postgresql'
runtimeOnly "ch.qos.logback:logback-classic"
runtimeOnly 'io.micronaut:micronaut-jackson-databind'
@testlens-app

testlens-app Bot commented May 8, 2026

Copy link
Copy Markdown

🚨 TestLens detected 2 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

Check Project/Task Test Runs
CI - Groovy Joint Validation Build / build_grails :grails-data-hibernate5-dbmigration:test GroovyChangeLogSpec > outputs a warning message by calling the warn method
CI - Groovy Joint Validation Build / build_grails :grails-data-hibernate5-dbmigration:test GroovyChangeLogSpec > updates a database with Groovy Change

🏷️ Commit: ca11e8f
▶️ Tests: 8303 executed
🟡 Checks: 14/35 completed

Test Failures

GroovyChangeLogSpec > outputs a warning message by calling the warn method (:grails-data-hibernate5-dbmigration:test in CI - Groovy Joint Validation Build / build_grails)
Condition not satisfied:

output.toString().contains('warn message')
|      |          |
|      |          false
|      21:22:04.291 [Test worker] INFO org.hibernate.dialect.Dialect -- HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
|      Running Changeset: changelog.groovy::2::John Smith
|       
|      UPDATE SUMMARY
|      Run:                          1
|      Previously run:               0
|      Filtered out:                 0
|      -------------------------------
|      Total change sets:            1
|       
|      Liquibase: Update has been successful. Rows affected: 1
21:22:04.291 [Test worker] INFO org.hibernate.dialect.Dialect -- HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Running Changeset: changelog.groovy::2::John Smith
 
UPDATE SUMMARY
Run:                          1
Previously run:               0
Filtered out:                 0
-------------------------------
Total change sets:            1
 
Liquibase: Update has been successful. Rows affected: 1

	at org.grails.plugins.databasemigration.liquibase.GroovyChangeLogSpec.outputs a warning message by calling the warn method(GroovyChangeLogSpec.groovy:87)
GroovyChangeLogSpec > updates a database with Groovy Change (:grails-data-hibernate5-dbmigration:test in CI - Groovy Joint Validation Build / build_grails)
Condition not satisfied:

output.toString().contains('confirmation message')
|      |          |
|      |          false
|      21:22:03.855 [Test worker] INFO org.hibernate.dialect.Dialect -- HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
|      Running Changeset: changelog.groovy::1::John Smith
|       
|      UPDATE SUMMARY
|      Run:                          1
|      Previously run:               0
|      Filtered out:                 0
|      -------------------------------
|      Total change sets:            1
|       
|      Liquibase: Update has been successful. Rows affected: 1
21:22:03.855 [Test worker] INFO org.hibernate.dialect.Dialect -- HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Running Changeset: changelog.groovy::1::John Smith
 
UPDATE SUMMARY
Run:                          1
Previously run:               0
Filtered out:                 0
-------------------------------
Total change sets:            1
 
Liquibase: Update has been successful. Rows affected: 1

	at org.grails.plugins.databasemigration.liquibase.GroovyChangeLogSpec.updates a database with Groovy Change(GroovyChangeLogSpec.groovy:61)

Muted Tests

Note

Checks are currently running using the configuration below.

Select tests to mute in this pull request:

🔲 GroovyChangeLogSpec > outputs a warning message by calling the warn method
🔲 GroovyChangeLogSpec > updates a database with Groovy Change

Reuse successful test results:

🔲 ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

🔲 Rerun jobs


Learn more about TestLens at testlens.app.

jamesfredley added a commit that referenced this pull request May 8, 2026
…ntime

After PR #15647 + #15648 fixed dockerBuildNative + Cloud Run startup
for the analytics service, both Forge deploy workflows finally
finished green and pushed `next.grails.org` (8.0.0-M1 hotfix branch)
and `prev-snapshot.grails.org` (8.0.x) revisions live. start.grails.org
still does not list 8.0.0-M1 / 8.0-SNAPSHOT in the Grails Version
selector, though, because both backends return:

  $ curl -i -H 'Origin: https://start.grails.org' https://next.grails.org/versions
  HTTP/1.1 500 Internal Server Error
  Content-Type: application/json
  ...
  {"_embedded":{"errors":[{"message":"Internal Server Error: Error
   encoding object [org.grails.forge.api.VersionDTO@..] to JSON: No
   serializable introspection present for type VersionDTO. Consider
   adding Serdeable.Serializable annotate to type VersionDTO."}]},
   "message":"Internal Server Error"}

Reproduced locally (`docker run grails-forge-local:web-netty` against
the GraalVM Community 21.0.2-ol9 native image with CORS_ALLOWED_ORIGIN
set, then `curl -H 'Origin: https://start.grails.org' /versions`) -
identical 500.

Root cause: PR #15365 review feedback (commit e1f3013,
"Address PR review feedback: telemetry override, forge BOM
adoption, publish plugin bump") swapped grails-forge-web-netty's
runtime JSON dependency:

  -    runtimeOnly 'io.micronaut:micronaut-jackson-databind'
  +    runtimeOnly 'io.micronaut.serde:micronaut-serde-jackson'

with the rationale that "the forge stack is a Micronaut 4 serde-
jackson application; the legacy jackson-databind runtime was a
copy-paste from an earlier generator template". That is not yet
true: every DTO returned by the Forge controllers
(VersionDTO, ApplicationTypeDTO, FeatureDTO, FeatureList,
SelectOptionsDTO, GormImplDTO, JdkVersionDTO, LanguageDTO,
ServletImplDTO, DevelopmentReloadingDTO, GitHubCreateDTO,
PreviewDTO, ApplicationTypeList, LinkDTO, the abstract Linkable
parent, ...) carries `@Introspected` only - none of them are
`@Serdeable` or registered via `@SerdeImport`. Micronaut Serde
correctly rejects @Introspected-only classes at runtime, while
Micronaut Jackson Databind serialises them via Jackson + bean
introspection. The mismatch turns every JSON endpoint behind
next.grails.org and prev-snapshot.grails.org into a 500, which
is why Forge UI's `versions` fetch is empty for those backends
and 8.0.0-M1 is missing from the start.grails.org Grails Version
selector.

The other Forge modules (forge-api tests, forge-core, analytics-
postgres) all still use micronaut-jackson-databind; web-netty
was the only inconsistency. Restore web-netty to
micronaut-jackson-databind so the runtime matches the rest of the
forge stack and the existing @introspected DTOs serialise. A
proper end-to-end Serde migration belongs in a separate PR that
also annotates every Forge DTO with @Serdeable / @SerdeImport.

Verified locally on Docker 29.3.0 with the same Java 21 GraalVM
Community 21.0.2-ol9 native-image container the deploy workflows
use:

  $ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/versions
  HTTP/1.1 200 OK
  Access-Control-Allow-Origin: https://start.grails.org
  Vary: Origin
  Access-Control-Allow-Credentials: true
  content-type: application/json
  ...
  {"_links":{"self":{"href":"http://.../versions","templated":false}},
   "versions":{...,"grails.version":"8.0.0-M1",...}}

  $ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/select-options
  HTTP/1.1 200 OK
  Access-Control-Allow-Origin: https://start.grails.org
  ...

Endpoints serve JSON cleanly; CORS allow-origin header is present;
the start.grails.org Grails Version selector will pick up 8.0.0-M1
once next.grails.org is redeployed from this commit.

Assisted-by: claude-code:claude-4.7-opus
jamesfredley added a commit that referenced this pull request May 8, 2026
…ntime

After PR #15647 + #15648 fixed dockerBuildNative + Cloud Run startup
for the analytics service, both Forge deploy workflows finally
finished green and pushed `next.grails.org` (8.0.0-M1 hotfix branch)
and `prev-snapshot.grails.org` (8.0.x) revisions live. start.grails.org
still does not list 8.0.0-M1 / 8.0-SNAPSHOT in the Grails Version
selector, though, because both backends return:

  $ curl -i -H 'Origin: https://start.grails.org' https://next.grails.org/versions
  HTTP/1.1 500 Internal Server Error
  Content-Type: application/json
  ...
  {"_embedded":{"errors":[{"message":"Internal Server Error: Error
   encoding object [org.grails.forge.api.VersionDTO@..] to JSON: No
   serializable introspection present for type VersionDTO. Consider
   adding Serdeable.Serializable annotate to type VersionDTO."}]},
   "message":"Internal Server Error"}

Reproduced locally (`docker run grails-forge-local:web-netty` against
the GraalVM Community 21.0.2-ol9 native image with CORS_ALLOWED_ORIGIN
set, then `curl -H 'Origin: https://start.grails.org' /versions`) -
identical 500.

Root cause: PR #15365 review feedback (commit e1f3013,
"Address PR review feedback: telemetry override, forge BOM
adoption, publish plugin bump") swapped grails-forge-web-netty's
runtime JSON dependency:

  -    runtimeOnly 'io.micronaut:micronaut-jackson-databind'
  +    runtimeOnly 'io.micronaut.serde:micronaut-serde-jackson'

with the rationale that "the forge stack is a Micronaut 4 serde-
jackson application; the legacy jackson-databind runtime was a
copy-paste from an earlier generator template". That is not yet
true: every DTO returned by the Forge controllers
(VersionDTO, ApplicationTypeDTO, FeatureDTO, FeatureList,
SelectOptionsDTO, GormImplDTO, JdkVersionDTO, LanguageDTO,
ServletImplDTO, DevelopmentReloadingDTO, GitHubCreateDTO,
PreviewDTO, ApplicationTypeList, LinkDTO, the abstract Linkable
parent, ...) carries `@Introspected` only - none of them are
`@Serdeable` or registered via `@SerdeImport`. Micronaut Serde
correctly rejects @Introspected-only classes at runtime, while
Micronaut Jackson Databind serialises them via Jackson + bean
introspection. The mismatch turns every JSON endpoint behind
next.grails.org and prev-snapshot.grails.org into a 500, which
is why Forge UI's `versions` fetch is empty for those backends
and 8.0.0-M1 is missing from the start.grails.org Grails Version
selector.

The other Forge modules (forge-api tests, forge-core, analytics-
postgres) all still use micronaut-jackson-databind; web-netty
was the only inconsistency. Restore web-netty to
micronaut-jackson-databind so the runtime matches the rest of the
forge stack and the existing @introspected DTOs serialise. A
proper end-to-end Serde migration belongs in a separate PR that
also annotates every Forge DTO with @Serdeable / @SerdeImport.

Verified locally on Docker 29.3.0 with the same Java 21 GraalVM
Community 21.0.2-ol9 native-image container the deploy workflows
use:

  $ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/versions
  HTTP/1.1 200 OK
  Access-Control-Allow-Origin: https://start.grails.org
  Vary: Origin
  Access-Control-Allow-Credentials: true
  content-type: application/json
  ...
  {"_links":{"self":{"href":"http://.../versions","templated":false}},
   "versions":{...,"grails.version":"8.0.0-M1",...}}

  $ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/select-options
  HTTP/1.1 200 OK
  Access-Control-Allow-Origin: https://start.grails.org
  ...

Endpoints serve JSON cleanly; CORS allow-origin header is present;
the start.grails.org Grails Version selector will pick up 8.0.0-M1
once next.grails.org is redeployed from this commit.

Assisted-by: claude-code:claude-4.7-opus
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants