Skip to content

[HYPERSHELL-259] feat: reconcile gateway version for CLI installation - #210

Merged
jsell-rh merged 25 commits into
mainfrom
feature/gateway-version-install
Sep 11, 2026
Merged

jsell-rh merged 25 commits into
mainfrom
feature/gateway-version-install

Conversation

@jsell-rh

@jsell-rh jsell-rh commented Aug 26, 2026 •

Copy link
Copy Markdown
Collaborator

Jira: HYPERSHELL-259

Ready gateways now publish their observed runtime version in the read-only gateway_version field. The console uses that value to show an OpenShell CLI installation command. For example, v0.0.109-rh9a8f8 selects the upstream CLI release v0.0.109, while the API keeps the full reported value.

  • Validate the gateway namespace as a Kubernetes DNS label before building the health URL. Read the version from the internal /health endpoint with bounded requests, response-size limits, and redirect rejection. Keep the last value if observation fails.
  • Store the version through an atomic field-owned write. General gateway updates cannot overwrite it. When a service-account allowlist is configured, ordinary gateway owners and creators cannot call SetGatewayVersion.
  • Keep one definition of the internal health Service and controller policy, owned by the health reconciler. Remove the duplicate provisioning manifests and their unused namespace parameter. Reconcile these resources at first use, then every five minutes. Retry on the next health pass after a failed version observation. Give each Kubernetes resource operation its own three-second timeout. Remove old health-port permissions from the owned sandbox and router policies on existing gateways. Preserve other permissions and retry update conflicts.
  • Use an initial list, periodic resync, and four bounded health workers. Keep each gateway's health and version work in one serial pass. Preserve current cluster filtering, Keycloak status protection, metrics, tracing, and controller supervision.
  • Show the install command before gateway setup. Keep the sandbox connection instructions, editor selection, and CPU and memory defaults from current main. Poll for a missing version for a bounded period.
  • Use the HyperShell CLI-only installer. Download the selected NVIDIA release archive, verify its SHA-256 checksum, and install to ~/.local/bin. Add that directory to the current shell PATH. The upstream installer starts a local gateway; its TLS status check failed in all three live CI jobs. Remove the first - and all following text from the installer version, and add a leading v if needed. The command requires neither an installed OpenShell CLI nor jq.
  • Include the installation test from test(e2e): install openshell CLI via console-recommended command #219, adapted to current CI with its original author retained. CI always installs the selected CLI and compares complete version values. Shell tests cover suffix removal, invalid options, version mismatches, supported platforms, and damaged or missing checksums. CI runs the candidate script from the checkout before its public main URL is available.

The branch is updated against main through 67a1e80c. API and SDK output was regenerated. Fleet remains removed. The duplicate OpenShift environment change was dropped because #232 already supplies it. Desired image selection from #238 and release reconciliation from #235 remain separate from observed runtime version reporting.

Validation on this branch:

  • make check through commit and push hooks
  • Full control-plane tests with go test -race ./...
  • Gateway API integration suite with API_ENV=integration_testing go test ./plugins/gateways -count=1
  • API RBAC tests, including rejection of ordinary owners and creators for the version RPC
  • API-server and control-plane lint checks: no issues
  • Go SDK tests and TypeScript SDK check/build
  • Gateway management UI checks: 191 tests passed
  • Web-console checks: 103 tests passed; production and Storybook builds passed
  • make ci-test: all six shell test files passed
  • Download and checksum verification of the real v0.0.109 CLI archive
  • New tests for access-check frequency, retry after observation failure, cache expiry, separate resource timeouts, invalid namespaces, and a single owner for health resources
  • Base and OpenShift Kustomize renders

The original nine Amber review threads and the two follow-up threads are addressed. Shared Keycloak configuration and its HTTP client stay unchanged after construction. The mutex protects the token and its expiry. The concurrent token test passes under the race detector.

Related PRs:

The API migration setup error reported in the previous PR description is fixed. The new column migration uses explicit SQL. Both CI gates and all three live jobs (deployment, external, and CNPG) passed on implementation commit 1b5a49c6. The live tests verify installation of CLI v0.0.109 for runtime 0.0.109-rhaiv.0. The image builds and SDK drift check also passed.

Checks run · Tests run

Follow-up review decisions (9847c9f3, comments only):

  • Field 23 remains allocated to provisioning_conditions in feat: gateway provisioning progress model and stepper UI #269. Do not add reserved 23;: a reserved number cannot be used for that field. See the Protocol Buffers field-number rules. The allocation is now documented beside gateway_version.
  • Each gateway has its own namespace, and the worker queue removes duplicate gateway IDs. The access-check comment now states that the due check and timestamp write use separate lock acquisitions. A future design with shared namespaces must add a lock for each namespace around the full access operation.

Protocol generation produced no generated-code changes. Repository checks passed. CI has restarted for the comments-only commit.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. 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: Advanced

Run ID: 4ef4d609-6e4b-41cf-9005-3b4b5ff619a0

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.

@jsell-rh jsell-rh changed the title feat(web-console): add gateway-matched CLI installation [HYPERSHELL-259] feat(web-console): add gateway-matched CLI installation Aug 26, 2026
@jsell-rh
jsell-rh added this pull request to the merge queue Aug 27, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 27, 2026
@jsell-rh

jsell-rh commented Aug 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

Amber review

Status: Complete

Verdict

This is a clean, well-tested web-console change that adds a gateway-version-matched OpenShell CLI install command, reorders the Connection tab so installation precedes the combined registration + provider block, and removes the service-account shortcut - all reflected in the spec and covered by additive tests. I recommend it for merge after two Minor cleanups; the main action item is cross-PR coordination with #208, which restructures the same Connection tab and still consumes the pre-rename EditableCommand/buildSetupScript API this PR replaces.

Amber Analysis

The refactor of EditableCommand -> CommandBlock (making marker/label/value props optional so a static command can reuse the same highlighted, copyable block) is a good consolidation, and the user-supplied endpoint is correctly passed through shellArgument() before it lands in the generated command (with a test that injects a space). Test diff scrutiny came back clean: the modified assertions track real behavior changes (prereq copy, step ordering, service-account link removal) and are all mirrored in the spec and PR description, not silent contract removals - and the endpoint-undefined readiness case that moved out of gateway-connection-steps.test.tsx is still covered by the unit test in gateway-connections.test.ts.

Findings

[Minor] Translator description contradicts the rendered order - Content / i18n
connectionInstallPrereq's description says the note is "shown after gateway registration and before provider setup" (messages.ts:102, en.json:156), but the component renders the prerequisite alert (and its install command) before the combined registration+provider block, and the test asserts exactly that (installationIndex < registrationIndex). The description gives translators the wrong placement context; align it with the actual "before the one-time setup commands" order the spec mandates.

[Minor] install.sh is fetched from main while the CLI version is pinned - Supply chain (Confidence: Low)
buildOpenShellInstallCommand pins OPENSHELL_VERSION to the gateway's reported version but pulls the installer itself from .../NVIDIA/OpenShell/main/install.sh (gateway-connections.ts:80-81,104). A future breaking change to install.sh on main could desync from the pinned version. This matches the command verbatim added to architecture.spec.md in this same PR, so it is intentional - flagging only so maintainers consciously accept pulling the installer from an unpinned ref.

Cross-PR coordination

I reviewed the other open PRs in openshift-online/hypershell (#216, #214, #212, #211, #209, #208, #207, #206, #201, #200, #194, #189, #188, #185, #182, #179, #151, #150, #148, #135, #109, #75, #73). One material conflict:

No material conflict with the other open PRs. The dashboard PRs (#209/#214) and the deps/i18n PRs touch locales/en.json/messages.ts only additively (no competing design), #206 (hsctl login) is a separate Go CLI and does not compete with this UI's "register the gateway" copy change, and #216 (OpenShift Route console) changes how the control plane publishes the gateway endpoint but keeps the same endpoint field this UI consumes.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Translator description for connectionInstallPrereq says "after gateway registration" but the alert renders before registration - Content / i18n (messages.ts L102, en.json L156)
  2. [Minor] Installer pulled from NVIDIA/OpenShell/main/install.sh (unpinned) while CLI version is pinned - Supply chain (gateway-connections.ts L80-81, L104)

Convention Checklist

Convention Result
Input validated / shell arguments quoted Pass
No secrets in logs or responses Pass
Reuse shared/PatternFly UI components (no duplicate component) Pass
Config separate from code (i18n messages, URLs) Pass
Test Diff Scrutiny (modified assertions justified, no hidden contract removal) Pass
Spec updated to match behavior Pass
Conventional commit messages Pass

@jsell-rh jsell-rh left a comment

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.

Verdict

This is a clean, well-tested web-console change that adds a gateway-version-matched OpenShell CLI install command, reorders the Connection tab so installation precedes the combined registration + provider block, and removes the service-account shortcut - all reflected in the spec and covered by additive tests. I recommend it for merge after two Minor cleanups; the main action item is cross-PR coordination with #208, which restructures the same Connection tab and still consumes the pre-rename EditableCommand/buildSetupScript API this PR replaces.

Amber Analysis

The refactor of EditableCommand -> CommandBlock (making marker/label/value props optional so a static command can reuse the same highlighted, copyable block) is a good consolidation, and the user-supplied endpoint is correctly passed through shellArgument() before it lands in the generated command (with a test that injects a space). Test diff scrutiny came back clean: the modified assertions track real behavior changes (prereq copy, step ordering, service-account link removal) and are all mirrored in the spec and PR description, not silent contract removals - and the endpoint-undefined readiness case that moved out of gateway-connection-steps.test.tsx is still covered by the unit test in gateway-connections.test.ts.

Findings

[Minor] Translator description contradicts the rendered order - Content / i18n
connectionInstallPrereq's description says the note is "shown after gateway registration and before provider setup" (messages.ts:102, en.json:156), but the component renders the prerequisite alert (and its install command) before the combined registration+provider block, and the test asserts exactly that (installationIndex < registrationIndex). The description gives translators the wrong placement context; align it with the actual "before the one-time setup commands" order the spec mandates.

[Minor] install.sh is fetched from main while the CLI version is pinned - Supply chain (Confidence: Low)
buildOpenShellInstallCommand pins OPENSHELL_VERSION to the gateway's reported version but pulls the installer itself from .../NVIDIA/OpenShell/main/install.sh (gateway-connections.ts:80-81,104). A future breaking change to install.sh on main could desync from the pinned version. This matches the command verbatim added to architecture.spec.md in this same PR, so it is intentional - flagging only so maintainers consciously accept pulling the installer from an unpinned ref.

Cross-PR coordination

I reviewed the other open PRs in openshift-online/hypershell (#216, #214, #212, #211, #209, #208, #207, #206, #201, #200, #194, #189, #188, #185, #182, #179, #151, #150, #148, #135, #109, #75, #73). One material conflict:

  • #208 "[HYPERSHELL-129] feat(web-console): Adding section to gateway UI with instructions for sandbox connecting" - This is a genuine design/interface conflict, not just a file-merge overlap. Both PRs restructure the same Connection tab and edit the same shared files (editable-command.tsx, gateway-connection-steps.tsx, gateway-connections.ts, gateway-connection-steps.test.tsx, gateway-connections.test.ts, messages.ts, locales/en.json). Critically, #210 renames the shared exports EditableCommand -> CommandBlock and buildSetupScript -> buildOneTimeSetupScript, while #208 still imports and uses the old EditableCommand and buildSetupScript to add its "Connect to a sandbox" step. Whichever merges first, the other must rebase onto the renamed component API and the reordered step layout - and both are inserting/reordering children inside the same GatewayConnectionSteps step list. Maintainers should decide a merge order and have the later PR adopt this PR's CommandBlock/buildOneTimeSetupScript API rather than reintroducing the old names.

No material conflict with the other open PRs. The dashboard PRs (#209/#214) and the deps/i18n PRs touch locales/en.json/messages.ts only additively (no competing design), #206 (hsctl login) is a separate Go CLI and does not compete with this UI's "register the gateway" copy change, and #216 (OpenShift Route console) changes how the control plane publishes the gateway endpoint but keeps the same endpoint field this UI consumes.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Translator description for connectionInstallPrereq says "after gateway registration" but the alert renders before registration - Content / i18n (messages.ts L102, en.json L156)
  2. [Minor] Installer pulled from NVIDIA/OpenShell/main/install.sh (unpinned) while CLI version is pinned - Supply chain (gateway-connections.ts L80-81, L104)

Convention Checklist

Convention Result
Input validated / shell arguments quoted Pass
No secrets in logs or responses Pass
Reuse shared/PatternFly UI components (no duplicate component) Pass
Config separate from code (i18n messages, URLs) Pass
Test Diff Scrutiny (modified assertions justified, no hidden contract removal) Pass
Spec updated to match behavior Pass
Conventional commit messages Pass

Comment thread packages/gateway-management-ui/src/messages.ts Outdated
Comment thread packages/gateway-management-ui/src/gateways/gateway-connections.ts Outdated
@jsell-rh

jsell-rh commented Aug 31, 2026 •

Copy link
Copy Markdown
Collaborator Author

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@jsell-rh jsell-rh left a comment

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.

Verdict

This is a well-structured feature: the runtime version is observed from the gateway /health endpoint, stored through a dedicated field-owned gRPC write that a whole-row Replace cannot clobber, and the new concurrent health loop is correctly guarded (route-timer maps behind mu, Keycloak token returned as a locked snapshot). I found no blockers; the notes below are minor, plus one deploy change that is out of scope for this feature and overlaps a dedicated PR.

Strengths

  • SetGatewayVersion is atomic (UPDATE ... WHERE gateway_version IS DISTINCT FROM ?) and emits the update event only on a real change; Replace now omits GatewayVersion alongside ActiveSandboxCount, and grpc_integration_test.go proves a whole-row update cannot overwrite the reconciled version. Good use of the transactional-outbox pattern.
  • The move to 4 bounded workers is race-safe: the shared route-state maps are all accessed under h.mu, and keycloak.Client.ensureToken now returns a token snapshot instead of letting callers read c.token unlocked.
  • Version observation is defensively bounded: 3s timeout, redirects rejected (http.ErrUseLastResponse), response body size-limited, control characters and over-long values rejected, and observation failures are logged without perturbing phase/status.
  • Spec, data-model, OpenAPI, proto, SDKs, and web console are all updated consistently, and the read-only field is documented as control-plane-owned.

Minor findings

  1. Per-tick, per-gateway health-access reconcile (components/control-plane/internal/reconciler/gateway_version.go:43) - ReconcileGatewayHealthAccess runs for every ready gateway on every tick, issuing a Service+NetworkPolicy Get (and possible Update) each pass. This is bounded and correct, but the owned resources rarely drift; consider reconciling them less frequently (e.g. only on create/first-observe or on a longer cadence) to reduce steady-state API traffic across a large fleet. Confidence: Medium.

  2. Unrelated deploy/auth change bundled into this feature PR (deploy/openshift/kustomization.yaml:80-81) - adding API_ENV=development_oidc and restructuring the env patch to append is an OpenShift JWT-auth fix, not part of gateway-version reconciliation. It also duplicates a dedicated PR (see Cross-PR coordination). Consider dropping it here so the feature PR stays focused. Confidence: High.

  3. Sandbox NetworkPolicy no longer allows port 8081 (components/control-plane/internal/gateway/reconciler.go, openshell-gateway-allow-sandbox-v2) - health access on 8081 is now restricted to the controller-only policy. The spec was updated to match (sandboxes need only gRPC 8080), so this looks intentional; please confirm no sandbox workload relies on reaching the gateway health port. Confidence: Medium.

Cross-PR coordination

  • The dedicated OpenShift JWT-auth fix (API_ENV=development_oidc) that this PR adds in its final commit to deploy/openshift/kustomization.yaml is the entire subject of another open pull request that changes the same file for the same purpose. This is a duplicate/competing solution: maintainers must decide which PR owns the deploy-env change and coordinate merge order, because the second to merge will conflict and carry a redundant fix.
  • Another open pull request adds a new "Connect to a sandbox" section to the same gateway Connection-tab components this PR restructures. This PR renames the shared EditableCommand component to CommandBlock (making its props optional) and renames buildSetupScript to buildOneTimeSetupScript in gateway-connections.ts. That is a competing interface change to shared UI code the other PR builds on; whichever merges second must be updated to the renamed component/API, so the owners need to agree on the final component surface and a merge order.
  • Another open pull request is stacked on this branch and asserts, in an e2e test, the exact console-recommended install command that this PR's buildOpenShellInstallCommand produces (including the --suffix stripping) and consumes the new read-only gateway_version field. It must merge after this PR, and any change reviewers request to the install-command format here has to be mirrored there; coordinate the command contract and merge order.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Unrelated OpenShift JWT-auth deploy change bundled into a version-reconcile feature PR - Scope / Cross-PR (deploy/openshift/kustomization.yaml:80)
  2. [Minor] Per-tick, per-gateway reconcile of owned health Service/NetworkPolicy adds steady-state API traffic - Performance (gateway_version.go:43)
  3. [Minor] Confirm no sandbox relies on gateway port 8081 after tightening the sandbox NetworkPolicy - Spec Consistency (reconciler.go)

Convention Checklist

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
errors.IsNotFound handled for 404 scenarios Pass
No secrets in logs or responses Pass
Input validated (version length/control chars, required ID) Pass
Reconcile pattern used (update-or-create) Pass
Status/field updated only on change; error paths logged Pass
Concurrency safe (shared maps + Keycloak token guarded) Pass
OpenAPI client not manually edited (make generate) Pass
Image references consistent across manifests Pass
Conventional commit messages Pass
Test diff scrutiny (assertions additive, no flipped guarantees) Pass

Comment thread components/control-plane/internal/reconciler/gateway_version.go Outdated
Comment thread deploy/openshift/kustomization.yaml Outdated

@amber-review-bot amber-review-bot 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

Carried-forward assessment: COMMENT.

This entry carries forward the existing Amber review of commit 6f43074361614cb97889c7ff7109f3dbf7be27b2.

Original Amber review by @jsell-rh. The original review contains the findings and inline comments.

No new analysis was performed for this migration entry.

@amber-review-bot

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@amber-review-bot

amber-review-bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@amber-review-bot amber-review-bot 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

This PR adds a read-only gateway_version field that the health reconciler observes from the gateway's internal /health endpoint and writes back through a dedicated, RBAC-restricted, field-owned gRPC path, then drives an OpenShell CLI install command in the console. The implementation is careful and convention-compliant (bounded HTTP with redirect rejection and size/length/control-char validation, atomic IS DISTINCT FROM write with transactional-outbox event, service-account-only RPC, restrictive NetworkPolicy/Service reconciliation, and a mutex added to make the now-concurrent health loop safe). I found no blocking defects; my notes are minor, plus cross-PR coordination items maintainers should resolve before merge.

Strengths

  • Secret/observability hygiene: observed version is validated for control characters, capped in length, response body is bounded, and redirects are rejected. No secrets logged.
  • Field ownership: Replace omits GatewayVersion (like ActiveSandboxCount), the DAO write is atomic and only emits an event when the value changes, and SetGatewayVersion is added to the service-account-only method set with a matching interceptor test.
  • Concurrency correctness: moving the health loop from serial to four workers is backed by a new sync.Mutex guarding the route-timer maps and by reworking keycloak.ensureToken to return a token snapshot under lock (the previous code read c.token outside the lock, which would race under concurrency).
  • Migration is additive and nullable (ADD COLUMN IF NOT EXISTS gateway_version TEXT); no existing test flips an optional field to required, so no backfill gap.

Minor notes (non-blocking)

  1. Per-tick access reconciliation cost - reconcileGatewayVersion calls ReconcileGatewayHealthAccess for every Ready gateway on every 30s tick, which performs several Get/potential Update calls (Service, controller policy, three legacy policies). This is correct reconcile behavior, but at fleet scale it multiplies API-server/K8s calls each tick; consider a lighter cadence or a "converged" short-circuit once the legacy cleanup is done. (Minor, observability/scale.)
  2. Shared 3s access timeout - gatewayHealthAccessTimeout = 3s wraps the Service plus network-policy reconciliation plus three legacy-policy edits, each with RetryOnConflict. Under conflict-heavy conditions this shared budget can abort mid-reconcile and skip the version observation for that tick. It self-heals next tick, so low severity, but a per-operation timeout would be more predictable. (Minor.)
  3. Concurrent Keycloak client use - the token race is fixed, and go test -race is reported clean. Please confirm no other mutable state on the shared ConsoleClientChecker is touched concurrently by the four workers now that the loop is parallel. (Low confidence, verification request.)

Cross-PR coordination

  • #269 introduces provisioning_conditions as optional/repeated field number 23 in the Gateway proto message, the same field number this PR assigns to gateway_version. Both also add a new gateway migration and a new OpenAPI Gateway property. This is a direct competing interface change: whichever merges second must renumber its proto field (to 24) and reconcile the migration ordering and generated SDK/OpenAPI output. Maintainers should pick a merge order and assign non-overlapping field numbers.
  • #261 independently rewrites the same reconcileGatewayHealth Degraded/Provisioning determination (adding a deployment-not-ready grace timer and new per-gateway timer maps) while this PR changes that function's signature and makes the loop concurrent. The two designs collide on the same function and on openshell-gateway-health.spec.md; a maintainer decision is needed on which lands first and on ensuring #261's new timer maps are guarded by the mutex this PR introduces.
  • #219 provides the e2e "install the console-recommended CLI" test and is stacked on this branch, but this PR now incorporates that same install test inline. This is a duplicate solution: maintainers should decide whether #219 is closed/reworked after this merges, or whether the install test is dropped here in favor of #219, to avoid two implementations of the same e2e step.

Findings Summary (ordered by severity, highest first):

  1. [Minor] Full health-access reconciliation runs for every Ready gateway every tick, multiplying K8s calls at fleet scale - Observability/Scale (gateway_version.go L43)
  2. [Minor] Single 3s timeout shared across Service + policy + three legacy-policy edits can abort mid-reconcile under conflicts - Reliability (health_access.go L46)
  3. [Minor] Confirm no other shared mutable state on ConsoleClientChecker under the new concurrent workers - Concurrency (health.go L70)

Convention Checklist:

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
errors.IsNotFound / record-not-found handled Pass
No secrets in logs or responses Pass
Input validated (size/length/control chars, redirect rejection) Pass
SecurityContext / NetworkPolicy scoping Pass
Reconcile pattern (get-then-create/update) Pass
Image references consistent Pass
OpenAPI/SDK generated, not hand-edited Pass
Test Diff Scrutiny (no optional->required flip without fallback) Pass
Conventional commits Pass

Comment thread components/control-plane/internal/reconciler/gateway_version.go Outdated
Comment thread components/control-plane/internal/gateway/health_access.go Outdated
Comment thread components/control-plane/internal/reconciler/health.go
@amber-review-bot

amber-review-bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review

Status: Stopped

The pull request head changed before Amber posted the review. A later job can review the new head.

@amber-review-bot

amber-review-bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@amber-review-bot amber-review-bot 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

This PR adds a read-only gateway_version field, a control-plane health observer that reports the gateway's runtime version, and a CLI-only installer wired into the console; the code is well structured, defensively written, and thoroughly tested (bounded HTTP reads, redirect rejection, DNS-label validation, field-owned atomic writes, SA-only RPC, checksum-verified download). I found no blockers or security regressions; the notes below are minor, plus cross-PR coordination that needs a maintainer decision.

Amber Analysis

The change follows HyperShell conventions closely. Secret handling is clean (no secrets logged), input is validated (IsDNS1123Label on the namespace, version regex in the installer, control-character/length checks on the observed version), errors are wrapped with context, errors.IsNotFound/ErrRecordNotFound are handled, and reconciliation uses update-or-create with retry-on-conflict rather than create-or-skip. The write path is correctly narrowed: Replace omits GatewayVersion, a dedicated SetGatewayVersion DAO does an atomic IS DISTINCT FROM update with a transactional-outbox event, and the gRPC method is added to the service-account-only allowlist. The concurrency change (serial health loop to four bounded workers) is handled with care: the previously "serial, no sync needed" Keycloak client now returns a locked token snapshot, and all shared reconciler maps (routeNotReadySince, routeTornDown, routeVerifiedAt, healthAccessCheckedAt) are guarded by h.mu. Tests are additive and prove the new guarantees (SA-only RPC rejection, access-check cadence, retry after failed observation, checksum-failure handling); no pre-existing assertion was flipped to hide a removed guarantee.

Minor findings

  1. proto/hypershell/v1/gateways.proto allocates field 24 and intentionally leaves 23 unused for a sibling change. If that sibling allocation shifts, an unrelated future field could silently reuse 23. Consider reserved 23; to make the reservation explicit and wire-safe.
  2. healthAccessCheckDue reads then later records under separate lock acquisitions, so two workers processing gateways that share a namespace could both run the (idempotent) access reconcile in the same pass. Harmless today given per-gateway namespaces; noting only in case namespaces are ever shared.

Cross-PR coordination

  • #269 introduces provisioning_conditions on the same Gateway protobuf message and regenerates the same shared artifacts (gateways.pb.go, the OpenAPI model), adds a new migration registered in the same plugin.go, and edits the same grpc_handler.go/grpc_presenter.go/presenter.go/model.go. This PR reserves proto field 24 and leaves 23 for that work, and claims distinct migration IDs. Maintainers must confirm the 23-vs-24 field allocation and migration-ID ordering, and whichever PR merges second must regenerate the code-generated files against the other's field.
  • #261 modifies the same gateway health reconciliation pass. This PR restructures reconcileGatewayHealth (it now returns (namespace, ready), runs under bounded concurrent workers, and drives version observation), while #261 adds provisioning readiness timers to that same function to fix the intermittent Degraded status. These are incompatible in-place designs; maintainers must decide the merge order and ensure the version-observation step is preserved when the two are reconciled.
  • #219 adds an e2e OpenShell CLI installation test and edits the same e2e workflow/lib. This PR incorporates that test (adapted, original author retained) and the surrounding installer path, making #219 redundant once this merges. Maintainers must decide whether to close #219 or land it first.

Findings Summary (ordered by severity, highest first):

  1. [Minor] Proto field 23 is left unused for a sibling change without an explicit reserved guard - Interface Safety (gateways.proto L32)
  2. [Minor] healthAccessCheckDue check-then-record is non-atomic across workers sharing a namespace - Concurrency (gateway_version.go L46, L163)

Convention Checklist:

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
errors.IsNotFound / record-not-found handled Pass
No secrets in logs or responses Pass
Input validated (DNS label, version, URL/redirect/size) Pass
Reconcile pattern (update-or-create, retry-on-conflict) Pass
Restricted write path / SA-only RPC Pass
Concurrency safety (shared state under mutex) Pass
Image/reference and manifest consistency Pass
OpenAPI/proto generated, not hand-edited Pass
Test diff scrutiny (no flipped guarantees) Pass
Conventional commits Pass

Comment thread components/api-server/proto/hypershell/v1/gateways.proto
Comment thread components/control-plane/internal/reconciler/gateway_version.go
@amber-review-bot

amber-review-bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@amber-review-bot amber-review-bot 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

The gateway-version feature is well engineered: the observed runtime version is a control-plane-owned, RBAC-restricted, atomically-written field, the health HTTP client is hardened (DNS-label validation, bounded body, redirect rejection, per-op timeouts), and the newly concurrent health loop is correctly synchronized (all shared maps guarded by mu, Keycloak token now returned as a locked snapshot). I found no blockers or critical issues; the main items needing a human decision are cross-PR coordination (merge order and shared proto/data-model boundaries), plus one minor supply-chain note.

What I checked and liked

  • Ownership / atomic write: SetGatewayVersion uses an UPDATE ... WHERE ... IS DISTINCT FROM ... RETURNING inside a transaction with a transactional-outbox event, and Replace now omits GatewayVersion (like active_sandbox_count) so whole-row updates cannot clobber the reconciled value. The grpc_integration_test.go case proves a subsequent UpdateGateway does not overwrite it.
  • RBAC: SetGatewayVersion is added to isServiceAccountOnlyMethod, and TestUnaryInterceptor_GatewayVersionRestrictedToServiceAccount proves gateway:owner/gateway:creator humans are denied while the control-plane SA is allowed.
  • Input validation / hardening: namespace validated with IsDNS1123Label, response size capped, redirects rejected, control chars and over-length versions rejected, HTTPS-only checksum-verified installer.
  • Concurrency: worker pool dedups gateway IDs; all route-timer maps and healthAccessCheckedAt are mutex-protected; Keycloak ensureToken returns the token under lock, removing the previous serial-only assumption safely.
  • Migration: additive nullable TEXT column via ADD COLUMN IF NOT EXISTS, unique ID, idempotent rollback. Read-only field with pointer semantics means pre-existing rows (NULL) need no backfill.
  • Test diff scrutiny: modified pre-existing tests are additive (new field, new assertions); no existing assertion was flipped from accept to reject and no optional->required tightening of pre-existing data was introduced.

Findings

Minor

  1. Supply chain / reproducibility - packages/gateway-management-ui/src/gateways/gateway-connections.ts:82: the console-recommended command pipes curl .../main/scripts/install-openshell.sh | sh, pinned to the main branch. The recommended command therefore always fetches whatever is on main at run time, so a future incompatible edit to the script silently changes every gateway's install instructions and there is no reproducible pin. Consider pinning to a released tag/commit of the script (or serving it from a versioned path) so a given gateway version maps to a fixed installer.

Cross-PR coordination

  • #269 introduces provisioning_conditions on the same Gateway protobuf message and adds its own gateways-table migration. This PR takes proto field 24 for gateway_version and deliberately leaves field 23 unreserved for #269's provisioning_conditions. Maintainers must ensure #269 actually claims field 23 (not 24) and that the two gateways-table migration IDs stay distinct, then regenerate the SDK/OpenAPI/pb artifacts once, since both PRs edit the same generated files. This is a shared data-model/interface boundary that needs a merge-coordination decision, not an independent merge.
  • #261 modifies the same health-reconciliation pass (components/control-plane/internal/reconciler/health.go) that this PR restructures from a serial loop into a bounded concurrent worker pool that also drives version observation. These are competing designs for the same loop with an ordering dependency: whichever merges second must be reconciled by hand so the version-observation step (and the mutex-guarded shared state) is preserved. Maintainers should decide and enforce the merge order and re-integration.
  • #219 contributes the e2e "install CLI via the console-recommended command" test that this PR now incorporates directly. The #219 author and maintainers should decide whether #219 is rebased/closed as subsumed once this PR merges, to avoid landing the same test twice.

Findings Summary (ordered by severity, highest first)

  1. [Minor] Console install command pins the installer script to the main branch, so it is neither reproducible nor version-locked - Security / Supply Chain (gateway-connections.ts:82)

Convention Checklist

Convention Result
No panic() in production code Pass
Errors wrapped with fmt.Errorf context Pass
errors.IsNotFound / not-found handled Pass
No secrets in logs or responses Pass
Input validated (K8s DNS label, response bounds) Pass
Reconcile pattern (update-or-create, atomic writes) Pass
SecurityContext / least-privilege NetworkPolicy Pass
Proper context propagation (no context.TODO()) Pass
Concurrency correctly synchronized Pass
RBAC restricts control-plane-only mutation Pass
OpenAPI/proto regenerated, not hand-edited Pass
Migration additive / idempotent, no backfill gap Pass
Test Diff Scrutiny (no silently flipped assertions) Pass
Conventional commit messages Pass

"https://docs.nvidia.com/openshell/about/installation";

const installScriptUrl =
"https://raw.githubusercontent.com/openshift-online/hypershell/main/scripts/install-openshell.sh";

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] Supply chain / reproducibility. The console-recommended install command pipes this script from the main branch (curl .../main/scripts/install-openshell.sh | sh). Because it tracks main, any later incompatible change to install-openshell.sh silently rewrites the install instructions for every gateway, and a given gateway version has no reproducible installer pin. Consider pinning to a released tag/commit or serving a versioned path so a gateway version maps to a fixed installer script.

@jsell-rh
jsell-rh added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit 3e60e27 Sep 11, 2026
31 checks passed
@jsell-rh
jsell-rh deleted the feature/gateway-version-install branch September 11, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants