Found standing up a Keycloak-enabled Site on team-operator v1.30.2 (workshop rebuild, rstudio/rstudio-ide-automation#5619).
Symptom: the Site's <site>-keycloak PostgresDatabase never reconciles — postgres database mismatched db host — so the keycloak role/database are never created and the Keycloak StatefulSet crashloops on FATAL: password authentication failed for user "keycloak".
Cause: an asymmetry in how the keycloak path builds its PostgresDatabase versus how the reconciler compares hosts:
internal/controller/core/site_controller_keycloak.go builds the config with Host: dbUrl.Hostname() — port stripped.
internal/controller/core/postgresdatabase_controller.go (~L448) compares specDbUrl.Host != mainDBURL.Host — url.Host includes the port.
So whenever the workload secret's main-database-url carries an explicit :5432, the keycloak compare fails unconditionally (spec side is portless), while connect/pm/workbench pass because their spec URLs retain the port. Deployments with portless URLs (the PTD convention) never hit it, which is presumably why it went unnoticed.
Repro: Site with keycloak.enabled: true and a workload secret main-database-url of postgresql://host:5432/db?sslmode=require.
Workaround: write main-database-url without the explicit port (postgres defaults to 5432).
Suggested fix: compare hostnames consistently — either specDbUrl.Hostname() != mainDBURL.Hostname() in the reconciler, or keep the port in the keycloak path (Host: dbUrl.Host).
Addendum — second asymmetry in the same path (username): the reconciler provisions the role from the PostgresDatabase URL username (<site>_keycloak, e.g. workshop_site_keycloak), but the Keycloak CR wires usernameSecret to the site secret's keycloak-db-user key, which nothing forces to match. A deployment supplying keycloak there gets a Keycloak that can never authenticate (FATAL: password authentication failed for user "keycloak") while the actual role sits unused. Suggested fix: derive both from one source — either provision the role from usernameSecret, or render usernameSecret from the URL username.
Found standing up a Keycloak-enabled Site on team-operator v1.30.2 (workshop rebuild, rstudio/rstudio-ide-automation#5619).
Symptom: the Site's
<site>-keycloakPostgresDatabase never reconciles —postgres database mismatched db host— so the keycloak role/database are never created and the Keycloak StatefulSet crashloops onFATAL: password authentication failed for user "keycloak".Cause: an asymmetry in how the keycloak path builds its PostgresDatabase versus how the reconciler compares hosts:
internal/controller/core/site_controller_keycloak.gobuilds the config withHost: dbUrl.Hostname()— port stripped.internal/controller/core/postgresdatabase_controller.go(~L448) comparesspecDbUrl.Host != mainDBURL.Host—url.Hostincludes the port.So whenever the workload secret's
main-database-urlcarries an explicit:5432, the keycloak compare fails unconditionally (spec side is portless), while connect/pm/workbench pass because their spec URLs retain the port. Deployments with portless URLs (the PTD convention) never hit it, which is presumably why it went unnoticed.Repro: Site with
keycloak.enabled: trueand a workload secretmain-database-urlofpostgresql://host:5432/db?sslmode=require.Workaround: write
main-database-urlwithout the explicit port (postgres defaults to 5432).Suggested fix: compare hostnames consistently — either
specDbUrl.Hostname() != mainDBURL.Hostname()in the reconciler, or keep the port in the keycloak path (Host: dbUrl.Host).Addendum — second asymmetry in the same path (username): the reconciler provisions the role from the PostgresDatabase URL username (
<site>_keycloak, e.g.workshop_site_keycloak), but the Keycloak CR wiresusernameSecretto the site secret'skeycloak-db-userkey, which nothing forces to match. A deployment supplyingkeycloakthere gets a Keycloak that can never authenticate (FATAL: password authentication failed for user "keycloak") while the actual role sits unused. Suggested fix: derive both from one source — either provision the role fromusernameSecret, or renderusernameSecretfrom the URL username.