fix: version the dashboard asset URLs, and stop preflight crying wolf - #92
Conversation
Two deploy-hygiene defects. The first shipped a broken UI to production this morning; the second makes the diagnostic that should have caught it untrustworthy. Asset cache-busting. index.html is served no-cache and its assets public, max-age=3600, but the markup named them unversioned — src="app.js". A deploy that changed index.html, app.js, tools.js and style.css together therefore served NEW HTML against an HOUR-OLD app.js to every returning visitor: the new buttons rendered and did nothing, the previous 8s refresh interval kept running, and the whole thing healed itself when the cache expired, which is indistinguishable from a deploy that silently failed. Asset URLs now carry a short content hash of the embedded UI, computed once at init, and the references are rewritten as the (uncached) HTML is served: app.js?v=fd6f54029fdc. max-age=3600 is now correct rather than dangerous, because the stale URL is never requested again. Content rather than the build commit, so a dirty local rebuild also gets fresh URLs. One token over all assets rather than one per asset, because tools.css is loaded by tools.js and not by the HTML — a per-asset hash of tools.js would not move when only tools.css did, leaving the same stale pair one level down. The rewrite matches quoted references built from the embedded directory listing, so it is blind to attribute order, quoting and element, covers a new asset the day it lands, and is idempotent; the test asserts a token for every asset the served page references, and that the token follows the bytes. Preflight false negatives. Both reported failures on a host that was fine: - rclone was resolved with bare `command -v`, which uses sudo's secure_path (/sbin:/bin:/usr/sbin:/usr/bin) — no /usr/local/bin, where rclone is installed. The backup unit inherits systemd's PATH and had been uploading to Box successfully all along. Tools are now resolved against the PATH the unit will run with, python3 included. - The credential loop demanded a credential for SOME_VAR, which is not configured anywhere: it is the placeholder in the comment that DOCUMENTS key_env in upstreams.yaml, and it ships with the file, so this fired on every host. Comments are stripped before extraction — trailing ones too — and a configured key_env is still checked. A preflight that cries wolf is the one people learn to ignore. Signed-off-by: Osher-Elhadad <Osher.Elhadad@ibm.com>
|
Post-merge review of Every claim below was mutation-tested: each fix was broken deliberately and the tests re-run, since that is the only thing that establishes a guard test guards anything. 1.
|
Two deploy-hygiene defects found during this morning's redeploy. The first shipped a
broken UI to production; the second makes the diagnostic that should have caught it
untrustworthy.
1. Dashboard assets had no cache-busting
dash/ui.goservesindex.htmlasno-cacheand its assets aspublic, max-age=3600, while the markup named them unversioned —src="app.js".So a deploy that changed
index.html,app.js,tools.jsandstyle.csstogetherserved new HTML against an hour-old
app.jsto every returning visitor. Observedsymptom, reported by the service owner: the new buttons rendered and did nothing, the
previous 8-second refresh interval kept running, and it healed itself an hour later —
which is indistinguishable from a deploy that silently failed.
Confirmed against the live service before the fix:
index.htmlno-cacheapp.jspublic, max-age=3600src="app.js"tools.jspublic, max-age=3600src="tools.js"style.csspublic, max-age=3600href="style.css"After — the served page (the HTML is already
no-cache, so it is the right placeto inject):
max-age=3600is now correct rather than dangerous, because the stale URL is neverrequested again.
Three deliberate choices:
tools.cssis loaded bytools.js, not by the HTML, so a per-asset hash oftools.jswould not move whenonly
tools.csschanged — leaving the identical stale-pair bug one level down.it is blind to attribute order, quoting and element; it covers a new asset the day it
lands; and it is idempotent.
2.
install.sh preflightfailed on a healthy host, twiceBoth were false negatives, and both had to be disproved by hand before this morning's
deploy could proceed.
rclone missing— resolved with a barecommand -v, which undersudousessecure_path(/sbin:/bin:/usr/sbin:/usr/bin) and therefore cannot see/usr/local/bin, where rclone is installed. The backup unit inherits systemd's PATHand had been uploading to Box successfully all along. Tools are now resolved against
the PATH the unit will run with,
python3included.credential for SOME_VAR— not configured anywhere. It is the placeholder insidethe comment that documents
key_envinupstreams.yaml, and it ships with thefile, so this fired on every host. Comments are now stripped before extraction,
trailing ones included, and a configured
key_envis still checked.Acceptance test is the real host. Before:
Preflight FAILEDwith those two lines.After, from this branch, against the live
/etc/context-guru/upstreams.yaml:A preflight that cries wolf is the one people learn to ignore.
Tests
Four, one per property:
TestServedUIVersionsEveryAssetItReferences— every asset the served page referencescarries a token
TestAssetVersionFollowsAssetBytes— the token changes when the bytes changeTestPreflightReadsKeyEnvFromConfigNotCommentsTestPreflightResolvesToolsOnTheServicePATHCGO_ENABLED=1 go test -race -count=1 ./...→ 25 packages ok, exit 0.go vet ./...clean.gofmt -l .empty. No schema change;schemaVersionuntouched.