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
    }
This commit is contained in:
Alisdair McDiarmid 2022-06-13 13:56:50 -04:00
parent f6752c6cfa
commit 48d64eabb2
3 changed files with 128 additions and 2 deletions

View File

@ -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)
}
}

View File

@ -0,0 +1,19 @@
output "foo" {
value = "hello"
}
output "bar" {
value = tolist([
"hello",
timestamp(),
"world",
])
}
output "baz" {
value = {
greeting: "hello",
time: timestamp(),
subject: "world",
}
}

View File

@ -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"
}
}
}
}
}
}