From a7b3d2d2c719a74c3a339d3652c62454ee41b452 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:40:03 -0400 Subject: [PATCH] fix(deployment): quote ConfigMap values with %q, as the go agents already do MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A rendered ConfigMap value is an arbitrary string — a JSON document, a comma-joined list, a sentence. This template wrapped it in hand-written quotes, so the first " inside a value closed the string early and the rest of it became YAML. The render then refused its own output: configmap.yaml: yaml: line 6: did not find expected key and deleted the tree it had just written, leaving nothing to read, which is why it took a template comparison rather than a log to find. service-go and service-go-grpc already render `{{ printf "%q" $value }}`; only this agent and service-nextjs did not, so the same defect is open there and is being fixed separately. The secret template is deliberately untouched: a Secret's `data` holds base64, which contains nothing that breaks naive quoting, and the go agents quote it the same way. The test executes the template directly rather than through the agent's deployment path, because the fault is the template's quoting and nothing above it — a test needing core's deployment wrapper to reach this line would not say where the fault is. It fails on the old template with the same "not parseable YAML" symptom seen in production, and the assertion is that the document parses AND each value survives byte for byte. Found rendering platform-obin's staging deployment for the Lane 3 Catalogue; the module that hit it is a python-fastapi service whose configuration carries a JSON value. Co-Authored-By: Claude Opus 5 (1M context) --- configmap_quoting_test.go | 56 +++++++++++++++++++ .../overlays/environment/configmap.yaml.tmpl | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 configmap_quoting_test.go diff --git a/configmap_quoting_test.go b/configmap_quoting_test.go new file mode 100644 index 0000000..7e2017b --- /dev/null +++ b/configmap_quoting_test.go @@ -0,0 +1,56 @@ +package main + +import ( + "strings" + "testing" + "text/template" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +// A ConfigMap value is an arbitrary string: a JSON document, a comma-joined +// list, a sentence. This template used to wrap it in hand-written quotes, so +// the first `"` inside a value closed the string early and the remainder became +// YAML — which is how a render refused its own output with +// +// configmap.yaml: yaml: line 6: did not find expected key +// +// and then deleted the tree it had just written, leaving nothing to read. The +// sibling go agents already rendered `printf "%q"`; only this one did not. +// +// The template is executed directly rather than through the agent's deployment +// path, because the defect is in the template's quoting and nothing above it: +// a test that needs core's deployment wrapper to reach this line would not say +// where the fault is. +func TestConfigMapQuotesValuesThatContainYAMLSyntax(t *testing.T) { + hostile := map[string]string{ + "MODULE_PRINCIPALS": `{"documents":{"scopes":["read","write"]}}`, + "COLON_AND_SPACE": "key: value", + "TRAILING_BACKSLASH": `C:\path\`, + "NEWLINE": "first\nsecond", + "QUOTED_WORD": `say "hello"`, + "HASH": "value # not a comment", + "EMPTY": "", + } + raw, err := deploymentFS.ReadFile("templates/deployment/kustomize/overlays/environment/configmap.yaml.tmpl") + require.NoError(t, err) + parsed, err := template.New("configmap").Parse(string(raw)) + require.NoError(t, err) + + var out strings.Builder + require.NoError(t, parsed.Execute(&out, map[string]any{ + "Service": map[string]any{"Name": map[string]any{"DNSCase": "model"}}, + "Namespace": "platform-obin-model", + "Deployment": map[string]any{"ConfigMap": hostile}, + })) + + var document struct { + Data map[string]string `yaml:"data"` + } + require.NoError(t, yaml.Unmarshal([]byte(out.String()), &document), + "the rendered configmap is not parseable YAML:\n%s", out.String()) + for key, want := range hostile { + require.Equal(t, want, document.Data[key], "value for %s did not survive rendering", key) + } +} diff --git a/templates/deployment/kustomize/overlays/environment/configmap.yaml.tmpl b/templates/deployment/kustomize/overlays/environment/configmap.yaml.tmpl index b409cb6..00675f5 100644 --- a/templates/deployment/kustomize/overlays/environment/configmap.yaml.tmpl +++ b/templates/deployment/kustomize/overlays/environment/configmap.yaml.tmpl @@ -5,5 +5,5 @@ metadata: namespace: "{{ .Namespace }}" data: {{- range $key, $value := .Deployment.ConfigMap }} - {{$key}}: "{{$value}}" + {{$key}}: {{ printf "%q" $value }} {{- end }}