Skip to content

feat(control-plane): adopt upstream OpenShell Helm chart for gateway deployments - #194

Draft
bsquizz wants to merge 29 commits into
mainfrom
helm
Draft

feat(control-plane): adopt upstream OpenShell Helm chart for gateway deployments#194
bsquizz wants to merge 29 commits into
mainfrom
helm

Conversation

@bsquizz

@bsquizz bsquizz commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds spec for shifting the control plane from static YAML manifests (generated once via helm template) to installing gateways at runtime using the upstream OpenShell Helm chart via the Helm Go SDK
  • Defines values mapping from Gateway API resources to Helm chart values
  • Documents migration path for adopting existing SSA-managed resources into Helm releases without downtime
  • Decision: no NetworkPolicies — empirically verified on OVN-Kubernetes that they are unnecessary and create a self-defeating deny/re-allow cycle
  • Gap analysis narrowed to 2 items the control plane handles within the gateway namespace: OpenShift SCC binding and trusted CA ConfigMap

JIRA

HYPERSHELL-146

Test plan

  • Review spec for completeness and accuracy against current control-plane code
  • Validate Helm chart values mapping against upstream values.yaml
  • Confirm PR #2728 coverage of BackendTLSPolicy and backend CA ConfigMap

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Excluded labels (none allowed) (2)
  • do-not-merge/work-in-progress
  • do-not-merge/hold

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Team

Run ID: f2fd1ce5-e203-430e-90a5-6bde65c7c65d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

│ 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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In what cases would this not be covered by the chart?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The only time we might invoke 'helm upgrade' is in a "retry" scenario where initial install was not fully successful

Comment thread specs/platform/openshell-gateway-helm-adoption.spec.md

- 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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Can we ensure that the reconciler pulls the chart ONCE during startup, so it does not need to re-pull for every reconcile?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not necessary

- 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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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. |

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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. |

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

See above comment. We'll change the way this works, and this whole section can go away.


---

## NetworkPolicy Decision: Do Not Install

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This will go away

@bsquizz

bsquizz commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Amber Review — Spec Quality Assessment

Overall: 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

  1. TLS Architecture (lines 186-236): Excellent explanation of the fundamental difference between GRPCRoute+BackendTLSPolicy (re-encrypt) vs Route passthrough (end-to-end). Correctly identifies that Route mode requires externally-trusted CA.

  2. Gap Analysis (lines 293-304): Properly narrowed to 1 resource (SCC binding). Clear explanation of what the chart handles vs what the control plane handles.

  3. NetworkPolicy Decision (lines 307-322): Well-documented with empirical evidence. Rationale is sound.

  4. Chart Sourcing (lines 120-141): Solid strategy — embed .tgz by default, OCI override for dev only.

  5. Environment Variables (lines 384-392): Correctly adds EXTERNAL_CA_ISSUER_NAME and EXTERNAL_CA_ISSUER_KIND for Route mode.


🔧 Issues

1. Minor Inconsistency — Gap Table (line 291)

Issue: Line 291 states Go (post-Helm SSA patch) for trusted CA injection, but line 301 correctly says "no post-Helm SSA patch is needed."

Fix:

| Trusted CA volume/mount/env | Chart (`_gateway-workload.tpl`) | `_gateway-workload.tpl` | `server.oidc.caConfigMapName` |

Severity: Minor
Confidence: High (100%)


2. Missing Error Handling Guidance (Helm Operations)

Issue: Spec doesn't specify how Helm install/upgrade failures should be handled per HyperShell conventions:

  • Error wrapping with context (fmt.Errorf("installing gateway %s: %w", name, err))
  • Gateway status updates on error paths
  • No panic() in reconciler (return explicit errors)

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 failures

Severity: Major (impacts production error handling)
Confidence: High (95%)


3. Incomplete Retry Guidance (lines 102-107)

Issue: "Retry after failed install" scenario mentions helm upgrade but doesn't specify:

  • Max retry attempts before marking gateway as permanently failed
  • Backoff strategy (immediate retry vs exponential backoff)
  • How to detect a "failed release" (Helm release status query)
  • Whether to update Gateway status during retries

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 invoked

Severity: Major (impacts retry behavior)
Confidence: Medium (80% — exact retry strategy may vary)


4. Missing Configuration Validation

Issue: No requirement for validating env vars at startup. Control plane should fail-fast if EXTERNAL_CA_ISSUER_NAME is empty when managing clusters without Gateway API.

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)
Confidence: High (90%)


5. Chart Version Pinning — Implementation Detail Missing

Issue: 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)
Confidence: Medium (75% — multiple valid approaches)


📊 Summary

Category Count
Strengths 5
Minor Issues 2
Major Issues 3
Blockers 0

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.

@bsquizz bsquizz added amber/self-review This PR was reviewed by the Amber review agent by one of the contributors to the PR. amber/changes-requested Amber requested changes on this PR labels Aug 24, 2026
@bsquizz

bsquizz commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Amber Follow-Up Review — Post TLS Architecture Addition

Status: Latest commit (0d8b697) successfully addresses bsquizz's feedback on TLS architecture for Route passthrough mode.


User Feedback Addressed

bsquizz'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:

  • Clear TLS architecture diagrams for both modes
  • Explanation of why Route passthrough requires externally-trusted CA
  • certManager.serverIssuerRef mechanism documented
  • Chart validation rules listed
  • Cluster prerequisite (pre-provisioned ClusterIssuer) documented
  • Environment variables added (EXTERNAL_CA_ISSUER_NAME, EXTERNAL_CA_ISSUER_KIND)

Verdict: ✅ Fully addressed. The explanation is accurate and comprehensive.


🔧 Remaining Issues from Previous Review

My previous review identified 5 issues. Checking current status:

Issue #1: Gap Table Inconsistency (Line 296) — ❌ Still Present

Line 296 still states:

| Trusted CA volume/mount/env | Go (post-Helm SSA patch) | ... |

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
Confidence: High (100%)


Issue #2: Missing Error Handling Guidance — ❌ Still Missing

No 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 failures

Severity: Major
Confidence: High (95%)


Issue #3: Incomplete Retry Guidance (lines 102-107) — ❌ Still Incomplete

Retry scenario lacks:

  • Max retry attempts
  • Backoff strategy
  • Release status detection
  • Status update during retries

Severity: Major
Confidence: Medium (80%)


Issue #4: Missing Configuration Validation — ❌ Still Missing

No requirement for validating EXTERNAL_CA_ISSUER_NAME at startup.

Severity: Major
Confidence: High (90%)


Issue #5: Chart Version Pinning Detail — ❌ Still Vague

Line 129 says "pinned in the Dockerfile build script" but doesn't specify how.

Severity: Minor
Confidence: Medium (75%)


📊 Updated Summary

Status Count
User Feedback Addressed ✅ Complete
Minor Issues Remaining 2
Major Issues Remaining 3
Blockers 0

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:

  1. Approve the spec for the TLS architecture work (user's primary concern)
  2. Track the 5 issues as implementation requirements to be addressed in the code PR
  3. Fix the minor gap table inconsistency (1-line change) now if convenient

Confidence: High (95%)


Amber review complete. The spec correctly documents the TLS architecture difference that bsquizz asked for.

@bsquizz

bsquizz commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Amber Final Review — Excellent Work! ✅

Status: Commit afdf18e addresses 4 of 5 issues from my previous review. Outstanding work implementing the feedback.


Issues Resolved

Issue #2: Error Handling Guidance — ✅ RESOLVED

Added: Lines 353-361 now document HyperShell error handling conventions:

  • Context wrapping with fmt.Errorf
  • Gateway status updates on failures
  • No panic() in reconciler
  • Multi-error collection for partial failures

Verdict: Excellent. Fully compliant with HyperShell conventions from CLAUDE.md.


Issue #3: Retry Guidance — ✅ RESOLVED

Added: Lines 106-110 now specify:

  • Helm release status detection via action.Get
  • Exponential backoff strategy (1m, 2m, 4m, 8m, max 15m)
  • Max 5 consecutive failures before permanent failure
  • Status updates during retries

Verdict: Complete and production-ready. The exponential backoff prevents reconciler storms.


Issue #4: Configuration Validation — ✅ RESOLVED

Added: Lines 409-416 specify startup validation:

  • Validate EXTERNAL_CA_ISSUER_NAME when Route passthrough mode will be used
  • Fail-fast with clear error messages
  • Validate HELM_CHART_VERSION when OCI registry is configured
  • Chart version compatibility warnings

Verdict: Exactly what was needed. Prevents runtime failures with clear startup feedback.


Issue #5: Chart Version Pinning — ✅ RESOLVED

Added: Lines 132-135 specify implementation:

  • Chart version in components/control-plane/charts/VERSION file
  • Dockerfile reads version via ARG CHART_VERSION=$(cat charts/VERSION)
  • Build command with helm pull oci://...--version ${CHART_VERSION}
  • Upgrade process documented

Verdict: Clear, actionable implementation guidance. Easy to automate in CI.


🔧 Remaining Issue

Issue #1: Gap Table Inconsistency (Line 296)⚠️ Still Present

Current state:

| Trusted CA volume/mount/env | Go (post-Helm SSA patch) | `_gateway-workload.tpl` | `server.oidc.caConfigMapName` |

Contradiction: Line 306 states: "no post-Helm SSA patch is needed."

One-line 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` |

Impact: Minor documentation inconsistency. Doesn't block implementation — implementers will read line 306 and do the right thing.

Severity: Minor
Confidence: High (100%)


📊 Final Summary

Metric Status
User Feedback (TLS Architecture) ✅ Fully Addressed
Amber Major Issues ✅ 3/3 Resolved
Amber Minor Issues ⚠️ 1/2 Resolved
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

  1. Comprehensive TLS Architecture: Lines 186-236 clearly explain the security model difference between GRPCRoute (re-encrypt) and Route (passthrough)
  2. Production-Grade Error Handling: HyperShell conventions documented, no panic(), proper context wrapping
  3. Robust Retry Logic: Exponential backoff prevents reconciler storms, max retries prevent infinite loops
  4. Fail-Fast Validation: Startup checks catch configuration errors before runtime
  5. Clear Implementation Path: Chart version pinning, Helm SDK usage, ordering constraints all documented
  6. 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.

@bsquizz bsquizz added amber/approved The Amber review agent has approved this PR. and removed amber/changes-requested Amber requested changes on this PR labels Aug 25, 2026
bsquizz and others added 4 commits August 26, 2026 12:37
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 jsell-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 imageSecurity / 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:29 runs git 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:40 pins defaultGatewayImage = quay.io/bsquizza/openshell-gateway:16112bc (personal registry), while :41 keeps the supervisor on ghcr.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/OpenShell PR #2728, but as written it cannot merge to main. Please either (a) point CHART_REPO/CHART_REF at 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 swallowedError handling / "never silently swallow partial failures"

  • components/control-plane/internal/reconciler/reconciler.go:1414: resolveDatabaseConfig failure changed from return reconcileErr (which marked the gateway Failed) to a log.Printf("WARN ... skipping database reconciliation") and continues into the Helm install. The gateway then deploys referencing openshell-gateway-db-credentials which 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 (mark Failed), or document why proceeding is safe.

3. Spec says "Helm Go SDK"; implementation shells out to the helm CLISpec consistency

  • specs/platform/openshell-gateway-helm-adoption.spec.md states the reconciler "SHALL use the Helm Go SDK" and documents helm.sh/helm/v3/pkg/action. The implementation (internal/helm/shell_client.go) instead execs the helm binary (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 a helm binary 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:74 skips when status == "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:152 uses --reuse-values together with --values on 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 stateArchitecture

  • internal/reconciler/health.go:439 self-documents that DeleteGatewayAPIResources deletes 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 upgrade with routing disabled) rather than a TODO before it lands.

Minor

6. splitImageRef mishandles digest referencesinternal/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 indirectioninternal/gateway/helm_deploy.go:111 getEnvgetEnvHelperos.Getenv. Collapse to a single helper (or reuse an existing env helper) to reduce noise.

8. Lost documentationinternal/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:1450 now backfills images.DefaultGatewayImage()) and points them at a personal fork (config.go:40). Both edit internal/gateway/config.go and scripts/kind/lib.sh, and #194 deletes internal/gateway/manifests.go which #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 Handle path. #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 edit internal/reconciler/reconciler.go and skills/RECONCILE.md. Coordination needed: #151's convergence gate must drive a helm upgrade under #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-caopenshell-gateway-backend-ca, and flags health.go route teardown as Helm-drift-prone; #216 adds control-plane-managed console Routes and edits the same health.go teardown path, reconciler.go, and reconciler_test.go. Coordination needed: agree on who owns Route objects (chart vs control plane) and reconcile the health.go teardown semantics.

  • #179 — "reconcile existing Keycloak clients on gated gateways" (lower priority): Overlapping restructure of internal/reconciler/reconciler.go and health.go around 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):

  1. [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)
  2. [Major] Database-config resolution error swallowed; gateway deploys anyway and phase never reflects failure - Error handling (reconciler.go L1414)
  3. [Major] Spec mandates Helm Go SDK; code shells out to helm CLI - Spec consistency (helm-adoption spec, shell_client.go)
  4. [Major] Deployed releases never upgraded (create-or-skip) + --reuse-values on retry - Reconciliation (helm_deploy.go L74, shell_client.go L152)
  5. [Major] Out-of-band route teardown drifts Helm state (self-admitted TODO) - Architecture (health.go L439)
  6. [Minor] splitImageRef corrupts digest references - Correctness (values.go L247)
  7. [Minor] Pointless getEnv/getEnvHelper indirection - Style (helm_deploy.go L111)
  8. [Minor] Dropped DatabaseProvider rationale comment - Docs (config.go)
  9. [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 && \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Comment thread charts/CHART_REPO
@@ -0,0 +1 @@
https://github.com/bsquizz/OpenShell.git

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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" {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Minor] Pointless indirection. getEnv -> getEnvHelper -> os.Getenv. Collapse into one helper (or reuse an existing env helper in the package).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Then, I think we should keep the namespace creation in our hands, so we can attach labels to it as we are doing today

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found no LoadArchive in the code, this seems like a too low level implementation detail for a spec?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, maybe is because we are using the helm binary, so our code doesn't really load the chart?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should this file exist in this PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What does it mean "(conditional)" ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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 |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

😵‍💫

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

An example in the notes column will help to disambiguate

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Right, the desired behavior is that control-plane ensures the secret is there first

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

@rh-amarin rh-amarin Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

GatewayReconciler calls the helm delete, which would in turn cause these resources to be deleted. We can clarify this.

@rh-amarin

Copy link
Copy Markdown
Collaborator

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

Root cause: opts.UpdateRouteAddress (the callback that writes route_address back to the API server via gRPC) was only ever called with "" in the teardown path (TeardownRouteResources). It was never called with the actual hostname after a successful Helm install. So route_address stayed empty in the DB for every gateway — even fully-running ones like gw1.

What the DB confirmed:
- gw1: route_address = "", console_address = "https://console-openshell-6a4209a7af1bcb6c.gw.localhost" — console was set, route address was not
- dev-gateway: both empty (torn down, correctly cleared)

The fix (gateway/reconciler.go): After deployGatewayViaHelm succeeds, call opts.UpdateRouteAddress(ctx, nsConfig.Gateway.Route.Host) when routing is enabled and a hostname exists. The hostname is already derived earlier in the same function (either from GATEWAY_API_BASE_DOMAIN or an explicit Route.Host) and set into nsConfig.Gateway.Route.Host — it just wasn't being published back.

@bsquizz

bsquizz commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Adding a test for the scenario you hit problems with

bsquizz and others added 4 commits September 1, 2026 13:44
- 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>
@bsquizz

bsquizz commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

/retest

@bsquizz

bsquizz commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Ok @rh-amarin -- your feedback should be incorporated. Try testing it again too, hopefully the problem you encountered is fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amber/approved The Amber review agent has approved this PR. amber/self-review This PR was reviewed by the Amber review agent by one of the contributors to the PR. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants