keycloak.realmHosts gives a realm a hostname of its own: this release's Service published under a further name, with that name's bare / sent to /realms/<realm>/account/. It renders in both routing modes, and the default [] renders nothing at all.
This PR is the feature, not the migration. Nothing in jk-cluster-config is touched, created or removed. With realmHosts: [] the render is byte-identical to 1.2.0, so no running system changes — which is exactly why adopting the four live objects is a separate step, and why the open question about name-dependencies in that repository belongs to that step rather than to this one.
rule 1: Exact: / + RequestRedirect, ReplaceFullPath → /realms/<realm>/account/, statusCode: 302; rule 2: PathPrefix: / → the Service
One object per entry, never one with several hostnames — spec.hostnames applies to the whole object and a rule's matches are path-based, so inside a shared route every entry would match Exact: / and the realm could not be told apart.
Names carry the list index, as homeassistant's per-host routes do and for the reason the style guide gives (a realm name is not a dependable object-name fragment). Consequence written down rather than left to be found: reordering the list renames and recreates the objects. NOTES.txt prints which index carries which hostname and realm.
statusCode: 302 is written out at the field's own default. 301 and 302 are both Support: Core, so #119's trap — an implementation declining an Extended code sets Accepted: False on the whole route — does not apply; ingress-nginx emits 302 for app-root, and a value that is only accidentally right documents nothing.
The work is the Ingress half
Two convention rules do not survive these objects, and both deviations are required, not merely tolerated — recorded in README §4 and in Chart-Style-Guide:
tlsSecretName per entry, required, never derived. These hostnames live in zones whose certificates already exist and several entries typically share one (a wildcard). A derived name would ask cert-manager for a second certificate for a name already covered — one ACME order per hostname, for nothing.
clusterIssuer optional per entry, and empty is the normal case: cert-manager keys a Certificate by the secret it writes, so of several objects naming one secret exactly one may carry the annotation. The same deliberate omission the qBittorrent object in freesailarr makes.
annotations per entry, because the set is not uniform: of the four live hostnames three carry the proxy-buffer pair and one does not.
Also per entry: className (falls back to ingress.className) and parentRefs (falls back to httpRoute.parentRefs) — the two counterparts of each other, because realm hostnames sit in several DNS zones and a chart-global parent pinning one sectionName would attach every realm route to one certificate's listener.
Measured, not assumed
The four live objects in namespace keycloak (release jk-cluster-config-fleet-cd-keycloak-realm-ingress) were read with read-only kubectl and every field of the issue's table reproduced: all four className: nginx, one rule path: / pathType: Prefix → keycloak-keycloak-service:8080; app-rootbekemeyer-klein / muth-event / mail / klein-family on id.klein.pub; secrets tls-id-wildcard-ingress (×2), tls-id-mail-ingress, tls-id-klein-pub-ingress; cluster-issuer on the mail and klein.pub objects only; the proxy-buffer pair on three of four, absent on id.klein.pub. The running ingress-nginx is v1.14.5 (rancher/nginx-ingress-controller:v1.14.5-hardened2), i.e. the version the app-root → 302 claim was measured against.
Those values were used as throwaway test input to check the render against the table. None of this installation's data is in the chart: values.yaml ships realmHosts: [] and documents the form with neutral examples.
The empty render diff, for the defaults, in both modes — helm template on this branch versus origin/main, with ci/keycloak/test-values.yaml at origin/main's version:
With the four measured entries supplied, the render is non-empty and matches the table field for field — four Ingress objects with the right app-root, the right secret, the issuer on two of them and the buffers on three; four HTTPRoutes with the two-rule shape above.
Render aborts, and what each prevents
Every path below was executed, not reasoned about:
Abort
Prevents
no host
ingress-nginx reads an empty host as a catch-all; an HTTPRoute with no hostnames attaches to every hostname of its listener
no realm
/realms//account/ — a 404 behind a healthy hostname, certificate and route
realm containing /
a silently retargeted redirect
no tlsSecretName in Ingress mode
a TLS block with no secret: the controller's default certificate, a browser-only failure
a repeated host, or one the release already publishes (ingress.domain, ingress.admin.domain, an httpRoute hostname)
webhook rejection in Ingress mode; two merged routes decided by object age in Gateway mode
a filled list with both modes off
no object at all, and nothing in the release that reveals the list was ignored
The entry-shape checks sit in one helper so they exist in both modes. Two findings from running them:
A precise message hid the structural one. With both modes on, the realm check fired first and answered "this entry needs a tlsSecretName" to a values file whose actual defect is that both modes are enabled. Template execution order is not something a chart may rely on, so the fix is scope, not ordering: the Ingress template computes the realm list only while httpRoute.enabled is false. Now the mutual-exclusion message wins. Added to Lessons-Learned, because any chart adding a per-entry check next to a mode guard can repeat it.
The strict schema makes two template guards unreachable through an ordinary render (required: [host, realm], minLength: 1). They are kept as the second guard the repo's required() convention asks for, and checked to be live rather than dead by rendering once with --skip-schema-validation — where all three fire correctly.
CI
ci/run-httproute-match-test.sh locally: assertions held: 60, 21 charts, 0 failed, with keycloak at ok (8 assertion(s) held, 2 not checked) — up from 4/0. The two "not checked" lines are the backend-less redirect rules, correct by design. The #118 prefix/redirect pairing does not fire on these routes and must not: it is scoped to rules that strip a prefix with a ReplacePrefixMatch rewrite, and a realm route strips nothing. run-quantity-render-test.sh and run-selector-match-test.sh: 0 failed. helm lint --strict keycloak: 0 failed.
The two CI entries live in ci/keycloak/test-values.yaml, which ci/httproute/keycloak.yaml is applied on top of, so one definition covers both modes — two Ingress objects installed by the install-test, two HTTPRoutes rendered by the match test. Defining them in the overlay only would have left the Ingress half — the half carrying the required value and the deviation — with no coverage. They are the two forms, one with a clusterIssuer and one without, and the render test holds their inertness in Gateway mode (verified: no tlsSecretName, clusterIssuer or per-entry annotation appears anywhere in the Gateway render).
Version
1.3.0+up26.8.0 — additive, default [], no value becomes required.
⚠️ README line 184 needs a rebase after the first of two merges
This PR and the rss-bridge Gateway-API round both change the single sentence in README.md that carries the whole history of the assertion count. At my last git fetch origin main, the rss-bridge round had not landed (main at 6517643), so this branch's text does not contain its 58 over 22.
Its wiki edit had landed, and I built on it there rather than over it: CI-CD now carries both numbers with the explicit note that neither total includes the other.
So, for whichever of the two merges second: rebase and merge the two sentences — keep the other round's, append this one's — and read the new total out of the first CI run on main rather than adding 58 and 60 together. The two were measured on branches that did not contain each other; what is durable from this PR is the delta of +4, which is why the README sentence states the delta first and labels its own total with the basis it was measured on.
Not proven, and it cannot be here
No Gateway controller runs in CI, so no HTTPRoute in this repository ever reaches Accepted: True and nothing here shows that a realm hostname answers or that a redirect is served. That the Gateway mode of this repository routes at all is now measured in production on freesailarr — evidence for the form, and not for this chart and not for these routes. On the Ingress side, CI has no ingress controller either, so app-root is exercised by nobody; what the install-test adds is that the API server accepts the objects and that the Service port named by name exists.
The app-root → return 302 rendering is quoted from #119/#122 at nginx.tmpl:1000. I confirmed the running controller version matches (v1.14.5) but could not read the template itself — pods/exec is blocked for my account — so that line reference stays relayed rather than re-measured.
`keycloak.realmHosts` gives a realm a hostname of its own: this release's Service published under a further name, with that name's bare `/` sent to `/realms/<realm>/account/`. It renders in **both** routing modes, and the default `[]` renders nothing at all.
**This PR is the feature, not the migration.** Nothing in `jk-cluster-config` is touched, created or removed. With `realmHosts: []` the render is byte-identical to `1.2.0`, so no running system changes — which is exactly why adopting the four live objects is a separate step, and why the open question about name-dependencies in that repository belongs to that step rather than to this one.
## What it renders
| Mode | Object per entry | How the bare `/` reaches the realm |
|---|---|---|
| Ingress | `<release>-keycloak-realm-ingress`, `…-realm-ingress-1`, … | `nginx.ingress.kubernetes.io/app-root` |
| Gateway | `<release>-keycloak-realm-httproute`, `…-realm-httproute-1`, … | rule 1: `Exact: /` + `RequestRedirect`, `ReplaceFullPath` → `/realms/<realm>/account/`, `statusCode: 302`; rule 2: `PathPrefix: /` → the Service |
**One object per entry, never one with several hostnames** — `spec.hostnames` applies to the whole object and a rule's matches are path-based, so inside a shared route every entry would match `Exact: /` and the realm could not be told apart.
**Names carry the list index**, as `homeassistant`'s per-host routes do and for the reason the style guide gives (a realm name is not a dependable object-name fragment). Consequence written down rather than left to be found: reordering the list renames and recreates the objects. `NOTES.txt` prints which index carries which hostname and realm.
**`statusCode: 302` is written out** at the field's own default. 301 and 302 are both `Support: Core`, so #119's trap — an implementation declining an Extended code sets `Accepted: False` on the *whole* route — does not apply; ingress-nginx emits 302 for `app-root`, and a value that is only accidentally right documents nothing.
## The work is the Ingress half
Two convention rules do not survive these objects, and both deviations are **required**, not merely tolerated — recorded in README §4 and in `Chart-Style-Guide`:
- **`tlsSecretName` per entry, required, never derived.** These hostnames live in zones whose certificates already exist and several entries typically share one (a wildcard). A derived name would ask cert-manager for a second certificate for a name already covered — one ACME order per hostname, for nothing.
- **`clusterIssuer` optional per entry**, and empty is the normal case: cert-manager keys a Certificate by the secret it writes, so of several objects naming one secret exactly one may carry the annotation. The same deliberate omission the qBittorrent object in `freesailarr` makes.
- **`annotations` per entry**, because the set is not uniform: of the four live hostnames three carry the proxy-buffer pair and one does not.
Also per entry: `className` (falls back to `ingress.className`) and `parentRefs` (falls back to `httpRoute.parentRefs`) — the two counterparts of each other, because realm hostnames sit in several DNS zones and a chart-global parent pinning one `sectionName` would attach every realm route to one certificate's listener.
## Measured, not assumed
The four live objects in namespace `keycloak` (release `jk-cluster-config-fleet-cd-keycloak-realm-ingress`) were read with read-only `kubectl` and **every field of the issue's table reproduced**: all four `className: nginx`, one rule `path: / pathType: Prefix` → `keycloak-keycloak-service:8080`; `app-root` `bekemeyer-klein` / `muth-event` / `mail` / **`klein-family`** on `id.klein.pub`; secrets `tls-id-wildcard-ingress` (×2), `tls-id-mail-ingress`, `tls-id-klein-pub-ingress`; `cluster-issuer` on the `mail` and `klein.pub` objects only; the proxy-buffer pair on three of four, absent on `id.klein.pub`. The running ingress-nginx is `v1.14.5` (`rancher/nginx-ingress-controller:v1.14.5-hardened2`), i.e. the version the `app-root` → 302 claim was measured against.
Those values were used as **throwaway** test input to check the render against the table. **None of this installation's data is in the chart**: `values.yaml` ships `realmHosts: []` and documents the form with neutral examples.
**The empty render diff, for the defaults, in both modes** — `helm template` on this branch versus `origin/main`, with `ci/keycloak/test-values.yaml` at `origin/main`'s version:
```
=== INGRESS MODE (fixture defaults) ===
EMPTY DIFF
=== GATEWAY MODE (fixture + overlay) ===
EMPTY DIFF
```
With the four measured entries supplied, the render is non-empty and matches the table field for field — four Ingress objects with the right `app-root`, the right secret, the issuer on two of them and the buffers on three; four HTTPRoutes with the two-rule shape above.
## Render aborts, and what each prevents
Every path below was executed, not reasoned about:
| Abort | Prevents |
|---|---|
| no `host` | ingress-nginx reads an empty host as a **catch-all**; an `HTTPRoute` with no hostnames attaches to every hostname of its listener |
| no `realm` | `/realms//account/` — a 404 behind a healthy hostname, certificate and route |
| `realm` containing `/` | a silently retargeted redirect |
| no `tlsSecretName` in Ingress mode | a TLS block with no secret: the controller's default certificate, a browser-only failure |
| a repeated host, or one the release already publishes (`ingress.domain`, `ingress.admin.domain`, an `httpRoute` hostname) | webhook rejection in Ingress mode; two **merged** routes decided by object age in Gateway mode |
| a filled list with **both** modes off | no object at all, and nothing in the release that reveals the list was ignored |
The entry-shape checks sit in one helper so they exist in **both** modes. Two findings from running them:
1. **A precise message hid the structural one.** With both modes on, the realm check fired first and answered *"this entry needs a `tlsSecretName`"* to a values file whose actual defect is that both modes are enabled. Template execution order is not something a chart may rely on, so the fix is scope, not ordering: the Ingress template computes the realm list only while `httpRoute.enabled` is false. Now the mutual-exclusion message wins. Added to `Lessons-Learned`, because any chart adding a per-entry check next to a mode guard can repeat it.
2. **The strict schema makes two template guards unreachable** through an ordinary render (`required: [host, realm]`, `minLength: 1`). They are kept as the second guard the repo's `required()` convention asks for, and checked to be live rather than dead by rendering once with `--skip-schema-validation` — where all three fire correctly.
## CI
`ci/run-httproute-match-test.sh` locally: **`assertions held: 60`, 21 charts, 0 failed**, with keycloak at `ok (8 assertion(s) held, 2 not checked)` — up from 4/0. The two "not checked" lines are the backend-less redirect rules, correct by design. The **#118 prefix/redirect pairing does not fire** on these routes and must not: it is scoped to rules that strip a prefix with a `ReplacePrefixMatch` rewrite, and a realm route strips nothing. `run-quantity-render-test.sh` and `run-selector-match-test.sh`: 0 failed. `helm lint --strict keycloak`: 0 failed.
The two CI entries live in **`ci/keycloak/test-values.yaml`**, which `ci/httproute/keycloak.yaml` is applied on top of, so one definition covers both modes — two Ingress objects installed by the install-test, two HTTPRoutes rendered by the match test. Defining them in the overlay only would have left the Ingress half — the half carrying the required value and the deviation — with no coverage. They are the two forms, one with a `clusterIssuer` and one without, and the render test holds their **inertness** in Gateway mode (verified: no `tlsSecretName`, `clusterIssuer` or per-entry annotation appears anywhere in the Gateway render).
## Version
`1.3.0+up26.8.0` — additive, default `[]`, no value becomes required.
## ⚠️ README line 184 needs a rebase after the first of two merges
This PR and the `rss-bridge` Gateway-API round both change the single sentence in `README.md` that carries the whole history of the assertion count. **At my last `git fetch origin main`, the `rss-bridge` round had not landed** (`main` at `6517643`), so this branch's text does not contain its `58 over 22`.
Its wiki edit *had* landed, and I built on it there rather than over it: `CI-CD` now carries both numbers with the explicit note that neither total includes the other.
So, for whichever of the two merges **second**: rebase and merge the two sentences — keep the other round's, append this one's — and **read the new total out of the first CI run on `main`** rather than adding `58` and `60` together. The two were measured on branches that did not contain each other; what is durable from this PR is the **delta of `+4`**, which is why the README sentence states the delta first and labels its own total with the basis it was measured on.
## Not proven, and it cannot be here
**No Gateway controller runs in CI**, so no `HTTPRoute` in this repository ever reaches `Accepted: True` and nothing here shows that a realm hostname answers or that a redirect is served. That the Gateway mode of this repository routes at all is now measured **in production** on `freesailarr` — evidence for the *form*, and not for this chart and not for these routes. On the Ingress side, CI has no ingress controller either, so `app-root` is exercised by nobody; what the install-test adds is that the API server accepts the objects and that the Service port named by name exists.
The `app-root` → `return 302` rendering is quoted from #119/#122 at `nginx.tmpl:1000`. I confirmed the **running controller version matches** (`v1.14.5`) but could not read the template itself — `pods/exec` is blocked for my account — so that line reference stays relayed rather than re-measured.
Fixes #122
keycloak.realmHosts gives a realm a hostname of its own: this release's
Service published under a further name, with that name's bare / sent to
/realms/<realm>/account/. It renders in BOTH modes - one Ingress per entry
carrying nginx.ingress.kubernetes.io/app-root, or one HTTPRoute per entry
whose Exact match on / carries a RequestRedirect filter - because a routing value in
this repository is mode-independent by design - ingress.domain is read in
Gateway mode too - so a feature rendering in one mode only would silently
disappear when an installation switched.
Default []: nothing renders, and the render is byte-identical to 1.2.0 for
the fixture's values in both modes. Minor, additive, no value becomes
required.
ONE OBJECT PER ENTRY, not one with several hostnames. spec.hostnames applies
to the whole object and a rule's matches are path-based, so inside a shared
route every entry would match Exact: / and the realm could not be told
apart. Names therefore carry the LIST INDEX, as homeassistant's per-host
routes do and for the same reason - a realm name is not a dependable
object-name fragment. The consequence is written down rather than left to be
discovered: reordering the list renames and recreates the objects.
THE WORK IS IN THE INGRESS HALF, NOT THE ROUTE. Two convention rules do not
survive these objects, and both deviations are required rather than
tolerated: tlsSecretName is a per-entry value with NO default, because these
hostnames already have certificates and several entries typically share one,
so a derived name would ask cert-manager for a second certificate for a name
a wildcard already covers; and the cert-manager annotation is OPTIONAL per
entry, because cert-manager keys a Certificate by the secret it writes and
only one object per secret may request it - the same deliberate omission the
qBittorrent object in freesailarr makes. Annotations are per entry too: of
the four hostnames this was written for, three carry the proxy-buffer pair
and one does not, which a chart-global block could not express.
statusCode: 302 is written out at the field's own default. 301 and 302 are
both Support: Core, so the portability trap of #119 - an implementation
declining an Extended code sets Accepted: False on the WHOLE route - does
not apply here; ingress-nginx emits 302 for app-root, and a value that is
only accidentally right documents nothing.
Five render aborts, each against a failure that reports nothing: an entry
without a host (ingress-nginx reads an empty host as a catch-all for the
whole controller), without a realm (/realms//account/, a 404 behind a
healthy hostname and certificate), without a tlsSecretName in Ingress mode
(the controller's default certificate, a browser-only failure), a repeated
host or one the release already publishes (webhook rejection in Ingress
mode, two merged routes decided by object age in Gateway mode), and a filled
list with both modes off (no object at all, and nothing in the release that
reveals the list was ignored). The entry-shape checks sit in a shared helper
so they exist in both modes, and the Ingress template skips them while
httpRoute.enabled is also true so that the specific message cannot hide the
mutual-exclusion one.
CI: the two entries live in ci/keycloak/test-values.yaml, which
ci/httproute/keycloak.yaml is applied on top of, so one definition covers
both modes - two Ingress objects in the install-test, two HTTPRoutes in the
render test. They cover both Ingress forms, with and without a clusterIssuer,
and the render test holds their inertness in Gateway mode. The httproute
match test goes from 56 assertions over 21 charts to 60, keycloak's share
from 4 to 8 plus two "not checked" lines for the backend-less redirect rules.
The #118 prefix/redirect pairing correctly does not fire: it is scoped to
rules that strip a prefix, and a realm route strips nothing.
NOT IN THIS CHANGE: the migration. Nothing in jk-cluster-config is touched.
With the default [] no running system changes, so the order a takeover has to
run in is documented instead - remove the objects in the owning repository
first, then install with the list filled, because the reverse or both at once
is rejected by ingress-nginx's admission webhook and leaves a Fleet bundle in
ErrApplied until forceSyncGeneration is raised. Whether anything there
depends on those objects BY NAME is unchecked and named as unchecked.
Fixes#122
The rss-bridge Gateway-API round landed while this branch was open, and the
two rounds shared exactly one line: the sentence in README.md that carries
the whole history of the httproute-match assertion count.
Merged rather than rebased on purpose: the branch is already pushed and its
history is not rewritten here, so nothing of the other round's text can be
lost to a force push.
Their half is kept verbatim, including the delta rule they added - that
58/22 and 60/21 were measured on branches which did not contain each other,
so the delta is what either number can be trusted for, and the running total
belongs to the first branch that holds both.
This branch IS that first branch, so the open question is now answered with
a measurement instead of an addition: 62 assertions over 22 charts, 0 failed,
read off ci/run-httproute-match-test.sh on this merge. keycloak contributes
8 of them, plus the two "not checked" lines for its realm routes'
backend-less redirect rules.
Update: the rss-bridge round landed, and README line 184 is settled — the warning above is resolved
At the time this PR was opened, main was at 6517643 and the rss-bridge Gateway-API round had not merged, so the body above warned that the shared sentence in README.md would need a rebase after the first of the two merges. That has now happened: main carries 8896fc8, and this branch has been brought up to date.
Merged, not rebased. The branch was already pushed, so rewriting its history would have meant a force push — which the house rule forbids precisely because it can silently delete a parallel edit. 5cdb2a6 is therefore a merge commit, and the other round's text is kept verbatim, including the delta rule it added: 58/22 and 60/21 were each measured on a branch that did not contain the other, so the delta is what either figure can be trusted for.
And their sentence left one thing open — "the running total is whatever the first run on main prints once both have landed" — which this branch can now answer by measurement rather than by addition, because it is the first state that contains both:
62 over 22, 0 failed — which is what README line 184 now states, with the forward-looking clause replaced by the figure it asked for. keycloak: ok (8 assertion(s) held, 2 not checked) is unchanged by the merge.
Nothing else in the diff changed: the net change against main is still the same 10 files, and the two template files the merge brought in (rss-bridge/templates/rss-bridge.httproute.yaml, ci/httproute/rss-bridge.yaml) are the other round's, untouched here.
CI on the pre-merge commit was fully green, including the install-test, whose helm install output shows the two realm Ingress objects accepted by the API server:
REALM HOSTNAMES - 2 further name(s), each landing in ONE realm
https://realm-one.ci.example.com
object ci-keycloak-realm-ingress
cert tls-ci-realm-shared-ingress, NOT requested by this object - it must already exist or be issued elsewhere
https://realm-two.ci.example.com
object ci-keycloak-realm-ingress-1
cert tls-ci-realm-two-ingress, requested by THIS object (ci-selfsigned)
The wiki was updated the same way — the other round's CI-CD edit had already landed, so this one builds on it rather than over it, and both numbers plus the delta rule stand there side by side.
## Update: the `rss-bridge` round landed, and README line 184 is settled — the warning above is resolved
At the time this PR was opened, `main` was at `6517643` and the `rss-bridge` Gateway-API round had not merged, so the body above warned that the shared sentence in `README.md` would need a rebase after the first of the two merges. That has now happened: `main` carries `8896fc8`, and this branch has been brought up to date.
**Merged, not rebased.** The branch was already pushed, so rewriting its history would have meant a force push — which the house rule forbids precisely because it can silently delete a parallel edit. `5cdb2a6` is therefore a merge commit, and the other round's text is kept verbatim, including the **delta rule** it added: `58/22` and `60/21` were each measured on a branch that did not contain the other, so the delta is what either figure can be trusted for.
**And their sentence left one thing open — "the running total is whatever the first run on `main` prints once both have landed" — which this branch can now answer by measurement rather than by addition**, because it is the first state that contains both:
```
====================== httproute render test ======================
checked: 22 - bentopdf bulwark calibre-web-automated cyberchef freesailarr garage
homeassistant keycloak matrix-synapse outline paperless-ngx patchmon
rallly rss-bridge searxng stalwart sure template-chart
urlaubsverwaltung voucher-vault ytdlp-web-player zipline
not exercised: 8 - apache-tika gotenberg minecraft-router minecraft-server samba
satisfactory teamspeak-server valkey
failed: 0
assertions held: 62
==================================================================
```
`62` over `22`, `0` failed — which is what README line 184 now states, with the forward-looking clause replaced by the figure it asked for. `keycloak: ok (8 assertion(s) held, 2 not checked)` is unchanged by the merge.
Nothing else in the diff changed: the net change against `main` is still the same 10 files, and the two template files the merge brought in (`rss-bridge/templates/rss-bridge.httproute.yaml`, `ci/httproute/rss-bridge.yaml`) are the other round's, untouched here.
CI on the pre-merge commit was fully green, including the install-test, whose `helm install` output shows the two realm Ingress objects accepted by the API server:
```
REALM HOSTNAMES - 2 further name(s), each landing in ONE realm
https://realm-one.ci.example.com
object ci-keycloak-realm-ingress
cert tls-ci-realm-shared-ingress, NOT requested by this object - it must already exist or be issued elsewhere
https://realm-two.ci.example.com
object ci-keycloak-realm-ingress-1
cert tls-ci-realm-two-ingress, requested by THIS object (ci-selfsigned)
```
The wiki was updated the same way — the other round's `CI-CD` edit had already landed, so this one builds on it rather than over it, and both numbers plus the delta rule stand there side by side.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
keycloak.realmHostsgives a realm a hostname of its own: this release's Service published under a further name, with that name's bare/sent to/realms/<realm>/account/. It renders in both routing modes, and the default[]renders nothing at all.This PR is the feature, not the migration. Nothing in
jk-cluster-configis touched, created or removed. WithrealmHosts: []the render is byte-identical to1.2.0, so no running system changes — which is exactly why adopting the four live objects is a separate step, and why the open question about name-dependencies in that repository belongs to that step rather than to this one.What it renders
/reaches the realm<release>-keycloak-realm-ingress,…-realm-ingress-1, …nginx.ingress.kubernetes.io/app-root<release>-keycloak-realm-httproute,…-realm-httproute-1, …Exact: /+RequestRedirect,ReplaceFullPath→/realms/<realm>/account/,statusCode: 302; rule 2:PathPrefix: /→ the ServiceOne object per entry, never one with several hostnames —
spec.hostnamesapplies to the whole object and a rule's matches are path-based, so inside a shared route every entry would matchExact: /and the realm could not be told apart.Names carry the list index, as
homeassistant's per-host routes do and for the reason the style guide gives (a realm name is not a dependable object-name fragment). Consequence written down rather than left to be found: reordering the list renames and recreates the objects.NOTES.txtprints which index carries which hostname and realm.statusCode: 302is written out at the field's own default. 301 and 302 are bothSupport: Core, so #119's trap — an implementation declining an Extended code setsAccepted: Falseon the whole route — does not apply; ingress-nginx emits 302 forapp-root, and a value that is only accidentally right documents nothing.The work is the Ingress half
Two convention rules do not survive these objects, and both deviations are required, not merely tolerated — recorded in README §4 and in
Chart-Style-Guide:tlsSecretNameper entry, required, never derived. These hostnames live in zones whose certificates already exist and several entries typically share one (a wildcard). A derived name would ask cert-manager for a second certificate for a name already covered — one ACME order per hostname, for nothing.clusterIssueroptional per entry, and empty is the normal case: cert-manager keys a Certificate by the secret it writes, so of several objects naming one secret exactly one may carry the annotation. The same deliberate omission the qBittorrent object infreesailarrmakes.annotationsper entry, because the set is not uniform: of the four live hostnames three carry the proxy-buffer pair and one does not.Also per entry:
className(falls back toingress.className) andparentRefs(falls back tohttpRoute.parentRefs) — the two counterparts of each other, because realm hostnames sit in several DNS zones and a chart-global parent pinning onesectionNamewould attach every realm route to one certificate's listener.Measured, not assumed
The four live objects in namespace
keycloak(releasejk-cluster-config-fleet-cd-keycloak-realm-ingress) were read with read-onlykubectland every field of the issue's table reproduced: all fourclassName: nginx, one rulepath: / pathType: Prefix→keycloak-keycloak-service:8080;app-rootbekemeyer-klein/muth-event/mail/klein-familyonid.klein.pub; secretstls-id-wildcard-ingress(×2),tls-id-mail-ingress,tls-id-klein-pub-ingress;cluster-issueron themailandklein.pubobjects only; the proxy-buffer pair on three of four, absent onid.klein.pub. The running ingress-nginx isv1.14.5(rancher/nginx-ingress-controller:v1.14.5-hardened2), i.e. the version theapp-root→ 302 claim was measured against.Those values were used as throwaway test input to check the render against the table. None of this installation's data is in the chart:
values.yamlshipsrealmHosts: []and documents the form with neutral examples.The empty render diff, for the defaults, in both modes —
helm templateon this branch versusorigin/main, withci/keycloak/test-values.yamlatorigin/main's version:With the four measured entries supplied, the render is non-empty and matches the table field for field — four Ingress objects with the right
app-root, the right secret, the issuer on two of them and the buffers on three; four HTTPRoutes with the two-rule shape above.Render aborts, and what each prevents
Every path below was executed, not reasoned about:
hostHTTPRoutewith no hostnames attaches to every hostname of its listenerrealm/realms//account/— a 404 behind a healthy hostname, certificate and routerealmcontaining/tlsSecretNamein Ingress modeingress.domain,ingress.admin.domain, anhttpRoutehostname)The entry-shape checks sit in one helper so they exist in both modes. Two findings from running them:
tlsSecretName" to a values file whose actual defect is that both modes are enabled. Template execution order is not something a chart may rely on, so the fix is scope, not ordering: the Ingress template computes the realm list only whilehttpRoute.enabledis false. Now the mutual-exclusion message wins. Added toLessons-Learned, because any chart adding a per-entry check next to a mode guard can repeat it.required: [host, realm],minLength: 1). They are kept as the second guard the repo'srequired()convention asks for, and checked to be live rather than dead by rendering once with--skip-schema-validation— where all three fire correctly.CI
ci/run-httproute-match-test.shlocally:assertions held: 60, 21 charts, 0 failed, with keycloak atok (8 assertion(s) held, 2 not checked)— up from 4/0. The two "not checked" lines are the backend-less redirect rules, correct by design. The #118 prefix/redirect pairing does not fire on these routes and must not: it is scoped to rules that strip a prefix with aReplacePrefixMatchrewrite, and a realm route strips nothing.run-quantity-render-test.shandrun-selector-match-test.sh: 0 failed.helm lint --strict keycloak: 0 failed.The two CI entries live in
ci/keycloak/test-values.yaml, whichci/httproute/keycloak.yamlis applied on top of, so one definition covers both modes — two Ingress objects installed by the install-test, two HTTPRoutes rendered by the match test. Defining them in the overlay only would have left the Ingress half — the half carrying the required value and the deviation — with no coverage. They are the two forms, one with aclusterIssuerand one without, and the render test holds their inertness in Gateway mode (verified: notlsSecretName,clusterIssueror per-entry annotation appears anywhere in the Gateway render).Version
1.3.0+up26.8.0— additive, default[], no value becomes required.⚠️ README line 184 needs a rebase after the first of two merges
This PR and the
rss-bridgeGateway-API round both change the single sentence inREADME.mdthat carries the whole history of the assertion count. At my lastgit fetch origin main, therss-bridgeround had not landed (mainat6517643), so this branch's text does not contain its58 over 22.Its wiki edit had landed, and I built on it there rather than over it:
CI-CDnow carries both numbers with the explicit note that neither total includes the other.So, for whichever of the two merges second: rebase and merge the two sentences — keep the other round's, append this one's — and read the new total out of the first CI run on
mainrather than adding58and60together. The two were measured on branches that did not contain each other; what is durable from this PR is the delta of+4, which is why the README sentence states the delta first and labels its own total with the basis it was measured on.Not proven, and it cannot be here
No Gateway controller runs in CI, so no
HTTPRoutein this repository ever reachesAccepted: Trueand nothing here shows that a realm hostname answers or that a redirect is served. That the Gateway mode of this repository routes at all is now measured in production onfreesailarr— evidence for the form, and not for this chart and not for these routes. On the Ingress side, CI has no ingress controller either, soapp-rootis exercised by nobody; what the install-test adds is that the API server accepts the objects and that the Service port named by name exists.The
app-root→return 302rendering is quoted from #119/#122 atnginx.tmpl:1000. I confirmed the running controller version matches (v1.14.5) but could not read the template itself —pods/execis blocked for my account — so that line reference stays relayed rather than re-measured.Fixes #122
Update: the
rss-bridgeround landed, and README line 184 is settled — the warning above is resolvedAt the time this PR was opened,
mainwas at6517643and therss-bridgeGateway-API round had not merged, so the body above warned that the shared sentence inREADME.mdwould need a rebase after the first of the two merges. That has now happened:maincarries8896fc8, and this branch has been brought up to date.Merged, not rebased. The branch was already pushed, so rewriting its history would have meant a force push — which the house rule forbids precisely because it can silently delete a parallel edit.
5cdb2a6is therefore a merge commit, and the other round's text is kept verbatim, including the delta rule it added:58/22and60/21were each measured on a branch that did not contain the other, so the delta is what either figure can be trusted for.And their sentence left one thing open — "the running total is whatever the first run on
mainprints once both have landed" — which this branch can now answer by measurement rather than by addition, because it is the first state that contains both:62over22,0failed — which is what README line 184 now states, with the forward-looking clause replaced by the figure it asked for.keycloak: ok (8 assertion(s) held, 2 not checked)is unchanged by the merge.Nothing else in the diff changed: the net change against
mainis still the same 10 files, and the two template files the merge brought in (rss-bridge/templates/rss-bridge.httproute.yaml,ci/httproute/rss-bridge.yaml) are the other round's, untouched here.CI on the pre-merge commit was fully green, including the install-test, whose
helm installoutput shows the two realm Ingress objects accepted by the API server:The wiki was updated the same way — the other round's
CI-CDedit had already landed, so this one builds on it rather than over it, and both numbers plus the delta rule stand there side by side.