From 47de42cd793e882ba3ed1f52ff2f4546eb661d1c Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 22 Sep 2026 15:59:25 -0400 Subject: [PATCH] fix: recover config from unparseable ENV file entries An ENV file holding an entry the parser rejects made every config read for that app fail outright, so the existing recovery that drops invalid keys and rewrites the file never had a chance to run. This went unnoticed because two environment test fixtures had gone stale against parser leniency that no longer exists, and the module manifest was out of date enough that its tests refused to run at all. --- plugins/config/environment.go | 35 +++++++++++++++++++++++++----- plugins/config/environment_test.go | 6 +++-- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/plugins/config/environment.go b/plugins/config/environment.go index 0f6131250..bb4f6166f 100644 --- a/plugins/config/environment.go +++ b/plugins/config/environment.go @@ -396,20 +396,28 @@ func prettyPrintEnvEntries(prefix string, entries map[string]string) string { func loadFromFile(name string, filename string) (env *Env, err error) { envMap := make(map[string]string) + dirty := false if filename == "-" { envMap, err = godotenv.Parse(os.Stdin) if err != nil { return nil, err } } - if _, err := os.Stat(filename); err == nil { - envMap, err = godotenv.Read(filename) - if err != nil { - return nil, err + if _, statErr := os.Stat(filename); statErr == nil { + contents, readErr := os.ReadFile(filename) + if readErr != nil { + return nil, readErr } + + parsed, parseErr := godotenv.Unmarshal(string(contents)) + if parseErr != nil { + common.LogInfo1(fmt.Sprintf("Discarding unparseable entries from config for %s: %s", name, parseErr)) + parsed = parseLineByLine(string(contents)) + dirty = true + } + envMap = parsed } - dirty := false for k := range envMap { if err := validateKey(k); err != nil { common.LogInfo1(fmt.Sprintf("Deleting invalid key %s from config for %s", k, name)) @@ -431,6 +439,23 @@ func loadFromFile(name string, filename string) (env *Env, err error) { return } +// parseLineByLine parses ENVFILE contents a line at a time, discarding the +// lines the parser rejects, so that a single malformed entry does not make the +// rest of the environment unreadable +func parseLineByLine(contents string) map[string]string { + envMap := make(map[string]string) + for _, line := range strings.Split(contents, "\n") { + lineMap, err := godotenv.Unmarshal(line) + if err != nil { + continue + } + for key, value := range lineMap { + envMap[key] = value + } + } + return envMap +} + func loadFromFileJSON(name string, filename string) (env *Env, err error) { envMap := make(map[string]string) if filename == "-" { diff --git a/plugins/config/environment_test.go b/plugins/config/environment_test.go index 61f55bf85..fd1dd2a22 100644 --- a/plugins/config/environment_test.go +++ b/plugins/config/environment_test.go @@ -46,14 +46,16 @@ func TestMerge(t *testing.T) { e.Merge(e2) Expect(e.Map()).To(Equal(pairs("BAR", "baz", "FOO", "bar"))) - e3, _ := newEnvFromString("FOO='ba \\nz'") + e3, err := newEnvFromString("FOO=\"ba \\nz\"") + Expect(err).NotTo(HaveOccurred()) e.Merge(e3) Expect(e.Map()).To(Equal(pairs("BAR", "baz", "FOO", "ba \nz"))) } func TestExport(t *testing.T) { RegisterTestingT(t) - e, _ := newEnvFromString("BAR='BAZ'\nFOO='b'ar '\nBAZ='a\\nb'") + e, err := newEnvFromString("BAR='BAZ'\nFOO=\"b'ar \"\nBAZ=\"a\\nb\"") + Expect(err).NotTo(HaveOccurred()) Expect(e.Export(ExportFormatEnvfile)).To(Equal("BAR=\"BAZ\"\nBAZ=\"a\\nb\"\nFOO=\"b'ar \"")) Expect(e.Export(ExportFormatDockerArgs)).To(Equal("--env=BAR='BAZ' --env=BAZ='a\nb' --env=FOO='b'\\''ar '")) Expect(e.Export(ExportFormatDockerArgsKeys)).To(Equal("--env=BAR --env=BAZ --env=FOO"))