- name
- demo-web-ng-fastpath
- description
- Refresh the Kubernetes `demo` namespace with a web-ng-only change using the ServiceRadar fast path. Use when the diff only touches `elixir/web-ng/**` and the user wants a faster local demo rollout without rebuilding the full image graph. Covers scope verification, copying unchanged images forward, rebuilding the production `serviceradar-web-ng` release locally, pushing with `crane`, signing with the OpenBao release key, patching Argo, and verifying the rollout. Do not use when non-web-ng services changed or when cutting a release.
# Demo Web-NG Fast Path
## Overview
Use this skill when a change is isolated to `elixir/web-ng/**` and the goal is to test it in `demo` quickly. Rebuild only `serviceradar-web-ng`, copy the other `demo` images forward to the new immutable tag, sign the new web-ng image, patch Argo, and verify the rollout.
Formal releases use semver tags, such as `v1.2.41`, and ArgoCD Image Updater. This skill is only for temporary unpublished `sha-...` demo testing.
## Workflow
1. Work from the repo root.
2. Determine the new immutable tag from `git rev-parse HEAD`.
3. Verify the diff only touches `elixir/web-ng/**`.
4. Identify the currently deployed `demo` tag.
5. Copy every unchanged `demo` image from the old tag to the new tag.
6. Build a local production `web-ng` release.
7. Package and push the new `serviceradar-web-ng` image with `crane`.
8. Sign the new web-ng digest with the OpenBao-backed release key.
9. Patch `serviceradar-demo-prod` to the new tag.
10. Watch Argo and the key workloads until the rollout completes.
## Guardrails
- Use this only when the diff is actually `web-ng`-only. If anything outside `elixir/web-ng/**` changed, fall back to `$demo-local-rollout`.
- Do not use this for release cuts or any namespace other than `demo` unless the user explicitly redirects you.
- Do not leave a formal release rollout on a `sha-...` tag. After testing is complete, use `$release-cut-and-demo-roll` to return `demo` to the published semver/Image Updater path.
- Do not skip signing. `demo` admission is Kyverno-enforced.
- Sign by digest, not by tag, whenever possible.
- Keep the other demo images identical by copying them forward from the currently deployed tag.
## Verify The Scope First
Run:
```bash
git diff --name-only <currently-deployed-sha>..HEAD
```
Proceed only if every changed file is under `elixir/web-ng/`.
## Copy Unchanged Images Forward
Copy the unchanged images from the current demo tag to the new tag with `crane`:
```bash
/tmp/gobin/crane copy \
registry.carverauto.dev/serviceradar/<image>:sha-<old> \
registry.carverauto.dev/serviceradar/<image>:sha-<new>
```
Repeat for:
- `arancini`
- `serviceradar-agent`
- `serviceradar-agent-gateway`
- `serviceradar-core-elx`
- `serviceradar-datasvc`
- `serviceradar-db-event-writer`
- `serviceradar-faker`
- `serviceradar-flow-collector`
- `serviceradar-log-collector`
- `serviceradar-rperf-client`
- `serviceradar-tools`
- `serviceradar-trapd`
- `serviceradar-zen`
Leave `serviceradar-log-collector-tcp` alone unless the user explicitly changed that path too.
## Build The Production Web-NG Release
From `elixir/web-ng`:
```bash
MIX_ENV=prod HEX_HTTP_CONCURRENCY=1 HEX_HTTP_TIMEOUT=120 mix deps.compile
MIX_ENV=prod HEX_HTTP_CONCURRENCY=1 HEX_HTTP_TIMEOUT=120 mix compile
MIX_ENV=prod HEX_HTTP_CONCURRENCY=1 HEX_HTTP_TIMEOUT=120 mix assets.deploy
MIX_ENV=prod HEX_HTTP_CONCURRENCY=1 HEX_HTTP_TIMEOUT=120 mix release --path /tmp/serviceradar_web_ng_release_<shortsha>
```
## Package And Push The Web-NG Image
Create the image layer tarball:
```bash
tar --owner=10001 --group=10001 --transform='s,^,app/,' \
-cf /tmp/serviceradar_web_ng_layer_<shortsha>.tar \
-C /tmp/serviceradar_web_ng_release_<shortsha> .
```
Append the release onto the pinned Elixir base image and then mutate the runtime config:
```bash
/tmp/gobin/crane append \
--platform linux/amd64 \
-b index.docker.io/hexpm/elixir:1.19.4-erlang-28.3-debian-bookworm-20251208-slim \
-f /tmp/serviceradar_web_ng_layer_<shortsha>.tar \
-t registry.carverauto.dev/serviceradar/serviceradar-web-ng:sha-<new>
/tmp/gobin/crane mutate \
--platform linux/amd64 \
--tag registry.carverauto.dev/serviceradar/serviceradar-web-ng:sha-<new> \
--entrypoint /app/bin/serviceradar_web_ng \
--cmd start \
--env HOME=/app \
--env PATH=/app/bin:/usr/local/bin:/usr/bin:/bin \
--env PHX_SERVER=true \
--env MIX_ENV=prod \
--exposed-ports 4000/tcp \
--user 10001:10001 \
--workdir /app \
registry.carverauto.dev/serviceradar/serviceradar-web-ng:sha-<new>
```
Capture the pushed digest with:
```bash
/tmp/gobin/crane digest registry.carverauto.dev/serviceradar/serviceradar-web-ng:sha-<new>
```
## Verified Facts (2026-08-22, live run)
These were each confirmed against the live `carverauto` cluster during a real web-ng roll.
Do not re-derive them.
- **OpenBao is HTTPS.** Through the port-forward, `https://127.0.0.1:18200` works and
`http://` returns `400 Client sent an HTTP request to an HTTPS server`.
- **The role is `forgejo-signing-runner` (namespace `forgejo-actions`), NOT `forgejo-runner`.**
The plain `forgejo-runner` role does not exist. A successful login returns policies
`["cosign-runner","default","plugin-upload-signing"]`.
- **`.argocd-source-serviceradar-demo-prod.yaml` REPLACES `helm.parameters` at render time.**
This is stronger than a race: a `kubectl patch` of `spec.source.helm.parameters` on
`serviceradar-demo-prod` persists in the Application spec, syncs `Synced|Healthy|Succeeded`,
and is still **completely ignored** — only the parameters listed in that file on
`demo/prod-release` reach Helm. Any parameter you need (`global.imageTag`,
`image.digests.*`) must be committed to that file on `demo/prod-release`.
- **`image.digests.<service>` is a real per-service escape hatch** (`_helpers.tpl`
`serviceradar.imageRefSuffix`): it short-circuits ahead of the tag, so you can move ONE
service and leave every other image on the already-signed release tag — one signature
instead of fifteen. Service key for web-ng is `webNg`. It still has to go in the
`.argocd-source-...` file to take effect.
- **`make push_all` also moves `latest`** on every image (`oci_push` carries
`static_tags = ["latest"]`), despite advice elsewhere to "tag only sha-<commit>".
It does NOT move `v<VERSION>`: `scripts/workspace_status.sh` emits `STABLE_VERSION dev`
unless a matching `v<VERSION>` git tag points at HEAD, and `container_tags.bzl` filters
`vdev`. Verify with `git tag --points-at HEAD` before pushing.
- **Claude Code auto mode blocks the signing flow** unless these allow rules exist in
`.claude/settings.local.json`, and the commands are run discretely (a `bash -c '...'`
wrapper defeats prefix matching):
`Bash(kubectl create token:*)`,
`Bash(curl -sS -k -X POST https://127.0.0.1:18200/v1/auth/kubernetes/login:*)`,
`Bash(cosign sign:*)`, `Bash(cosign verify:*)`.
Keep the JWT and Vault token in files; never put them on a command line.
## Prepare OpenBao Signing Env
Port-forward the signer if needed:
```bash
kubectl port-forward -n openbao-system svc/openbao-active 18200:8200
```
Mint a Forgejo runner service-account token and exchange it for a Vault token:
```bash
OPENBAO_ADDR=https://127.0.0.1:18200
OPENBAO_K8S_ROLE=forgejo-signing-runner # NOT forgejo-runner (that role does not exist -> 403)
sa_jwt="$(kubectl create token -n forgejo-actions forgejo-signing-runner)"
vault_token="$({
curl -sS -k \
-H 'Content-Type: application/json' \
-d "{\"role\":\"${OPENBAO_K8S_ROLE}\",\"jwt\":\"${sa_jwt}\"}" \
"${OPENBAO_ADDR}/v1/auth/kubernetes/login" # HTTPS + -k: the listener is TLS, http:// returns 400
} | jq -er '.auth.client_token')"
```
Export:
```bash
export VAULT_ADDR="$OPENBAO_ADDR"
export VAULT_TOKEN="$vault_token"
export COSIGN_KEY_REF=hashivault://cosign-release
export COSIGN_YES=true
export COSIGN_DOCKER_MEDIA_TYPES=1
export COSIGN_REFERRERS_MODE=legacy
export COSIGN_TLOG_UPLOAD=true
```
If signing fails with `403 permission denied`, mint a fresh Vault token and retry.
## Sign The Web-NG Digest
```bash
cosign sign --key "$COSIGN_KEY_REF" \
registry.carverauto.dev/serviceradar/serviceradar-web-ng@sha256:<digest>
```
## Patch Demo Argo App
```bash
kubectl patch application -n argocd serviceradar-demo-prod \
--type merge \
-p '{"spec":{"source":{"helm":{"parameters":[{"name":"global.imageTag","value":"sha-<new>"}]}}}}'
```
## Verify Rollout
Wait for:
```text
Synced|Healthy|Succeeded
```
Use:
```bash
kubectl get application -n argocd serviceradar-demo-prod \
-o jsonpath='{.status.sync.status}{"|"}{.status.health.status}{"|"}{.status.operationState.phase}{"\n"}'
```
Check the key deployments:
```bash
kubectl get deploy -n demo \
serviceradar-web-ng serviceradar-core serviceradar-agent serviceradar-tools \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .spec.template.spec.containers[*]}{.image}{" "}{end}{"\n"}{end}'
```
Inspect pods and jobs if Argo is still `Progressing`:
```bash
kubectl get pods -n demo -o wide
kubectl get jobs -n demo
```
Do not report success until the new `serviceradar-web-ng` pod is running on the new tag and Argo reaches `Succeeded`.
## Report Back
Close with:
- target immutable tag
- old tag that was copied forward
- web-ng digest that was signed
- final Argo status
- any lingering rollout risk
Ver no GitHub