Skip to content

release-train: develop -> staging - #640

Merged
tracebloc-release-train[bot] merged 6 commits into
stagingfrom
release-train/to-staging
Aug 6, 2026
Merged

release-train: develop -> staging#640
tracebloc-release-train[bot] merged 6 commits into
stagingfrom
release-train/to-staging

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Automated promotion by the release train (RFC-0008 D14). Head is the train-managed release-train/to-staging branch (a mirror of develop), so it never collides with a human PR. Merged only when the fr-gate is green.


Note

Medium Risk
Touches cluster creation, GPU scheduling, and Windows preflight paths customers hit on install/reboot; changes are guarded by tests and drift gates but a bad port/GPU classification could block or mislead installs.

Overview
Release train promotion (chart 1.9.22). The headline capability is GPU-enabled k3d edges (#616): a new docker/k3s-cuda image (CUDA base + NVIDIA Container Toolkit + baked device plugin/nvidia RuntimeClass), build-k3s-cuda manual CI workflow, and check-facts / installer-tests path filters so K3S_TAG cannot drift from K8S_VERSION.

Helm (#343) only injects GPU_REQUESTS / GPU_LIMITS into jobs-manager when GPU_LIMITS is non-empty, so CPU-only installs no longer show a phantom GPU in the CLI.

Windows installer (install-k8s.ps1) adds tri-state port 6550 preflight (reuse own running cluster vs hard-fail foreign listener vs warn on indeterminate k3d cluster list), refactors cluster run-state via full k3d cluster list JSON (#557), and improves Docker Desktop autostart for a provisioned daily user via Startup-folder shortcut (#558).

Bash install waits for metrics.k8s.io APIService before Helm render (#553). .github/workflows/public-pii-gate-caller.yml is removed. Manifest hashes and Pester/bats coverage updated for the above.

Reviewed by Cursor Bugbot for commit 047256a. Bugbot is set up for automated code reviews on this repo. Configure here.

divyasinghdsand others added 6 commits August 6, 2026 18:54
* fix(#343): stop stamping GPU_LIMITS on CPU-only installs
Only render the GPU_LIMITS env var when a GPU is actually configured
(a non-empty .Values.env.GPU_LIMITS). A CPU-only install writes
GPU_LIMITS: "" and a chart-direct install may omit the key entirely;
previously the template still emitted the var (or its "nvidia.com/gpu=1"
default), surfacing a phantom GPU in the CLI resources view that the CLI
had to normalize away client-side. Applied to both the api and
pods-monitor containers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#343): gate GPU_REQUESTS on same condition as GPU_LIMITS (#617 bugbot)
A chart-direct install that omits env.GPU_LIMITS was still emitting
GPU_REQUESTS via its "nvidia.com/gpu=1" fallback, so CPU-only installs
kept a phantom GPU and any chart-direct install could ask for a GPU
request with no matching limit (rejected by the API server). Gate
GPU_REQUESTS with the same `(default "" .Values.env.GPU_LIMITS)`
condition in both the api and pods-monitor containers so a CPU-only
install emits neither var and a GPU install emits both (request==limit).
Also bump client/Chart.yaml version 1.9.15 -> 1.9.16 so the chart
content change is publishable (chart-version-guard).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…stall (#618)
* fix(#553): wait out the metrics-server APIService race before helm install
On a freshly created k3d cluster, k3s registers its bundled metrics-server
(and the v1beta1.metrics.k8s.io APIService) shortly AFTER the API server is
ready. `k3d cluster create --wait` gates only on node/serverlb readiness, so
on a slow WSL2/laptop `helm upgrade --install` could render the
resource-monitor DaemonSet in that window and hit its `{{ fail }}` guard,
aborting the entire first install.
Add a bounded, best-effort `kubectl wait` for the metrics APIService before
the helm install (TB_METRICS_WAIT_S, default 120s). If it never registers we
fall through and let the chart's render-time guard produce its actionable
error, so a genuinely missing metrics-server is still caught (issue's
preferred option (a)).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(installer): refresh manifest.sha256 + address #618 review
- metrics APIService probe passes --request-timeout=10s (non-watch kubectl rule)
- outer spinner deadline derived from TB_METRICS_WAIT_S (+60s slack) so a
configured wait >180s is no longer silently truncated
- regenerate scripts/manifest.sha256 for the changed install-client-helm.sh
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#553): skip the metrics-server wait under test/no-kubectl so bats stays fast
The unconditional _wait_for_metrics_apiservice poll loop sleeps 3s up to
${TB_METRICS_WAIT_S:-120}s. In the mocked install_client_helm bats tests
kubectl isn't stubbed, and on the CI runner it isn't installed at all — the
`kubectl get` just fails instantly, so the loop still burns its full deadline
every test, blowing the 10-min job cap (Bugbot #618, CID 3728079502).
Gate the wait exactly like the neighbouring network-y step
_download_services_progress: no-op when TB_NO_SERVICE_PROGRESS is set (the
bats suite sets it in setup()) or when kubectl is unavailable. Real installs
set neither and always have kubectl, so the wait is unchanged for them.
Regen manifest.sha256.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Removes this repo's caller for the public PII gate. Lukas's decision,
2026-08-06: retire the gate for now rather than leave it in place undeployed.
WHY. The gate has never had a denylist. PII_DENYLIST does not exist as an org
Actions secret and never has, so the workflow correctly failed closed on every
PR - "an inactive guard must be visible as red, not invisible as green", as its
own header puts it. Applied to a secret that was never provisioned, that
produced a permanent red across all 7 public repos which blocked nothing: 24
open PRs carried it, and the whole org - this author included - had learned to
filter it out. A check that is red on every PR is not fail-closed; it is off
with extra steps, and it would be indistinguishable from noise on the day it
went red for a real reason.
WHAT LAPSES, stated plainly. The gate grepped PR title + body + every commit
message against a private list of customer/partner names, on public repos only.
It never read code, diffs or filenames, and detected no PII patterns - secrets
in code remain gitleaks' job. So what lapses is the mechanical backstop for
CLAUDE.md's rule that internal work and customer mentions never go in a public
repo. That rule still stands and is now enforced by review alone.
The reusable workflow and its selftest stay in tracebloc/.github, dormant with
no callers. Restoring is three steps: set the org secret, restore this file,
flip the inventory entry back to required. Checklist and the denylist-content
caveat are on backend#1409.
Not required on any branch, so this removes no merge gate.
#623)
* fix(#558): write Docker Desktop autostart for a provisioned daily user
Set-DailyUserProvisioning only wrote the HKCU Run key when the provisioned
user was the account running the installer ($user -eq $env:USERNAME). For a
provisioned DIFFERENT daily user (the hospital IT-installs-elevated case),
their registry hive isn't loaded, so no autostart was written and the code
relied on --always-run-service. On the WSL2 backend dockerd runs inside the
docker-desktop distro that the Docker Desktop GUI boots, so without the GUI
autostarting on that user's login the engine isn't up after a reboot and the
k3d containers have no daemon to restart into — the client is down until
someone opens Docker Desktop manually.
For the provisioned different user, drop a "Docker Desktop.lnk" into their
Startup folder (same launch-at-logon mechanism as the Run key, no hive load
needed). When they have no profile yet, name the one-click GUI setting to flip
after first sign-in. The current-user Run-key path is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(installer): refresh manifest.sha256 for changed install-k8s.ps1 (#558)
scripts/install-k8s.ps1 changed in this branch; regenerate the signed
integrity manifest via scripts/gen-manifest.sh so the "Static analysis"
gate (gen-manifest.sh --check) passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#558): surface autostart failures instead of a silent green summary
The cross-user Docker Desktop autostart path (Startup-folder shortcut for a
provisioned DIFFERENT daily user) can fail on Startup-dir creation, the
WScript.Shell COM object, or saving the .lnk. It was caught by the outer
`catch { Log ... }`, which logged but added nothing to $did. With docker-users
succeeding, the summary then printed a green "Configured for '$user'" with no
autostart note -- so IT left the elevated window thinking #558 was handled while
the daily user still had no autostart and the client is down after every reboot.
Append a manual-step note to $did on failure, mirroring the .wslconfig catch in
the same function, and name the one-click GUI remediation. Add source-guard
Pester tests for the Startup-shortcut feature and the new failure-note path;
refresh manifest.sha256.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(gpu): custom k3s-CUDA node image + publish workflow (#616)
Foundational build artifact for GPU-enabled edges, split into its own PR so it can
land on develop and be published via CI (workflow_dispatch only dispatches from the
default branch). Inert on develop — nothing references it until the installer PR
(#633) wires it in.
The stock rancher/k3s image is Alpine with no NVIDIA runtime, so GPU pods can never
schedule on it. This image rebuilds the SAME pinned k3s on an NVIDIA CUDA Ubuntu base,
installs the NVIDIA Container Toolkit, configures containerd for the `nvidia` runtime,
and bakes in the device plugin + `nvidia` RuntimeClass so the node advertises
nvidia.com/gpu on first boot. Based on the official k3d CUDA recipe.
- docker/k3s-cuda/Dockerfile, nvidia-device-plugin-daemonset.yaml, build.sh, README.md
- .github/workflows/build-k3s-cuda.yaml (manual dispatch; push=true publishes to GHCR,
which also validates the Dockerfile builds)
Once published (public), the installer pulls it automatically at cluster-create — the
end-user runs one command; no manual build or pull.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(gpu): resolve Bugbot findings on the k3s-CUDA build infra (#635)
- workflow: pass GITHUB_TOKEN + actor via `env:` and reference quoted shell vars
instead of interpolating context straight into the run line of a packages:write
job (GHA injection hardening).
- workflow: add `timeout-minutes: 60` so a stalled nvcr.io pull / wedged push
fails fast instead of spinning to the 6h default (parity with sibling workflows).
- Dockerfile: the NVIDIA keyring + apt-list curls now carry the TLS floor +
bounded timeouts inline (`--tlsv1.2 --connect-timeout 30 --max-time 60`), the
way scripts/install.sh spells them out (house rule; a Dockerfile can't source
curl_secure()).
- drift guard: the k3s pin was hardcoded in the Dockerfile ARG, build.sh, and the
workflow input default with a comment CLAIMING the workflow enforced it —
nothing did. Wire all three into check-facts.sh so they're verified == facts.env
K8S_VERSION; a bump now fails CI (and `--write` restamps them) instead of
silently deriving a GPU image tag that was never published (#547 class).
check-facts.sh --check passes; shellcheck clean; workflow YAML valid.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(gpu): seed the k3s-CUDA consumers into the check-facts sandbox (#635)
Adding the GPU image's k3s pin as check-facts consumers made check-facts.bats
fail — the throwaway repo the tests build only seeds scripts/, so the new
Dockerfile / build.sh / workflow consumers reported "file not found" and the
--check/--write tests went red. Seed those three files into setup() (matching the
spec) so the drift-guard tests exercise them too. Also proves the workflow
extractor picks the k3s_tag default (v… tag) and leaves cuda_tag alone.
Local: check-facts.bats 14/14, bats-hygiene 18/18.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(gpu): run the check-facts drift gate on GPU-image changes too (#635)
The check-facts --check gate lives in installer-tests.yaml, whose triggers were
path-filtered to scripts/**. A change touching only docker/k3s-cuda/* or the build
workflow never ran the gate, so a K3S_TAG edit in the GPU image could drift from
facts.env and merge green — defeating the drift rows just added (Bugbot). Add
docker/k3s-cuda/** and the build workflow to both the push and pull_request path
filters so the gate runs on those changes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…#622)
* fix(#557): preflight port 6550 on Windows so a conflict fails clearly
New-K3dCluster binds the cluster API server to 127.0.0.1:6550, but
Test-Preflight never checked whether 6550 was already in use. On a machine
where a leftover/other k3d cluster or an unrelated service owns 6550,
`k3d cluster create` failed and the installer surfaced k3d's raw stderr
instead of a clear "port already in use" cause.
Add Get-PfPortListening (Get-NetTCPConnection based; $null when
undeterminable) and a port-6550 check to Test-Preflight. A port owned by THIS
installer's own already-running cluster is fine (that run reuses it), so the
check only hard-fails when the listener is NOT our cluster, with actionable
guidance to find and stop the owner. Adds Pester coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#557): harden port-6550 preflight (bounded, fail-closed, running-gated)
Address PR #622 review on the Windows port-6550 preflight:
- Get-PfPortListening no longer fails open: -ErrorAction SilentlyContinue
swallowed real CIM/access errors into the same empty result as a free
port, so a busy port green-OK'd. Switch to -ErrorAction Stop and, in the
catch, treat only Get-NetTCPConnection's ObjectNotFound (no listener) as
"free"; every other error returns $null ("can't tell"). (Bugbot Med)
- Port-ownership check now reuses Test-ClusterRunning instead of a bare
`k3d cluster list -o json`: it wraps the call in the same ~15s job
deadline, so a wedged Docker engine can't hang preflight (Bugbot High),
and it gates on serversRunning >= 1, so a STOPPED leftover cluster no
longer masks a foreign listener on 6550 (Bugbot Med).
- Regenerate scripts/manifest.sha256 for the changed install-k8s.ps1.
- Add Pester coverage: probe error -> $null, ObjectNotFound -> free,
stopped-leftover + foreign listener -> fail, running-owned -> ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#557): give the port-6550 running-cluster case a default Has mock
After the rebase merged our port-6550 Pester tests with #612, Test-Preflight's
network-reachability block calls Has for kubectl/helm/k3d. The "port 6550 in use
by our running cluster -> ok" case passes ownership, so it now continues into
that block, where a k3d-only -ParameterFilter left those later Has calls with no
matching mock ("No mock for command 'Has' matched"). Use a plain default mock
(k3d + tools present -> only always-critical hosts probed) so every Has call is
covered and the case does not throw.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#557): tri-state port-6550 ownership so a slow k3d list can't hard-fail reuse (Bugbot 3728340365)
The port-6550 ownership check decided ownership solely from Test-ClusterRunning,
whose bounded `k3d cluster list` maps BOTH a timeout AND a parse failure to
$false. The ownership block treated that $false as "foreign listener" and
HARD-FAILED preflight with stop/delete hints, so a slow/wedged Docker on a
normal re-run wrongly blocked the install and pointed the operator at their own
cluster -- conflating "can't determine" with "definitely not ours".
Make cluster run-state tri-state:
- Get-ClusterRunStateFromList (pure) and Get-ClusterRunState (bounded) return
'running' | 'down' | 'unknown'. Get-ClusterRunState reports 'unknown' only
when the list times out or its output is unparseable.
- Test-ClusterRunningInList / Test-ClusterRunning become thin boolean wrappers
(=='running'), preserving their existing contracts and callers.
The ownership block now only hard-fails when CONFIDENT the listener is foreign
('down': enumerated and ours is absent/stopped, or no k3d installed). An
'unknown' read downgrades to a warning and proceeds, letting New-K3dCluster's
start/repair path settle it instead of blocking the install.
Tests: existing 3 cases updated to mock Get-ClusterRunState; added an
indeterminate case (list timed out -> warns, does NOT hard-fail). Manifest
regenerated. Pester not run locally (pwsh unavailable); validated by source
analysis. Source-of-truth guards for Test-ClusterRunning still hold.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#557): classify an absent cluster as 'down', not 'unknown' (Bugbot High 3728714531)
Get-ClusterRunState ran `k3d cluster list <name> -o json`, which fatals with
empty stdout when the named cluster is absent. The classifier read that empty
output as 'unknown' -> warn-and-proceed, so a genuinely-absent cluster with a
FOREIGN listener on 6550 escaped the hard-fail -- exactly the "re-run after the
first create failed on the busy port" case #557's preflight must catch.
List ALL clusters instead (`k3d cluster list -o json`, no name) and look for
$CLUSTER_NAME in the parsed array. A successful full list always emits at least
`[]`, so absent-vs-error stays separable:
- completed list without a running $CLUSTER_NAME (absent, stopped, or empty [])
-> 'down' (confidently not ours -> hard-fail on a busy port)
- only a TIMED-OUT or unparseable/failed list -> 'unknown' (warn-and-proceed)
Tests: added a Get-ClusterRunState tri-state Describe -- pure classifier cases
(absent/empty-list -> 'down'; empty/garbage -> 'unknown') plus bounded cases
(timeout -> 'unknown'; completed-but-absent -> 'down'). Manifest regenerated.
Pester not run locally (pwsh unavailable); validated by source analysis.
Source-of-truth guards for Test-ClusterRunning still hold.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#557): drop fragile job-cmdlet-mock tests for bounded Get-ClusterRunState (CI Pester)
The two bounded-wrapper cases mocked Start-Job/Wait-JobWithProgress/Receive-Job,
which is environment-fragile and fails under CI Pester. Coverage is retained via
the pure Get-ClusterRunStateFromList tests + the Test-ClusterRunning source-guard
(bounded-job regex). No product-code change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 047256a. Configure here.

Comment threaddocker/k3s-cuda/README.md
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

@shujaatTracebloc — flagging rather than fixing, since this is your k3s-CUDA feature (client#635) and the fix needs a judgement call about the GPU install flow that I should not make for you.

Bugbot rates this High on this develop -> staging promotion:

scripts/install-k8s.ps1 never selects the new k3s-cuda image or sets RUNTIME_CLASS_NAME=nvidia. GPU installs therefore continue using rancher/k3s, leaving the newly published NVIDIA runtime unused and GPU training pods unschedulable.

If that holds, the image is being built and published but nothing consumes it — the feature would ship to staging inert, and GPU training pods stay unschedulable, which is exactly the symptom someone would later debug as a cluster problem rather than a missing selection.

Per the train's rule the fix goes on develop and the promotion is re-cut — do not push onto this PR. Happy to help if you want a second pair of eyes on the install-k8s.ps1 side.

@LukasWodka — worth a look before this promotion lands.

@tracebloc-release-traintracebloc-release-trainBot added gate-nudge Toggled by the release train to (re-)fire the fr-gate and removed gate-nudge Toggled by the release train to (re-)fire the fr-gate labels Aug 6, 2026
@tracebloc-release-train
tracebloc-release-trainBot merged commit 03ddef4 into stagingAug 6, 2026
46 checks passed
Sign up for freeto 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

@LukasWodka@divyasinghds@shujaatTracebloc