Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🚫 Excluded labels (none allowed) (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Central YAML (inherited) Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
| │ GRPCRoute, BackendTLSPolicy (PR #2728) | ||
| │ (NetworkPolicy disabled — see decision below) | ||
| ├─ 8. Reconcile OpenShift SCC binding ← Go (unchanged) | ||
| ├─ 9. Reconcile ingress (BackendCA ConfigMap) ← Go (if not covered by chart) |
There was a problem hiding this comment.
In what cases would this not be covered by the chart?
There was a problem hiding this comment.
The only scenario I can think of at the moment is when the 'certgen' job fails to create the BackendCA Configmap due to a cert-manager timeout. In that case, we can circle back and run the equivalent of a 'helm upgrade'
| - AND the release namespace SHALL be the gateway's API-assigned namespace | ||
| - AND `Install.CreateNamespace` SHALL be `false` (the reconciler creates the namespace itself) | ||
|
|
||
| #### Scenario: Gateway update (Helm upgrade) |
There was a problem hiding this comment.
We are not currently handling upgrades. Let's specifically not in this spec that upgrades are currently not handled. Only install when a gateway is created / and uninstall when a gateway is deleted.
There was a problem hiding this comment.
The only time we might invoke 'helm upgrade' is in a "retry" scenario where initial install was not fully successful
|
|
||
| - GIVEN the environment variable `HELM_CHART_REGISTRY` is set (e.g. `oci://ghcr.io/nvidia/openshell/helm-chart`) | ||
| - WHEN the reconciler loads the chart | ||
| - THEN it SHALL pull from the OCI registry instead of the embedded path |
There was a problem hiding this comment.
Can we ensure that the reconciler pulls the chart ONCE during startup, so it does not need to re-pull for every reconcile?
There was a problem hiding this comment.
Could it be an init container that does the pull and then development mode will use the same logic as embedded chart?
| | `kubernetes-secrets` driver | `credentialDrivers.kubernetesSecrets.enabled=true` | | ||
| | Vault driver | `credentialDrivers.vault.enabled=true`, `credentialDrivers.vault.*` | | ||
|
|
||
| #### Ingress Values (conditional) |
There was a problem hiding this comment.
control-plane currently deploys a route.openshift.io Route resource in cases where a BackendTLSPolicy w/ Gateway API will not work (e.g. OpenShift versions under 4.22). We should use the chart to deploy the Route in these cases as well.
| - GIVEN the control plane manages sandbox CRD installation separately | ||
| - WHEN computing Helm values | ||
| - THEN `agentSandbox.preflight.enabled` SHALL be set to `false` | ||
| - AND the chart SHALL not fail if the sandbox CRD API is not yet served |
There was a problem hiding this comment.
I think that actually this is a good check to keep in place. Agent Sandbox is an installation pre-requisite. If it is not in place on the cluster, it is OK for the helm install to fail and for an error to be logged.
|
|
||
| --- | ||
|
|
||
| ### Requirement: Migration Path |
There was a problem hiding this comment.
The migration path can be very simple, because this platform is still in 'beta' state.
New gateway creations will use the helm chart
Don't worry about existing gateways.
There was a problem hiding this comment.
See comment about deleting namespaces.
| - WHEN the GatewayReconciler processes the event | ||
| - THEN it SHALL call `action.Uninstall` to remove chart-managed resources | ||
| - AND it SHALL separately clean up non-chart resources (database, console, SCC binding, extra network policies, Keycloak clients) | ||
| - AND it SHALL NOT delete the namespace (consistent with current behavior) |
There was a problem hiding this comment.
Let's begin to delete the namespace. We want to do this anyways, plus, it makes migration paths easier (if a helm chart was installed for the gateway, then run 'helm uninstall' before deleting namespace. Otherwise, just delete the namespace)
|
|
||
| Existing gateway deployments use directly-applied resources (SSA). Transitioning to Helm-managed releases requires adopting existing resources into the Helm release without downtime. | ||
|
|
||
| #### Scenario: Adopt existing resources into Helm release |
| - AND no resources SHALL be deleted or recreated during migration | ||
| - AND the gateway pod SHALL NOT be restarted unless the Deployment spec actually changes | ||
|
|
||
| #### Scenario: Rollback capability |
There was a problem hiding this comment.
We are not handling gateway upgrades at this time. Section not necessary
| - THEN the `Atomic` flag SHALL cause automatic rollback to the previous release revision | ||
| - AND the reconciler SHALL log the failure and retry on the next reconciliation cycle | ||
|
|
||
| #### Scenario: Mixed-state during rolling upgrade |
There was a problem hiding this comment.
This is fine as long as we do not need 2 code paths... i.e. I want to only have the 'helm deploy method' in the latest code and not keep the old "SSA-based deployment" code hanging around.
|
|
||
| | # | Resource | Kind | Why the Control Plane Handles It | | ||
| |---|---|---|---| | ||
| | 1 | `openshell-sandbox-privileged-scc` | RoleBinding | OpenShift SCC binding granting the `privileged` SCC to the sandbox ServiceAccount. The chart handles `podSecurityContext` values but has no concept of OpenShift SCC grants. | |
There was a problem hiding this comment.
You can add a note about how the creation of this rolebinding outside of the chart is documented here: https://github.com/NVIDIA/OpenShell/blob/main/deploy/helm/openshell/README.md#install-on-openshift
| | # | Resource | Kind | Why the Control Plane Handles It | | ||
| |---|---|---|---| | ||
| | 1 | `openshell-sandbox-privileged-scc` | RoleBinding | OpenShift SCC binding granting the `privileged` SCC to the sandbox ServiceAccount. The chart handles `podSecurityContext` values but has no concept of OpenShift SCC grants. | | ||
| | 2 | `gateway-trusted-ca` | ConfigMap | CA bundle for private-CA environments (e.g. Keycloak behind OpenShift ingress). Copied from the CP namespace and mounted into the gateway Deployment via a post-Helm SSA patch. | |
There was a problem hiding this comment.
Apparently, the upstream chart can handle this:
● The chart already handles this. Setting server.oidc.caConfigMapName to the name of a ConfigMap makes the chart automatically:
1. Add the SSL_CERT_FILE env var to the gateway container
2. Mount the ConfigMap as a volume
3. Wire it all up in the Deployment
So we don't need a post-Helm SSA patch for the trusted CA. The control plane just needs to:
1. Copy the gateway-trusted-ca ConfigMap into the tenant namespace (as it does today)
2. Pass server.oidc.caConfigMapName: "gateway-trusted-ca" in the Helm values
|
|
||
| --- | ||
|
|
||
| ## Trusted CA Injection Strategy |
There was a problem hiding this comment.
See above comment. We'll change the way this works, and this whole section can go away.
|
|
||
| --- | ||
|
|
||
| ## NetworkPolicy Decision: Do Not Install |
There was a problem hiding this comment.
Add a note that we will revisit this if/when using a restrictive network policy posture becomes a platform requirement.
| 2. CNPG Database + credentials Secret (must exist before Helm install) | ||
| 3. Trusted CA ConfigMap copy (must exist before Helm install if present) | ||
| 4. Helm install/upgrade (creates core workload + chart-managed resources) | ||
| 5. OpenShift SCC binding (can run after Helm, before pod scheduling) |
There was a problem hiding this comment.
Install the SCC binding before the helm install
| 3. Trusted CA ConfigMap copy (must exist before Helm install if present) | ||
| 4. Helm install/upgrade (creates core workload + chart-managed resources) | ||
| 5. OpenShift SCC binding (can run after Helm, before pod scheduling) | ||
| 6. Trusted CA Deployment overlay (must run after Helm, patches the Deployment) |
Amber Review — Spec Quality AssessmentOverall: Spec is comprehensive and addresses all user feedback. The TLS architecture explanation for Route passthrough mode is accurate and well-documented. Minor issues below. ✅ Strengths
🔧 Issues1. Minor Inconsistency — Gap Table (line 291)Issue: Line 291 states Fix: | Trusted CA volume/mount/env | Chart (`_gateway-workload.tpl`) | `_gateway-workload.tpl` | `server.oidc.caConfigMapName` |Severity: Minor 2. Missing Error Handling Guidance (Helm Operations)Issue: Spec doesn't specify how Helm install/upgrade failures should be handled per HyperShell conventions:
Recommendation: Add to "Code Changes" section: ### Error Handling (HyperShell Convention)
- Helm install/upgrade/uninstall errors SHALL be wrapped with context: `fmt.Errorf("helm install gateway %s: %w", gateway.Name, err)`
- Gateway status SHALL be updated on Helm failure with `State: "Failed"` and error message
- Reconciler SHALL return explicit errors — NEVER `panic()` on Helm failuresSeverity: Major (impacts production error handling) 3. Incomplete Retry Guidance (lines 102-107)Issue: "Retry after failed install" scenario mentions
Recommendation: Expand scenario: #### Scenario: Retry after failed install
- GIVEN a previous Helm install failed (e.g. certgen job timed out)
- WHEN the GatewayReconciler processes the next reconciliation event
- THEN it SHALL query Helm release status via `action.Get`
- AND if release status is `failed` or `pending-install`, it SHALL run `helm upgrade --reuse-values` to retry
- AND it SHALL implement exponential backoff (1m, 2m, 4m, 8m, max 15m between retries)
- AND it SHALL mark the Gateway status as `State: "Failed"` after 5 consecutive failures
- AND this is the only scenario where `helm upgrade` is invokedSeverity: Major (impacts retry behavior) 4. Missing Configuration ValidationIssue: No requirement for validating env vars at startup. Control plane should fail-fast if Recommendation: Add to "Configuration" section: ### Validation at Startup
- Control plane SHALL validate `EXTERNAL_CA_ISSUER_NAME` is set when any managed cluster lacks Gateway API support
- Control plane SHALL fail fast with clear error message if validation fails
- Chart version compatibility SHALL be verified against `HELM_CHART_VERSION` (if pulling from OCI registry)Severity: Major (prevents runtime failures) 5. Chart Version Pinning — Implementation Detail MissingIssue: Line 129 says "chart version SHALL be pinned in the Dockerfile build script" but doesn't specify HOW (version file, make variable, ARG, etc.). Recommendation: Add implementation detail: - AND the chart version SHALL be declared as a `VERSION` file in `components/control-plane/charts/VERSION`
- AND the Dockerfile SHALL read this file: `ARG CHART_VERSION=$(cat charts/VERSION)`
- AND the build SHALL run: `helm pull oci://ghcr.io/nvidia/openshell/helm-chart --version ${CHART_VERSION} --destination /charts/`Severity: Minor (implementation clarity) 📊 Summary
Recommendation: Address major issues (error handling, retry guidance, config validation) before implementation. Minor issues can be fixed during PR review of the actual code. Confidence Level: High (90%) — Spec is solid, issues are additive improvements rather than fundamental flaws. You are Amber. Be the colleague everyone wishes they had. |
Amber Follow-Up Review — Post TLS Architecture AdditionStatus: Latest commit (0d8b697) successfully addresses bsquizz's feedback on TLS architecture for Route passthrough mode. ✅ User Feedback Addressedbsquizz's request: "Yeah, we shouldn't be using a self-signed internal cert. I think you should add an explanation of all this functionality to the spec." Resolution: Lines 186-236 now include:
Verdict: ✅ Fully addressed. The explanation is accurate and comprehensive. 🔧 Remaining Issues from Previous ReviewMy previous review identified 5 issues. Checking current status: Issue #1: Gap Table Inconsistency (Line 296) — ❌ Still PresentLine 296 still states: But line 306 correctly states: "no post-Helm SSA patch is needed." Required Fix: -| Trusted CA volume/mount/env | Go (post-Helm SSA patch) | `_gateway-workload.tpl` | `server.oidc.caConfigMapName` |
+| Trusted CA volume/mount/env | Chart (`_gateway-workload.tpl`) | `_gateway-workload.tpl` | `server.oidc.caConfigMapName` |Severity: Minor Issue #2: Missing Error Handling Guidance — ❌ Still MissingNo HyperShell-convention error handling documented for Helm operations. Recommendation: Add to "Code Changes" section: ### Error Handling (HyperShell Convention)
- Helm install/upgrade/uninstall errors SHALL be wrapped with context: `fmt.Errorf("helm install gateway %s: %w", gateway.Name, err)`
- Gateway status SHALL be updated on Helm failure with `State: "Failed"` and error message
- Reconciler SHALL return explicit errors — NEVER `panic()` on Helm failuresSeverity: Major Issue #3: Incomplete Retry Guidance (lines 102-107) — ❌ Still IncompleteRetry scenario lacks:
Severity: Major Issue #4: Missing Configuration Validation — ❌ Still MissingNo requirement for validating Severity: Major Issue #5: Chart Version Pinning Detail — ❌ Still VagueLine 129 says "pinned in the Dockerfile build script" but doesn't specify how. Severity: Minor 📊 Updated Summary
Overall Assessment: The TLS architecture addition is excellent and fully addresses bsquizz's feedback. The 5 issues from my initial review remain unaddressed but are additive improvements — they don't block spec approval, but should be addressed before implementation begins. Recommendation:
Confidence: High (95%) Amber review complete. The spec correctly documents the TLS architecture difference that bsquizz asked for. |
Amber Final Review — Excellent Work! ✅Status: Commit ✅ Issues ResolvedIssue #2: Error Handling Guidance — ✅ RESOLVEDAdded: Lines 353-361 now document HyperShell error handling conventions:
Verdict: Excellent. Fully compliant with HyperShell conventions from CLAUDE.md. Issue #3: Retry Guidance — ✅ RESOLVEDAdded: Lines 106-110 now specify:
Verdict: Complete and production-ready. The exponential backoff prevents reconciler storms. Issue #4: Configuration Validation — ✅ RESOLVEDAdded: Lines 409-416 specify startup validation:
Verdict: Exactly what was needed. Prevents runtime failures with clear startup feedback. Issue #5: Chart Version Pinning — ✅ RESOLVEDAdded: Lines 132-135 specify implementation:
Verdict: Clear, actionable implementation guidance. Easy to automate in CI. 🔧 Remaining IssueIssue #1: Gap Table Inconsistency (Line 296) —
|
| Metric | Status |
|---|---|
| User Feedback (TLS Architecture) | ✅ Fully Addressed |
| Amber Major Issues | ✅ 3/3 Resolved |
| Amber Minor Issues | |
| Blockers | 0 |
| Implementation Readiness | ✅ Ready |
Overall Verdict: This spec is implementation-ready. The one remaining minor inconsistency is cosmetic and doesn't impact the implementation phase.
Recommendation:
- ✅ Approve the spec — all substantive issues resolved
⚠️ Fix the line 296 inconsistency if convenient (optional, 1-line change)- 🚀 Proceed to implementation with confidence
Confidence: High (98%)
🎯 Why This Spec Is Excellent
- Comprehensive TLS Architecture: Lines 186-236 clearly explain the security model difference between GRPCRoute (re-encrypt) and Route (passthrough)
- Production-Grade Error Handling: HyperShell conventions documented, no
panic(), proper context wrapping - Robust Retry Logic: Exponential backoff prevents reconciler storms, max retries prevent infinite loops
- Fail-Fast Validation: Startup checks catch configuration errors before runtime
- Clear Implementation Path: Chart version pinning, Helm SDK usage, ordering constraints all documented
- Empirically Validated Decisions: NetworkPolicy analysis backed by live cluster testing
bsquizz, this is solid engineering documentation. The implementation team has everything they need.
Amber review complete. Changing label to amber/approved.
Add specification for shifting the control plane from static YAML manifests to the upstream OpenShell Helm chart via the Helm Go SDK. Covers: SDK integration, values mapping, migration path, gap analysis, and the decision to not install NetworkPolicies. HYPERSHELL-146 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cision - Chart sourcing: embedded .tgz in container image (default), OCI registry override for dev only - Gap analysis narrowed to 2 items: SCC binding and trusted CA ConfigMap - NetworkPolicy decision: do not install, verified empirically on OVN-Kubernetes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Key changes from review: - No upgrades: install on create, uninstall+delete-namespace on delete, helm upgrade only for retry after failed install - Keep sandbox preflight enabled (valid prerequisite check) - Simple migration: new gateways use Helm, existing ones not migrated - Single code path: remove old SSA code entirely, no dual-mode - Load chart once at startup, reuse for all installs - Trusted CA via chart values (server.oidc.caConfigMapName), remove post-Helm SSA overlay section - SCC binding runs before Helm install - OpenShift Route via chart (openshiftRoute.enabled) - Add upstream docs reference for SCC binding - NetworkPolicy: add note to revisit if restrictive posture required - Remove rollback, mixed-state, upgrade, and implementation phases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…A config Explain how TLS differs between GRPCRoute (internal CA sufficient) and Route passthrough (needs externally trusted CA via certManager.serverIssuerRef). Add EXTERNAL_CA_ISSUER_NAME and EXTERNAL_CA_ISSUER_KIND env vars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jsell-rh
left a comment
There was a problem hiding this comment.
Verdict
REQUEST_CHANGES (posted as a COMMENT review). This is a well-structured, high-value refactor that replaces ~1,600 lines of hand-maintained SSA manifest code with a Helm-chart-driven deployment path, but it is not mergeable to main as-is: the control-plane image is built by cloning a personal GitHub fork's mutable feature branch and pins a personal quay image, a database-config resolution error is now silently swallowed, and the spec (Helm Go SDK) diverges from the implementation (shelling out to the helm CLI). Address the Blocker/Major items below before merge.
Hi @bsquizz — Amber here. The direction (delegate gateway rendering to the upstream chart, delete the drift-prone embedded manifests) is the right long-term call, and the internal/helm package is clean and readable. My concerns are about supply-chain reproducibility, a swallowed error, and reconcile semantics — details below.
Blocker
1. Production image build depends on a personal fork's mutable branch + personal quay image — Security / Supply chain / Image consistency
charts/CHART_REPO=https://github.com/bsquizz/OpenShell.git,charts/CHART_REF=feat/backend-tls-and-rbac-toggle(a branch, not a tag/SHA).components/control-plane/Dockerfile:29runsgit clone --depth 1 --branch "${CHART_REF}"at image-build time. A branch is mutable and can be force-pushed or deleted, so the control-plane image is not reproducible and CI can break at any time with no code change here.components/control-plane/internal/gateway/config.go:40pinsdefaultGatewayImage = quay.io/bsquizza/openshell-gateway:16112bc(personal registry), while:41keeps the supervisor onghcr.io/nvidia/openshell/supervisor:16112bc. CLAUDE.md requires "Image references must match across the stack."- This appears to be a deliberate temporary state pending upstream
NVIDIA/OpenShellPR #2728, but as written it cannot merge tomain. Please either (a) pointCHART_REPO/CHART_REFat the canonical upstream repo pinned to an immutable tag/SHA and use canonical images, or (b) explicitly gate this PR behind that upstream merge and note it in the description. This is the primary maintainer decision this PR needs.
Major
2. Database-config resolution error is now silently swallowed — Error handling / "never silently swallow partial failures"
components/control-plane/internal/reconciler/reconciler.go:1414:resolveDatabaseConfigfailure changed fromreturn reconcileErr(which marked the gatewayFailed) to alog.Printf("WARN ... skipping database reconciliation")and continues into the Helm install. The gateway then deploys referencingopenshell-gateway-db-credentialswhich may not exist, and its phase never reflects the failure. This violates the CLAUDE.md rule that "every error path must propagate or be collected." Restore the fail-closed behavior (markFailed), or document why proceeding is safe.
3. Spec says "Helm Go SDK"; implementation shells out to the helm CLI — Spec consistency
specs/platform/openshell-gateway-helm-adoption.spec.mdstates the reconciler "SHALL use the Helm Go SDK" and documentshelm.sh/helm/v3/pkg/action. The implementation (internal/helm/shell_client.go) insteadexecs thehelmbinary (with a comment explaining the pivot to avoid dependency conflicts). The pivot is reasonable, but the spec must be updated to match — otherwise the desired-state doc contradicts the code, and the runtime now has a new hard dependency on ahelmbinary in the image +HELM_BINARY/PATH resolution that the spec never describes.
4. Already-deployed releases are never upgraded (create-or-skip) — Reconciliation convention
internal/gateway/helm_deploy.go:74skips whenstatus == "deployed", so image/OIDC/route/config changes to a running gateway never converge. The spec acknowledges "Gateway upgrades are not handled at this time," but this is a functional regression from the previous SSA path, which did re-apply on spec changes, and it violates "reconcile, don't create-or-skip." Relatedly,internal/helm/shell_client.go:152uses--reuse-valuestogether with--valueson the retry path, so keys removed from the desired set persist across retries. Please call this limitation out explicitly in the PR body and file a follow-up, and coordinate with PR #151 (see Cross-PR section).
5. Out-of-band route teardown will drift Helm state — Architecture
internal/reconciler/health.go:439self-documents thatDeleteGatewayAPIResourcesdeletes chart-managed routing resources outside Helm, "may cause Helm state drift." With routing now owned by the chart, the health loop and Helm will fight over these resources (Helm still believes them present; a later reconcile may recreate). This needs a real plan (e.g.,helm upgradewith routing disabled) rather than a TODO before it lands.
Minor
6. splitImageRef mishandles digest references — internal/helm/values.go:247 splits on the last :; an image like repo@sha256:abcd yields repo=...@sha256, tag=abcd. Not hit by the current tag-based defaults, but will silently corrupt digest-pinned images. Guard for @.
7. Pointless indirection — internal/gateway/helm_deploy.go:111 getEnv → getEnvHelper → os.Getenv. Collapse to a single helper (or reuse an existing env helper) to reduce noise.
8. Lost documentation — internal/config/config.go dropped the detailed DatabaseProvider comment explaining the CNPG-vs-deployment default and no-silent-fallback behavior with no functional change. Keep the rationale.
9. Delegated pod SecurityContext is now unverifiable here — pod security context comes entirely from the upstream chart, and buildOpenShiftValues nils podSecurityContext.fsGroup/securityContext.runAsUser to defer to SCC. Please confirm the chart still yields runAsNonRoot: true, allowPrivilegeEscalation: false, and drop: ["ALL"] per security.spec.md, since we no longer set these in-tree.
Cross-PR coordination
I compared #194 against the other open PRs in openshift-online/hypershell. Open PRs at review time: #216, #214, #212, #211, #210, #209, #208, #207, #206, #201, #200, #194, #189, #188, #185, #182, #179, #151, #150, #148, #135, #109, #75, #73. The following have material (design/plan) conflicts, not mere text overlap:
-
#201 — "[HYPERSHELL-45] Update gateway and supervisor openshell images to Red Hat ones": Direct, competing design conflict over the canonical gateway/supervisor image source. #201 removes the in-code image defaults entirely ("now they are required to be set as env variables or inside the gatewayconfig resource") and switches to Red Hat images; #194 does the opposite — it keeps and relies more heavily on in-code defaults (
reconciler.go:1450now backfillsimages.DefaultGatewayImage()) and points them at a personal fork (config.go:40). Both editinternal/gateway/config.goandscripts/kind/lib.sh, and #194 deletesinternal/gateway/manifests.gowhich #201 modifies. Maintainer decision needed: pick the canonical image source (Red Hat vs upstream vs fork) and whether in-code defaults survive; then sequence these two PRs deliberately. -
#151 — "gate gateway re-provisioning on desired-state convergence": Opposed goals on the same
Handlepath. #151 exists to make the reconciler re-apply on spec changes (image/route/oidc/db) so drift stops being masked; #194 introduces a deploy path that explicitly skips already-deployed releases (helm_deploy.go:74) and states upgrades are out of scope. Both editinternal/reconciler/reconciler.goandskills/RECONCILE.md. Coordination needed: #151's convergence gate must drive ahelm upgradeunder #194's model, or the two designs will cancel out. -
#216 — "fix(console): support OpenShift Route ingress": Conflicting ownership of ingress/Route creation and overlapping teardown. #194 removes control-plane gateway Route reconciliation and delegates GRPCRoute/Route to the chart, renames
openshell-backend-ca→openshell-gateway-backend-ca, and flagshealth.goroute teardown as Helm-drift-prone; #216 adds control-plane-managed console Routes and edits the samehealth.goteardown path,reconciler.go, andreconciler_test.go. Coordination needed: agree on who owns Route objects (chart vs control plane) and reconcile thehealth.goteardown semantics. -
#179 — "reconcile existing Keycloak clients on gated gateways" (lower priority): Overlapping restructure of
internal/reconciler/reconciler.goandhealth.goaround the phase gate / Keycloak reconcile ordering that #194 also rewrites. This is primarily a merge-ordering concern rather than a design clash, but whichever lands first will force a non-trivial rebase of the other.
No material conflict found with the remaining open PRs (#211/#150/#148 touch kind/image-build tooling and specs that overlap only textually with #194; #200/#185 are reconciliation-contract specs that #194 does not contradict; the rest are UI/console/deps unrelated to this change).
Findings Summary (ordered by severity, highest first):
- [Blocker] Image build clones a personal fork's mutable branch and pins a personal quay image; non-reproducible + inconsistent references - Security / Supply chain (Dockerfile L29, CHART_REPO/CHART_REF, config.go L40-41)
- [Major] Database-config resolution error swallowed; gateway deploys anyway and phase never reflects failure - Error handling (reconciler.go L1414)
- [Major] Spec mandates Helm Go SDK; code shells out to
helmCLI - Spec consistency (helm-adoption spec, shell_client.go) - [Major] Deployed releases never upgraded (create-or-skip) +
--reuse-valueson retry - Reconciliation (helm_deploy.go L74, shell_client.go L152) - [Major] Out-of-band route teardown drifts Helm state (self-admitted TODO) - Architecture (health.go L439)
- [Minor]
splitImageRefcorrupts digest references - Correctness (values.go L247) - [Minor] Pointless
getEnv/getEnvHelperindirection - Style (helm_deploy.go L111) - [Minor] Dropped
DatabaseProviderrationale comment - Docs (config.go) - [Minor] Pod SecurityContext fully delegated to external chart; verify it still meets security.spec.md - Security (values.go buildOpenShiftValues)
Convention Checklist:
| Convention | Result |
|---|---|
No panic() in production code |
Pass |
Errors wrapped with fmt.Errorf context |
Pass |
| Never silently swallow partial failures | Fail (reconciler.go L1414) |
| Reconcile, don't create-or-skip | Fail (helm_deploy.go L74) |
| Image references consistent across the stack | Fail (config.go L40-41, Dockerfile, kind/lib.sh) |
| Reproducible builds / pinned dependencies | Fail (Dockerfile L29 mutable branch clone) |
| Spec matches implementation | Fail (Helm SDK vs CLI) |
| SecurityContext on pod specs | Deferred to upstream chart (verify) |
| Status updated on error paths | Fail (db-config path, reconciler.go L1414) |
| Conventional commit message | Pass |
Rollback: this is a self-contained control-plane change; reverting the merge commit restores the SSA manifest path. Happy to pair on the upstream-chart pinning once #2728 lands. Does this framing match your intent for the temporary fork references?
| COPY charts/CHART_REPO charts/CHART_REF /tmp/chart-source/ | ||
| RUN CHART_REPO=$(cat /tmp/chart-source/CHART_REPO) && \ | ||
| CHART_REF=$(cat /tmp/chart-source/CHART_REF) && \ | ||
| git clone --depth 1 --branch "${CHART_REF}" "${CHART_REPO}" /tmp/openshell && \ |
There was a problem hiding this comment.
[Blocker] Non-reproducible build from a mutable personal fork branch. git clone --depth 1 --branch "${CHART_REF}" where CHART_REF=feat/backend-tls-and-rbac-toggle (a branch, not an immutable tag/SHA) on https://github.com/bsquizz/OpenShell.git means the control-plane image content can change or vanish with no code change here, and CI can break at any time. Pin to the canonical upstream repo at an immutable tag/SHA (or explicitly gate this PR behind upstream NVIDIA/OpenShell #2728) before merging to main.
There was a problem hiding this comment.
Acknowledged — this is intentionally temporary. We need these custom images to prove e2e tests pass with the BackendTLSPolicy and namespace-scoped RBAC changes. We will switch to the canonical upstream chart source and images once NVIDIA/OpenShell#2728 and NVIDIA/OpenShell#2939 are merged. Noting this in the PR description.
| @@ -0,0 +1 @@ | |||
| https://github.com/bsquizz/OpenShell.git | |||
There was a problem hiding this comment.
[Blocker] Points the chart source at a personal fork (github.com/bsquizz/OpenShell). Combined with the mutable branch in CHART_REF, this makes production builds depend on personal, mutable infrastructure. Needs a maintainer decision on the canonical chart source + immutable pinning.
There was a problem hiding this comment.
Same as above — temporary state to enable e2e testing. Will point to canonical upstream once NVIDIA/OpenShell#2728 and #2939 land.
|
|
||
| const defaultGatewayImage = "ghcr.io/nvidia/openshell/gateway:0.0.109" | ||
| const defaultSupervisorImage = "ghcr.io/nvidia/openshell/supervisor:0.0.109" | ||
| const defaultGatewayImage = "quay.io/bsquizza/openshell-gateway:16112bc" |
There was a problem hiding this comment.
[Blocker/Major] Personal registry + mixed provenance. defaultGatewayImage now points at quay.io/bsquizza/openshell-gateway:16112bc (personal quay) while defaultSupervisorImage (L41) stays on ghcr.io/nvidia/openshell/supervisor:16112bc. CLAUDE.md requires image references to be consistent across the stack. Also note PR #201 proposes removing these in-code defaults entirely in favor of Red Hat images — coordinate on the canonical source before merge.
There was a problem hiding this comment.
Same — the personal quay image is required because the upstream gateway image doesn't include the BackendTLSPolicy changes from NVIDIA/OpenShell#2728 yet. The supervisor image is already available on ghcr.io from upstream at that commit. Once #2728 merges, both will point to the canonical upstream sources with consistent provenance.
| if resolveErr != nil { | ||
| reconcileErr = fmt.Errorf("resolve database config for gateway %s: %w", gw.Name, resolveErr) | ||
| return reconcileErr | ||
| log.Printf("WARN gateway %s: skipping database reconciliation: %v", event.ResourceID, resolveErr) |
There was a problem hiding this comment.
[Major] Swallowed partial failure. This changed from return reconcileErr (which marked the gateway Failed) to a warning-and-continue. The reconcile now proceeds to the Helm install referencing openshell-gateway-db-credentials, which may not exist, and the gateway phase never reflects the failure. This violates the "never silently swallow partial failures" rule and "status updated on error paths." Restore fail-closed behavior or document why proceeding is safe.
There was a problem hiding this comment.
Fixed — restored fail-closed behavior. The reconciler now marks the gateway Failed and returns the error when resolveDatabaseConfig fails, instead of logging a warning and continuing.
| if err := helmClient.Upgrade(ctx, nsConfig.Name, values); err != nil { | ||
| return fmt.Errorf("helm upgrade: %w", err) | ||
| } | ||
| } else if status.Status == "deployed" { |
There was a problem hiding this comment.
[Major] Create-or-skip anti-pattern. When the release is already deployed, this logs and skips, so image/OIDC/route/config changes to a running gateway never converge. This is a regression from the SSA path (which re-applied) and conflicts with the "reconcile, don't create-or-skip" convention. It also directly overlaps PR #151's goal of converging on desired-state changes. If out of scope for now, call it out explicitly in the PR body and file a follow-up.
There was a problem hiding this comment.
This is intentional for the current scope — the spec explicitly states "Gateway upgrades (image changes, config changes) are not handled at this time." The deployed skip is the correct behavior: install on create, uninstall on delete, retry on failure. Adding helm upgrade for running gateways is a follow-up that needs coordination with PR #151 (desired-state convergence gate). Noted this limitation in the PR description.
| args := []string{ | ||
| "upgrade", ReleaseName, c.ChartPath, | ||
| "--namespace", namespace, | ||
| "--reuse-values", |
There was a problem hiding this comment.
[Major] --reuse-values on the retry path. Upgrade passes both --reuse-values and --values <file>; keys removed from the desired set persist across retries, and on a retried failed install the prior (bad) values are merged back in. Consider --reset-values + full desired values so the release always reflects the computed values map.
There was a problem hiding this comment.
Fixed — changed --reuse-values to --reset-values in the Upgrade method. Combined with the full desired values file passed via --values, this ensures the release always reflects the computed values map and removed keys don't persist.
| } | ||
| } | ||
| // NOTE: With Helm adoption, routing resources are managed by the chart. | ||
| // This manual cleanup is retained for partial route teardown but may cause |
There was a problem hiding this comment.
[Major] Self-admitted Helm state drift. Deleting chart-managed routing resources out-of-band leaves Helm believing they still exist; a later reconcile can recreate them. With routing now owned by the chart this needs a concrete plan (e.g. helm upgrade with routing disabled) rather than a TODO. Also interacts with PR #216, which adds control-plane-managed console Routes on this same teardown path.
There was a problem hiding this comment.
Acknowledged — this is a known limitation documented in the TODO. The current teardown path deletes chart-managed routing resources outside of Helm when a gateway is no longer routed. The correct long-term fix is helm upgrade with routing values disabled, which requires implementing the upgrade path (out of scope for this PR, same follow-up as the create-or-skip issue above). For now, the teardown is safe because: (1) the next reconcile will skip the deployed release, not recreate resources; (2) namespace deletion on gateway delete cleans everything. The TODO is retained as a marker for the upgrade follow-up.
| func splitImageRef(image string) (repo, tag string) { | ||
| lastSlash := strings.LastIndex(image, "/") | ||
| lastColon := strings.LastIndex(image, ":") | ||
| if lastColon <= lastSlash { |
There was a problem hiding this comment.
[Minor] Digest refs mis-split. image@sha256:... yields repo=...@sha256, tag=<hash> because the split is on the last :. Guard for @ (digest) before splitting on :. Not hit by current tag defaults, but will silently corrupt digest-pinned images.
There was a problem hiding this comment.
Fixed — splitImageRef now checks for @ (digest separator) before splitting on :. Digest refs like image@sha256:abc123 are correctly split into repo=image, tag=sha256:abc123.
| } | ||
|
|
||
| // getEnv retrieves an environment variable with a fallback default. | ||
| func getEnv(key, fallback string) string { |
There was a problem hiding this comment.
[Minor] Pointless indirection. getEnv -> getEnvHelper -> os.Getenv. Collapse into one helper (or reuse an existing env helper in the package).
There was a problem hiding this comment.
Fixed — collapsed getEnv/getEnvHelper into a single getEnv function that calls os.Getenv directly.
|
|
||
| The control plane SHALL shift from applying static YAML manifests (generated once via `helm template` and maintained as embedded files) to installing OpenShell gateways using the upstream Helm chart at runtime via the Helm Go SDK. This eliminates drift between HyperShell and upstream, reduces maintenance burden, and gives automatic access to new chart features. | ||
|
|
||
| ### Current State |
There was a problem hiding this comment.
I wonder if this should be in a spec file.
Whe approved, the "current state" is forgotten and IMO becomes noise for later iterations on the specs.
There was a problem hiding this comment.
I guess we could call this ... "Old way of operating" and "Desired new way of operating" ?
|
|
||
| - GIVEN the control plane starts up | ||
| - WHEN the GatewayReconciler initializes | ||
| - THEN it SHALL create a Helm action configuration targeting each managed cluster's kubeconfig |
There was a problem hiding this comment.
The current implementation does not create one config per managed cluster... should it? or is this intended for a later iteration?
currently I guess we are working with a single managedCluster, so a single one is enough
And Mark shared the idea that controllers may run in the managedClusters, pulling info from the API, so in that operation mode a single config would be also enough
There was a problem hiding this comment.
Yes I am reading this to mean that there should be one helm SDK client per managed cluster... if the implementation is not currently doing that, I'll fix it.
| - THEN it SHALL call `action.Install` with the computed values | ||
| - AND the release name SHALL be `openshell-gateway` | ||
| - AND the release namespace SHALL be the gateway's API-assigned namespace | ||
| - AND `Install.CreateNamespace` SHALL be `false` (the reconciler creates the namespace itself) |
There was a problem hiding this comment.
I wonder if the namespace could be also created by the chart
Maybe not, if we want to put more things there that are not managed by the chart
Also, when we create the namespace we add some labels
app.kubernetes.io/managed-by: hypershell-control-plane
hypershell.redhat.io/managed: "true"
I wonder if we should add those labels also to the chart created objects
There was a problem hiding this comment.
It is possible to invoke 'helm install' with '--create-namespace' -- let me see if there's a way to call that similar code path using the SDK
Re the labels, right now all the resources installed by the chart appear to get these labels tied to them:
https://github.com/NVIDIA/OpenShell/blob/main/deploy/helm/openshell/templates/_helpers.tpl#L35
Is that good enough for now?
The upstream chart currently does not have the option to extend that list of labels with custom values
There was a problem hiding this comment.
Then, I think we should keep the namespace creation in our hands, so we can attach labels to it as we are doing today
There was a problem hiding this comment.
Agreed — the spec and implementation both keep namespace creation in the reconciler's hands (--create-namespace=false). Updated the spec to explicitly call out why: the reconciler applies custom labels (app.kubernetes.io/managed-by: hypershell-control-plane, hypershell.redhat.io/managed: "true") that the upstream chart does not support.
| - WHEN the GatewayReconciler processes the event | ||
| - THEN it SHALL clean up non-chart resources (database, Keycloak clients) | ||
| - AND if a Helm release exists in the gateway namespace, it SHALL call `action.Uninstall` | ||
| - AND it SHALL delete the gateway namespace |
There was a problem hiding this comment.
I wonder what happens in case of an error on Helm uninstall
- Does it retry the uninstall?
- Does it go directly to deleting the namespace?
There was a problem hiding this comment.
It goes straight to deleting the namespace
|
|
||
| - GIVEN the chart archive is vendored into the control plane container image at `/charts/openshell.tgz` | ||
| - WHEN the reconciler starts up | ||
| - THEN it SHALL use `loader.LoadArchive()` to load the chart once |
There was a problem hiding this comment.
I found no LoadArchive in the code, this seems like a too low level implementation detail for a spec?
There was a problem hiding this comment.
Ah, maybe is because we are using the helm binary, so our code doesn't really load the chart?
There was a problem hiding this comment.
Good question, not seeing 'LoadArchive' either. I can remove it from the spec
| - WHEN the reconciler starts up | ||
| - THEN it SHALL use `loader.LoadArchive()` to load the chart once | ||
| - AND the loaded chart SHALL be reused for all gateway installs without reloading | ||
| - AND the chart version SHALL be declared as a `VERSION` file in `components/control-plane/charts/VERSION` |
There was a problem hiding this comment.
Should this file exist in this PR?
There was a problem hiding this comment.
Good question, at some point it seems like this file got removed. I see CHART_REF and CHART_REPO ... I need to see if 'VERSION' is still relevant
| | DB credentials Secret name | `server.externalDbSecret` | `openshell-gateway-db-credentials` | | ||
| | Trusted CA ConfigMap name | `server.oidc.caConfigMapName` | `gateway-trusted-ca` (when present) | | ||
|
|
||
| #### OIDC Values (conditional) |
There was a problem hiding this comment.
What does it mean "(conditional)" ?
There was a problem hiding this comment.
I'll add clarifications... same goes for the other mentions of 'conditional' below in this spec
| |---|---|---| | ||
| | Gateway has `route` config + Gateway API available | `grpcRoute.enabled=true` | Enable GRPCRoute creation | | ||
| | Route hostname | `grpcRoute.hostnames` | `[gw-<ns>.<base-domain>]` where `<base-domain>` is from `GATEWAY_API_BASE_DOMAIN` (set via `deriveGatewayHostname` → `Route.Host`) | | ||
| | Gateway API Gateway ref | `grpcRoute.gateway.name`, `grpcRoute.gateway.namespace` | Cross-namespace parentRef | |
There was a problem hiding this comment.
😵💫
Gateway API Gateway ref
I'm still on the opinion that we should use the more verbose but explicit "OpenShell Gateway" when referring to the... OpenShell gateway.... to avoid something as confusing as this
There was a problem hiding this comment.
An example in the notes column will help to disambiguate
There was a problem hiding this comment.
Yes I agree. The first line will make more sense like this:
| HyperShell Gateway configuration has `route` config + kubernetes Gateway API available | `grpcRoute.enabled=true` | Enable GRPCRoute creation |
We can change the references of 'Gateway API' to 'kubernetes Gateway API'
| ``` | ||
|
|
||
| The key constraints: | ||
| - The DB credentials Secret (`openshell-gateway-db-credentials`) must be created before the Helm install because the chart's Deployment references it via `server.externalDbSecret`. If the Secret does not exist at install time, the pod will fail to start with a missing Secret error. |
There was a problem hiding this comment.
k8s will retry the creation of the pod, so it will eventually succeed, but I guess helm install will fail, or we should add a longer timeout for things to resolve.
There was a problem hiding this comment.
I can reword to:
If the Secret does not exist at install time, the pod will fail to start with a missing Secret error until the secret is created.
There was a problem hiding this comment.
But I think here the constraint means that the controller waits for the secret to exist before attempting to execute the helm install. So, in this case there will be no error.
My comment was a nitpick, since k8s will eventually start the pod if the secret comes in time
There was a problem hiding this comment.
Right, the desired behavior is that control-plane ensures the secret is there first
There was a problem hiding this comment.
Good point — the reconciler ensures the Secret exists before invoking Helm (database reconciliation is step 2, Helm install is step 5). Updated the spec wording to clarify this: the ordering constraint prevents the missing-Secret scenario in the normal path, and if it somehow happens, Kubernetes will eventually start the pod once the Secret appears.
|
|
||
| --- | ||
|
|
||
| ## Code Changes |
There was a problem hiding this comment.
IMO individual code changes should not be part of the more general spec.
The information here is more like a plan for the implementation
If this is useful for the LLM, we may want to add this somehow... and maybe tied to a concrete commit/PR, so after that PR is merged is no longer relevant?
There was a problem hiding this comment.
This looks like a high-level overview of the planned changes... I can reword it so that the details are more "spec style" instead of getting into low level details.
|
|
||
| --- | ||
|
|
||
| ## Risks and Mitigations |
There was a problem hiding this comment.
If these are already addressed in the specs an the implementation... should they still be here?
I mean, they document the "normal behaviour", so what should we (or the LLM) do with these?
There was a problem hiding this comment.
I'll check and see if this section is still needed
| - GIVEN a Gateway that previously had `route` configuration and associated route resources | ||
| - WHEN the `route` field is removed or set to null | ||
| - THEN the GatewayReconciler SHALL delete all route-owned resources: GRPCRoute, BackendTLSPolicy, `openshell-backend-ca` ConfigMap, and `openshell-gateway-allow-router` NetworkPolicy | ||
| - THEN the GatewayReconciler SHALL delete all route-owned resources: GRPCRoute, BackendTLSPolicy, `openshell-gateway-backend-ca` ConfigMap, and `openshell-gateway-allow-router` NetworkPolicy |
There was a problem hiding this comment.
I find a bit ambiguous here who is responsible for deleting these objects.
Is the controller code itself, or is helm who is now managing these objects the one in charge of deleting them?
There was a problem hiding this comment.
GatewayReconciler calls the helm delete, which would in turn cause these resources to be deleted. We can clarify this.
|
Testing it locally and creating a gateway didn't fully resolved the route_address for the gateway and the web console was stuck at displaying the openshell connection instructions Claude offered this diagnostig |
|
Adding a test for the scenario you hit problems with |
- Restore fail-closed behavior for database config resolution errors (was silently swallowed as a warning, now marks gateway Failed) - Fix splitImageRef to handle digest refs (image@sha256:...) before splitting on colon, preventing corruption of digest-pinned images - Change helm upgrade from --reuse-values to --reset-values so removed keys don't persist across retries - Collapse getEnv/getEnvHelper into a single function - Publish route_address to the API after successful gateway provisioning (was only called with "" in the teardown path, leaving route_address empty for all running gateways) - Update specs to reflect Helm CLI implementation (was documenting Helm Go SDK), align CHART_REPO/CHART_REF sourcing, clarify conditional values, namespace creation, uninstall error handling, and route teardown ownership - Add e2e test assertion that route_address is populated after gateway reaches Running phase Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Incorporate main branch changes including Route ingress mode support, Fleet removal, CLI auth improvements, and doc updates while preserving the helm branch's Helm-based gateway deployment architecture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tibility The upstream chart template joins image.repository and image.tag with a colon (printf "%s:%s"), so digest refs like image:tag@sha256:hash produce an invalid image name (image:tag:sha256:hash). Strip the @digest suffix in splitImageRef and retain only the tag portion. Also update deploy/base/controller.yaml to use the helm branch's test images and fix gofmt formatting from the merge resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…i-lint Remove reconcileGatewayAPIResources, reconcileCertManagerResources, and waitForSecret — these were brought in from main during the merge but are unused on the helm branch where the chart manages those resources. Also remove the unused images variable and now-unneeded imports (exposure, fields, watch). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
/retest |
|
Ok @rh-amarin -- your feedback should be incorporated. Try testing it again too, hopefully the problem you encountered is fixed. |
Summary
helm template) to installing gateways at runtime using the upstream OpenShell Helm chart via the Helm Go SDKJIRA
HYPERSHELL-146
Test plan
values.yaml🤖 Generated with Claude Code