fix(console): support OpenShift Route ingress - #216
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Central YAML (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Amber reviewStatus: Complete VerdictApprove with minor nits (posted as COMMENT). This PR cleanly generalizes the per-gateway console from a Gateway-API-only HTTPRoute to a mode-selected exposure (HTTPRoute in Amber here. Two-sentence summary: the change is well-structured, backward-compatible for existing route configs, and the test diff scrutiny passes (contract changes are additive, not silent flips). I recommend approval once the redundant RBAC grant is trimmed and the cross-PR overlaps with #194 and #151 are acknowledged. What this PR does well
Test Diff ScrutinyI checked the modified assertions in pre-existing tests:
Findings
|
jsell-rh
left a comment
There was a problem hiding this comment.
Verdict
Approve with minor nits (posted as COMMENT). This PR cleanly generalizes the per-gateway console from a Gateway-API-only HTTPRoute to a mode-selected exposure (HTTPRoute in gateway-api mode, edge-terminated OpenShift Route in route mode), and it threads a single resolved ingressMode through provisioning, readiness, self-heal, and teardown so the emitted and observed resources cannot diverge. Error handling, IsNotFound semantics, idempotent reconcile, additive test coverage, and the spec update are all solid; the only substantive issue is a redundant RBAC rule, plus some cross-PR coordination the maintainers should resolve.
Amber here. Two-sentence summary: the change is well-structured, backward-compatible for existing route configs, and the test diff scrutiny passes (contract changes are additive, not silent flips). I recommend approval once the redundant RBAC grant is trimmed and the cross-PR overlaps with #194 and #151 are acknowledged.
What this PR does well
- Single source of truth for ingress mode.
IngressMode(hasGatewayAPI, isOpenShift)is resolved once in bothGatewayReconcilerandGatewayHealthReconciler, and the health loop now usesh.ingressModeinstead of theexposure != nilproxy. This removes the class of bug where a Route-exposed gateway was observed through the Gateway API adapter. - Readiness is exposure-correct.
ConsoleExposureReadygates onAccepted+ResolvedRefsfor HTTPRoutes and onAdmitted=Truefrom at least one router for Routes, soconsole_addressis only published against a live public path.consoleOpenShiftRouteReadycorrectly prefers a specific rejection reason and short-circuits to ready on any admitted router (both covered by tests). - Cleanup converges.
reconcileConsoleExposuredeletes the inactive exposure before creating the active one (prevents two controllers claiming the same host across a mode change),RouteResourcesAbsentprobes both exposure kinds, andDeleteRouteResourcesnow joins errors instead of logging-and-swallowing — matching the "never silently swallow partial failures" convention. - Error wrapping / 404 handling. All new dynamic-client calls wrap with
fmt.Errorf("...: %w", err)and treatIsNotFoundas converged/absent. Nopanic(), no secrets in logs, nocontext.TODO().
Test Diff Scrutiny
I checked the modified assertions in pre-existing tests:
TestIsRoutedGatewayis additive: the pre-existingempty object {}->trueand{"enabled":true}->truecases are unchanged; only{"enabled":false}->falseand host-only ->trueare added. Combined withparseGatewayRouteConfigdefaultingEnabled: truefor any present, non-null route object, existing empty/host-only route records keep behaving as routed. This is the correct, backfilled way to moveenabledfrom ignored to honored — no removed guarantee. It actually fixes a latent inconsistency where a host-only/enabled:falseconfig madeisRoutedGatewayreport routed whileReconcileGatewayskipped creating route resources.TestConsoleRouteReady->TestConsoleExposureReadyHTTPRouteand theselfHealConsole"no exposure port" -> "no selected ingress" cases are renames preserving the same guarantee against the renamed API (ConsoleRouteReady->ConsoleExposureReady,exposure->ingressMode), with new sibling tests for the Route path. No coverage lost.
Findings
- [Minor] Redundant RBAC rule in
deploy/base/controller-rbac.yaml. The newroutes/custom-host(create,update) rule is already fully covered by the pre-existing rule on["routes", "routes/custom-host"]with["get","list","watch","create","update","patch","delete"]. The extra stanza (and its mirror in the spec) grants nothing new and adds config noise. Fold the comment into the existing rule or drop the duplicate. (No security impact — it is a subset of an existing grant.) - [Minor / behavior note]
syncConsoleAddressnow clearsconsole_addresswhen the base domain is unset (previously it returned early leaving the field untouched). This is an improvement (retracts a stale dead link) and is covered byTestSyncConsoleAddressClearsAddressWithoutBaseDomain, but it is a behavior change worth a line in the PR body for reviewers deploying withoutGATEWAY_API_BASE_DOMAIN.
Cross-PR coordination
I reviewed the other 23 open PRs and compared goals, ownership boundaries, data models, interfaces, and change order against #216. Material items maintainers should decide:
- #194
feat(control-plane): adopt upstream OpenShell Helm chart for gateway deployments— HIGH, competing design over the same code. #194 restructures how gateways (and their ingress/console/NetworkPolicy resources) are produced, moving from Go-emitted SSA manifests to a runtime Helm install, and it edits the exact functions #216 changes:internal/gateway/reconciler.go(RouteResourcesAbsent,deleteRouteResources,reconcileConsole),internal/reconciler/{reconciler.go,health.go}, andopenshell-gateway-routing.spec.md. Two direct conflicts: (1) incompatibleRouteResourcesAbsentsignatures — #216 adds aningressModeparameter and a second console-exposure probe, while #194 keeps the current 5-arg form; (2) opposite NetworkPolicy decision — #194 removes the console/router NetworkPolicies entirely ("no NetworkPolicies" design decision), whereas #216 keeps them and adds absence probes for them. Maintainers need to decide the merge order and whether #216's Route-mode console exposure is re-expressed as Helm chart values or retained as Go-emitted resources; merging both without a decision will silently reintroduce or drop the console exposure/NetworkPolicies. - #151
spec(control-plane): gate gateway re-provisioning on desired-state convergence— MEDIUM, conflicting assumption about provisioning re-entry. #151 replaces the phase gate inGatewayReconciler.Handle(skip whenRunning/Provisioning/Degraded) with a convergence gate (skip only whenobserved_generation == generation). #216 newly invokesReconcileConsoleinside the provisioning path's Route branch and repeatedly documents the assumption that "once the gateway reaches Running the provisioning path never runs again (phase gate); the health loop owns the console." Under #151 a spec change re-enters provisioning regardless of phase, so console reconciliation would run from both the provisioning path and the health loop. That is likely benign (both are idempotent) but the ownership assumption #216 relies on no longer holds — the maintainers should confirm the console self-heal/teardown split still behaves as intended once the gate changes. - #179
fix(control-plane): reconcile existing Keycloak clients on gated gateways— LOW/overlap, coordinate merge. #179 edits the samereconcileGatewayHealthfunction (the phase/status write near the end) while #216 edits the earlier console-sync/self-heal/teardown hunks and theselfHealConsoleguard. The concerns are orthogonal (Keycloak client status preservation vs. ingress-mode selection), so this is a mechanical merge overlap rather than a design conflict, but both restructure the health tick and should be rebased in a known order.
No other open PR (image updates #201, trace correlation #207, kind metrics #211, web-console #208/#210/#214/#209, hsctl #206, spec/docs #185/#200/#148/#150, dependency bumps) touches #216's ownership area in a way that requires a design decision.
Findings Summary (ordered by severity, highest first)
- [Minor] Redundant
routes/custom-hostRBAC rule duplicates an existing broader grant - Config / RBAC hygiene (controller-rbac.yaml L33-36) - [Minor]
syncConsoleAddressnow clearsconsole_addresswhen the base domain is unset (behavior change, tested) - Behavior clarity (reconciler.go L1819-1840)
Convention Checklist
| Convention | Result |
|---|---|
No panic() in production code |
Pass |
Errors wrapped with fmt.Errorf context |
Pass |
errors.IsNotFound handled for 404 scenarios |
Pass |
| Never silently swallow partial failures (errors.Join in teardown) | Pass |
| No secrets in logs or error messages | Pass |
| Reconcile pattern (not create-or-skip) | Pass |
Proper context propagation (no context.TODO()) |
Pass |
| Image references consistent across manifests | N/A (no image changes) |
| Test Diff Scrutiny (modified assertions justified / additive) | Pass |
| SecurityContext on pod specs | N/A (no pod specs changed) |
| Spec updated to match behavior | Pass |
| Least-privilege RBAC (no redundant grants) | Fail (redundant duplicate rule) |
Amber reviewStatus: Complete VerdictThis PR cleanly extends the per-gateway console to support OpenShift Route ingress alongside Gateway API, gating Amber Analysis The design correctly funnels both the gateway and its console through the single Findings[Minor] Route semantics change for [Minor] Cross-API robustness of the inactive/console Route probes on non-OpenShift clusters. [Minor/Positive] RBAC least-privilege. Cross-PR coordinationOne other open pull request adopts an upstream OpenShell Helm chart as the mechanism for provisioning gateway resources: it deletes the exact Findings Summary (ordered by severity, highest first)
Convention Checklist
|
jsell-rh
left a comment
There was a problem hiding this comment.
Verdict
This PR cleanly extends the per-gateway console to support OpenShift Route ingress alongside Gateway API, gating consoleAddress on Route Admitted=True, unifying teardown/absence probes by ingress mode, and narrowing the routes/custom-host RBAC to create/update. The change is well-structured, well-tested, and spec-aligned; I recommend COMMENT with a few minor items to confirm before merge.
Amber Analysis
The design correctly funnels both the gateway and its console through the single IngressMode resolver, so the emitted exposure and the observed exposure cannot diverge. Error handling wraps context throughout, cleanup joins partial errors instead of swallowing them, no secrets are logged, and the modified test assertions are additive (renames + new mode cases) rather than flipped guarantees. The remaining notes are about a subtle route-parsing behavior change for pre-existing data and cross-API robustness on non-OpenShift clusters.
Findings
[Minor] Route semantics change for enabled-less route objects.
parseGatewayRouteConfig now defaults Enabled: true for any present, non-empty route object ({}, host-only). Previously Handle unmarshalled directly, so nsConfig.Gateway.Route.Enabled was the JSON zero value (false) for those objects, and the provisioning switch took the delete branch. This flips such gateways to "routed/provisioned". It is documented in the console spec as an intentional compatibility default and actually removes a prior inconsistency (isRoutedGateway already returned true for {}), so it is likely a fix — but please confirm no running gateway persists {}/host-only route JSON with the intent of "not routed". Confidence: Medium.
[Minor] Cross-API robustness of the inactive/console Route probes on non-OpenShift clusters.
RouteResourcesAbsent unconditionally Gets the route.openshift.io/v1 console Route, and reconcileConsoleExposure unconditionally Deletes the inactive exposure (the OpenShift Route in gateway-api mode). On a Gateway-API-only cluster that lacks the Route CRD, these calls normally return a Kubernetes NotFound (tolerated). If instead the API surfaces a discovery/no-match error, RouteResourcesAbsent returns an error and teardown never confirms absence (retries indefinitely), and console reconcile logs a WARN every pass. Consider gating the inactive-mode probe/delete by cluster capability, or confirm the client returns IsNotFound for an unregistered GVR. Confidence: Low.
[Minor/Positive] RBAC least-privilege.
Splitting routes/custom-host into its own rule scoped to create/update is the correct narrowing for the admission check that runs when the controller sets spec.host. Good change.
Cross-PR coordination
One other open pull request adopts an upstream OpenShell Helm chart as the mechanism for provisioning gateway resources: it deletes the exact switch ingressMode block in ReconcileGateway, removes reconcileRouteResources/deleteRouteResources, and replaces the teardown path with a Helm uninstall, moving Deployment/Service/GRPCRoute/BackendTLSPolicy/Route provisioning into chart values. This PR instead deepens the direct dynamic-client reconcile path in the same region (route-mode console exposure, ReconcileConsole from the route branch, exported DeleteRouteResources, ingress-mode-aware RouteResourcesAbsent, mode-selected teardownRoute). These are competing designs for the same provisioning and teardown surface, and they raise an unresolved ownership question — whether console/Route exposure belongs in the Helm chart or in the control plane's direct reconcile. Maintainers should decide a design and a merge order: whichever lands first, the other must be re-implemented against the chosen substrate (Helm values vs direct reconcile) rather than merged as-is.
Findings Summary (ordered by severity, highest first)
- [Minor] Route object without
enablednow defaults to enabled (provisioning behavior change for pre-existing data) - Test Diff Scrutiny / Migration (reconciler.go L1692-L1717) - [Minor] Inactive/console OpenShift Route probe + delete run on non-OpenShift clusters; non-
NotFounderrors could stall teardown - Reconciliation Robustness (reconciler.go L480, console.go L814) - [Minor]
routes/custom-hostnarrowed to create/update - Security (positive) (controller-rbac.yaml L34-L36)
Convention Checklist
| Convention | Result |
|---|---|
No panic() in production code |
Pass |
Errors wrapped with fmt.Errorf context |
Pass |
errors.IsNotFound handled for 404 scenarios |
Pass |
| No secrets in logs or responses | Pass |
| Reconcile pattern (not create-or-skip) | Pass |
| Never silently swallow partial failures | Pass |
| Image references consistent | N/A |
| SecurityContext on pod specs | N/A (no pod specs changed) |
| Test diff scrutiny (no flipped guarantees) | Pass |
| Spec updated to match code | Pass |
| RBAC least-privilege | Pass |
| return gateway.RouteConfig{}, nil | ||
| } | ||
|
|
||
| config := gateway.RouteConfig{Enabled: true} |
There was a problem hiding this comment.
Behavior change worth confirming: a present, non-empty route object that omits enabled (e.g. {} or host-only) now defaults to Enabled: true. Before this PR Handle unmarshalled the route string directly, so nsConfig.Gateway.Route.Enabled was the JSON zero value (false) for those objects and the provisioning switch took the delete branch. This flips such gateways to provisioned/routed. It's documented in the console spec and removes a prior inconsistency (isRoutedGateway already treated {} as routed), so it's likely a fix — but please confirm no running gateway stores {}/host-only route JSON meaning "not routed".
| } | ||
| dynamicProbes = append(dynamicProbes, | ||
| dynamicProbe{httpRouteGVR, consoleName}, | ||
| dynamicProbe{openShiftRouteGVR, consoleName}, |
There was a problem hiding this comment.
This probes the route.openshift.io/v1 console Route unconditionally, including in gateway-api mode on a non-OpenShift cluster that lacks the Route CRD. That's fine if the client returns a Kubernetes NotFound for an unregistered GVR (tolerated below). If instead it returns a discovery/no-match error, RouteResourcesAbsent returns an error and teardown can never confirm absence (retries indefinitely). Consider gating the inactive-mode probe by cluster capability, or confirm IsNotFound holds for a missing GVR. Same concern applies to the unconditional inactive-exposure delete in reconcileConsoleExposure.
| verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] | ||
| # OpenShift checks this subresource when the controller sets spec.host. | ||
| - apiGroups: ["route.openshift.io"] | ||
| resources: ["routes/custom-host"] |
There was a problem hiding this comment.
Good least-privilege change: routes/custom-host is the subresource OpenShift admission checks when the controller sets an explicit spec.host, and create/update are the correct verbs. Narrowing this from the full verb set is the right call.
Summary
Verification
Live verification
Validated on the hysh-ibm-01 OpenShift cluster. The console Route became admitted, the stale HTTPRoute was removed, the console address was published, and the HTTPS endpoint returned the expected OAuth redirect.