feat(frontend): complete the generatedConfig payload — namespace, environment, schemaVersion - #687
feat(frontend): complete the generatedConfig payload — namespace, environment, schemaVersion#687Starefossen wants to merge 4 commits into
Conversation
…ironment, schemaVersion The generated frontend config (nais.js) is consumed by the @nais/apm SDK as a versioned contract (nais/grafana-apm-app#134). The payload previously carried only telemetryCollectorURL, app.name and app.version — missing the two fields the APM plugin needs most: - app.namespace (the owning team; all telemetry is attributed by team) - environment (the cluster name, from naiserator's cluster-name config) schemaVersion: 1 marks the completed shape so consumers can detect which generation of the payload they read; bump it when the shape changes.
Phase 2 of nais/grafana-apm-app#134: the same payload emitted twice with identical content — the existing nais.js ES module (import) and a new nais.json (fetch), mounted as siblings so pointing mountPath into a served web root exposes both. JSON is what static SPAs consume via the @nais/apm SDK's initFromConfigUrl(); an ES module cannot be fetched-and-parsed and a plain <script src> cannot read an export. The payload is now built once as a struct; the JSON variant is marshalled from it (correct escaping for free), the ES module rendered from the same values.
Pre-existing failure, not specific to this branch (master panics identically): govulncheck v1.1.4 panics with 'ForEachElement called on type containing *types.TypeParam' under the current toolchain. v1.6.0 fixes the panic — and then correctly reports GO-2026-5856 (crypto/tls Encrypted Client Hello privacy leak) in go1.26.4's stdlib, fixed in go1.26.5. Bump both: govulncheck now reports no vulnerabilities. go test ./pkg/resourcecreator/... green (16 packages).
|
CI failure triage: the Fixed here since it blocks this PR's CI: govulncheck → v1.6.0 (fixes the panic), which then correctly flagged GO-2026-5856 (crypto/tls ECH privacy leak) in go1.26.4 → Go bumped to 1.26.5. |
… findings Two independent adversarial reviews of this PR (posted in the PR thread) refuted the original implementation. Fixes: - nais.js is now 'export default <marshalled JSON>;' — ONE escaped serialization backs both files. Previously the Sprintf template put the unvalidated spec.image tag (and telemetry URL) unescaped inside single-quoted JS literals: a tag containing a quote produced an unimportable module that silently disagreed with the valid nais.json. - versionFromImage(): digest- and registry-port-aware tag extraction (last colon after last slash, digest suffix stripped), mirroring @nais/apm's resolution so both platform paths agree on the version. Previously strings.Split leaked sha256 hex as app.version for digest-pinned images and lost the version behind registry ports. - The JSON sibling mount is narrowed: only added when the CLEANED mountPath ends in the conventional nais.js filename. Kills three concrete existing-app breakages: uncleaned paths (/dir//nais.json) sneaking duplicate-target mounts past Kubernetes' exact-string uniqueness check, trailing-slash mountPaths nesting a mount inside a file (CreateContainerError), and directory-ish paths dropping nais.json at container root. - Unit tests for all three (hostile-value round-trip, tag extraction matrix, sibling-narrowing matrix); golden file updated to the new module format. Accepted as-is (documented in the PR): environment carries naiserator's cluster-name flag default verbatim (consistent with NAIS_CLUSTER_NAME); a user filesFrom mount at <dir>/nais.json still collides at admission (loud API-server rejection, not silent breakage).
🔴 Adversarial review (two independent passes) — findings & resolutionsTwo adversarial reviews were run against this PR (operator-semantics lens and contract/injection lens), instructed to refute its safety claims. Both succeeded. All confirmed findings are fixed in Confirmed findings
Attacks that failed (safety confirmed)
Consumer follow-upThe SDK's own |
Phases 1–2 of nais/grafana-apm-app#134 (the frontend config contract, ADR-0001 in nais/apm).
Commit 1 — complete the payload
The
spec.frontend.generatedConfigpayload previously carried onlytelemetryCollectorURL,app.name,app.version. Adds:app.namespace—metadata.namespace, the owning team (the Nais APM plugin attributes all telemetry by team). Note: docs.nais.io currently implies this field already exists — it didn't.environment— the cluster name from naiserator'scluster-nameconfig: a single image resolves its environment at runtime instead of impossibly at build time.schemaVersion: 1— consumers detect which generation of the payload they read.Commit 2 — nais.json variant
The same payload emitted twice with identical content: the existing
nais.jsES module (import) and a newnais.json(fetch), mounted as siblings — pointingmountPathinto a served web root exposes both:JSON is what static SPAs consume via the
@nais/apmSDK'sinitFromConfigUrl(); an ES module can't be fetched-and-parsed, and a plain<script src>can't read anexport. The payload is now built once as a struct; JSON marshalled from it (correct escaping for free), the ES module rendered from the same values. Data only — the files never gain init behavior (ADR-0001 decision 5).New payload (both formats):
Configinterface gainsGetClusterName()(already implemented by the generator options, same as pod/ingress/azure).Tests
frontend_config.yamlgolden file updated (cluster-name in test config, JSON data key, both subPath mounts);go test ./pkg/resourcecreator/...— 16 packages green. The consumer side (nais/apm#18) carries a contract test importing this PR's verbatim golden-file output.Behavior note
Existing
generatedConfigusers get one new mounted file (<dir>/nais.json) next to their chosen mountPath. Collision guard: if mountPath itself is.../nais.json, the second mount is skipped.Later phases of #134 (not here)
Ingress-served well-known config URL; CDN publish-pipeline config writing.