From 48d64eabb24fcf632d2ea74411203097e5e4f6a4 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Mon, 13 Jun 2022 13:56:50 -0400 Subject: [PATCH] json-output: Extended detail for unknown outputs Planned output changes are represented in the JSON output format using the same change object as planned resource changes. This structure includes an `after` value and a parallel `after_unknown` value, which can be combined to determine which specific parts of a value are known only at apply time. Previously, structured output values would be marked in the JSON plan as coarsely known or unknown, even if only some subset of the structure will be known only at apply time. This simplification was unnecessary, and this commit reuses the same logic for resource changes to give more information to consumers of this format. For example, consider this output: output "bar" { value = tolist([ "hello", timestamp(), "world", ]) } The plan output for this output would be: + bar = [ + "hello", + (known after apply), + "world", ] For the same plan, the JSON output was previously: "bar": { "actions": [ "create" ], "before": null, "after_unknown": true, "before_sensitive": false, "after_sensitive": false } After this commit, the output is instead: "bar": { "actions": [ "create" ], "before": null, "after": [ "hello", null, "world" ], "after_unknown": [ false, true, false ], "before_sensitive": false, "after_sensitive": false } --- internal/command/jsonplan/plan.go | 15 ++- .../testdata/show-json/unknown-output/main.tf | 19 ++++ .../show-json/unknown-output/output.json | 96 +++++++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 internal/command/testdata/show-json/unknown-output/main.tf create mode 100644 internal/command/testdata/show-json/unknown-output/output.json diff --git a/internal/command/jsonplan/plan.go b/internal/command/jsonplan/plan.go index 0a4f9bcd6e..b8ff13676b 100644 --- a/internal/command/jsonplan/plan.go +++ b/internal/command/jsonplan/plan.go @@ -442,7 +442,8 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error { changeV.After, _ = changeV.After.UnmarkDeep() var before, after []byte - afterUnknown := cty.False + var afterUnknown cty.Value + if changeV.Before != cty.NilVal { before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type()) if err != nil { @@ -455,8 +456,18 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error { if err != nil { return err } + afterUnknown = cty.False } else { - afterUnknown = cty.True + filteredAfter := omitUnknowns(changeV.After) + if filteredAfter.IsNull() { + after = nil + } else { + after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type()) + if err != nil { + return err + } + } + afterUnknown = unknownAsBool(changeV.After) } } diff --git a/internal/command/testdata/show-json/unknown-output/main.tf b/internal/command/testdata/show-json/unknown-output/main.tf new file mode 100644 index 0000000000..d97891e224 --- /dev/null +++ b/internal/command/testdata/show-json/unknown-output/main.tf @@ -0,0 +1,19 @@ +output "foo" { + value = "hello" +} + +output "bar" { + value = tolist([ + "hello", + timestamp(), + "world", + ]) +} + +output "baz" { + value = { + greeting: "hello", + time: timestamp(), + subject: "world", + } +} diff --git a/internal/command/testdata/show-json/unknown-output/output.json b/internal/command/testdata/show-json/unknown-output/output.json new file mode 100644 index 0000000000..8a52b8dc57 --- /dev/null +++ b/internal/command/testdata/show-json/unknown-output/output.json @@ -0,0 +1,96 @@ +{ + "format_version": "1.1", + "terraform_version": "1.3.0-dev", + "planned_values": { + "outputs": { + "bar": { + "sensitive": false + }, + "baz": { + "sensitive": false + }, + "foo": { + "sensitive": false, + "type": "string", + "value": "hello" + } + }, + "root_module": {} + }, + "output_changes": { + "bar": { + "actions": [ + "create" + ], + "before": null, + "after": [ + "hello", + null, + "world" + ], + "after_unknown": [ + false, + true, + false + ], + "before_sensitive": false, + "after_sensitive": false + }, + "baz": { + "actions": [ + "create" + ], + "before": null, + "after": { + "greeting": "hello", + "subject": "world" + }, + "after_unknown": { + "time": true + }, + "before_sensitive": false, + "after_sensitive": false + }, + "foo": { + "actions": [ + "create" + ], + "before": null, + "after": "hello", + "after_unknown": false, + "before_sensitive": false, + "after_sensitive": false + } + }, + "prior_state": { + "format_version": "1.0", + "terraform_version": "1.3.0", + "values": { + "outputs": { + "foo": { + "sensitive": false, + "value": "hello", + "type": "string" + } + }, + "root_module": {} + } + }, + "configuration": { + "root_module": { + "outputs": { + "bar": { + "expression": {} + }, + "baz": { + "expression": {} + }, + "foo": { + "expression": { + "constant_value": "hello" + } + } + } + } + } +}