From ecec65010c0714dbb506fc62397473a5f26da1ca Mon Sep 17 00:00:00 2001 From: ChrisJr404 <11917633+ChrisJr404@users.noreply.github.com> Date: Mon, 4 May 2026 08:23:50 -0400 Subject: [PATCH] fix: Avoid warnings in 'terraform output -json' (#38512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #38512. The same backend deprecation warnings that #38487 suppressed for 'terraform output -raw' also pollute stdout with 'terraform output -json', breaking 'terraform output -json | jq ...' pipelines. The JSON formatter routes diagnostics through the same writer the human formatter uses, which causes warnings to land on stdout alongside the JSON value. Apply the same fix used for the raw formatter to the JSON formatter: filter to ErrorsOnly() before forwarding to view.Diagnostics. The new helper added in #38487 is reused as-is. Adds TestOutputJSON_warningsSuppressed mirroring the existing TestOutputRaw_warningsSuppressed. The new test reuses the output-backend-with-deprecation fixture from #38487 and asserts: - exit code 0 - 'deprecated' does not appear on stdout (the parseable-JSON gate) - 'deprecated' does not appear on stderr in this case - stdout contains the value '"bar"' parseable as JSON Mechanical, additive change — no schema or behavior change for any caller that doesn't currently produce backend deprecation warnings, and pipelines like 'terraform output -json | jq' stop breaking on backends that do. --- .changes/v1.16/BUG FIXES-20260504-160000.yaml | 5 ++ internal/command/output_test.go | 74 +++++++++++++++++++ internal/command/views/output.go | 8 +- 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 .changes/v1.16/BUG FIXES-20260504-160000.yaml diff --git a/.changes/v1.16/BUG FIXES-20260504-160000.yaml b/.changes/v1.16/BUG FIXES-20260504-160000.yaml new file mode 100644 index 000000000000..dc083a970a01 --- /dev/null +++ b/.changes/v1.16/BUG FIXES-20260504-160000.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: Avoid warnings on stdout in 'terraform output -json' so the result remains parseable JSON +time: 2026-05-04T16:00:00.000000+00:00 +custom: + Issue: "38512" diff --git a/internal/command/output_test.go b/internal/command/output_test.go index 3b0666bbd8fb..5ab5f9e831db 100644 --- a/internal/command/output_test.go +++ b/internal/command/output_test.go @@ -463,3 +463,77 @@ func TestOutputRaw_warningsSuppressed(t *testing.T) { t.Fatalf("expected output \"bar\", got: %#v", actual) } } + +func TestOutputJSON_warningsSuppressed(t *testing.T) { + // Pre-populate the inmem backend with a state containing an output value + inmem.Reset() + originalState := states.BuildState(func(s *states.SyncState) { + s.SetOutputValue( + addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance), + cty.StringVal("bar"), + false, + ) + }) + + // Register a backend that wraps inmem with a deprecation warning, + // simulating a backend like S3 whose PrepareConfig warns about + // deprecated attributes (e.g. dynamodb_table). The same fixture + // powering TestOutputRaw_warningsSuppressed also reproduces the + // `-json` issue reported in #38512. + backendInit.Set("inmem", func() backend.Backend { + return &deprecatedInmemBackend{Backend: inmem.New()} + }) + defer backendInit.Set("inmem", inmem.New) + + td := t.TempDir() + testCopyDir(t, testFixturePath("output-backend-with-deprecation"), td) + t.Chdir(td) + + // Write the state into the inmem backend's default workspace + b := inmem.New() + b.Configure(cty.ObjectVal(map[string]cty.Value{ + "lock_id": cty.NullVal(cty.String), + })) + sMgr, sDiags := b.StateMgr(backend.DefaultStateName) + if sDiags.HasErrors() { + t.Fatalf("unexpected error: %s", sDiags.Err()) + } + sMgr.WriteState(originalState) + if err := sMgr.PersistState(nil); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + view, done := testView(t) + c := &OutputCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + View: view, + }, + } + + args := []string{"-json", "foo"} + code := c.Run(args) + output := done(t) + if code != 0 { + t.Fatalf("unexpected exit code %d\nstderr:\n%s", code, output.Stderr()) + } + + // The key assertion: warnings must not appear on stdout because the + // caller is piping the output to `jq` or similar and any non-JSON + // content there breaks that pipeline. They are also not expected on + // stderr in this run because there was no error to surface. + stdout := output.Stdout() + if strings.Contains(stdout, "deprecated") { + t.Fatalf("warnings should be suppressed from stdout in -json mode, got:\n%s", stdout) + } + stderr := output.Stderr() + if strings.Contains(stderr, "deprecated") { + t.Fatalf("warnings should be suppressed in -json mode, got on stderr:\n%s", stderr) + } + + // What remains on stdout must be parseable JSON containing the value. + actual := strings.TrimSpace(stdout) + if actual != `"bar"` { + t.Fatalf(`expected output "\"bar\"", got: %#v`, actual) + } +} diff --git a/internal/command/views/output.go b/internal/command/views/output.go index 9cc683547a33..06620dbef4ae 100644 --- a/internal/command/views/output.go +++ b/internal/command/views/output.go @@ -258,7 +258,13 @@ func (v *OutputJSON) Output(name string, outputs map[string]*states.OutputValue) } func (v *OutputJSON) Diagnostics(diags tfdiags.Diagnostics) { - v.view.Diagnostics(diags) + // filter out warnings as these wouldn't be expected in JSON mode + // either: pipelines like `terraform output -json | jq` cannot + // tolerate non-JSON content on stdout, and warnings typically don't + // influence the exit code so the user cannot expect them in stdout. + // Mirrors the same suppression added for `-raw` in #38487. + errsOnly := diags.ErrorsOnly() + v.view.Diagnostics(errsOnly) } // For text and raw output modes, an empty map of outputs is considered a