Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

json-output: Add output type to JSON format #30945

Merged
merged 1 commit into from Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/command/jsonplan/plan.go
Expand Up @@ -106,6 +106,7 @@ type change struct {

type output struct {
Sensitive bool `json:"sensitive"`
Type json.RawMessage `json:"type,omitempty"`
Value json.RawMessage `json:"value,omitempty"`
}

Expand Down
10 changes: 8 additions & 2 deletions internal/command/jsonplan/values.go
Expand Up @@ -57,7 +57,7 @@ func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
continue
}

var after []byte
var after, afterType []byte
changeV, err := oc.Decode()
if err != nil {
return ret, err
Expand All @@ -68,14 +68,20 @@ func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
changeV.After, _ = changeV.After.UnmarkDeep()

if changeV.After != cty.NilVal && changeV.After.IsWhollyKnown() {
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
ty := changeV.After.Type()
after, err = ctyjson.Marshal(changeV.After, ty)
if err != nil {
return ret, err
}
afterType, err = ctyjson.MarshalType(ty)
if err != nil {
return ret, err
}
}

ret[oc.Addr.OutputValue.Name] = output{
Value: json.RawMessage(after),
Type: json.RawMessage(afterType),
Sensitive: oc.Sensitive,
}
}
Expand Down
1 change: 1 addition & 0 deletions internal/command/jsonplan/values_test.go
Expand Up @@ -137,6 +137,7 @@ func TestMarshalPlannedOutputs(t *testing.T) {
map[string]output{
"bar": {
Sensitive: false,
Type: json.RawMessage(`"string"`),
Value: json.RawMessage(`"after"`),
},
},
Expand Down
9 changes: 8 additions & 1 deletion internal/command/jsonstate/state.go
Expand Up @@ -38,6 +38,7 @@ type stateValues struct {
type output struct {
Sensitive bool `json:"sensitive"`
Value json.RawMessage `json:"value,omitempty"`
Type json.RawMessage `json:"type,omitempty"`
}

// module is the representation of a module in state. This can be the root module
Expand Down Expand Up @@ -180,12 +181,18 @@ func marshalOutputs(outputs map[string]*states.OutputValue) (map[string]output,

ret := make(map[string]output)
for k, v := range outputs {
ov, err := ctyjson.Marshal(v.Value, v.Value.Type())
ty := v.Value.Type()
ov, err := ctyjson.Marshal(v.Value, ty)
if err != nil {
return ret, err
}
ot, err := ctyjson.MarshalType(ty)
if err != nil {
return ret, err
}
ret[k] = output{
Value: ov,
Type: ot,
Sensitive: v.Sensitive,
}
}
Expand Down
40 changes: 36 additions & 4 deletions internal/command/jsonstate/state_test.go
Expand Up @@ -36,6 +36,7 @@ func TestMarshalOutputs(t *testing.T) {
"test": {
Sensitive: true,
Value: json.RawMessage(`"sekret"`),
Type: json.RawMessage(`"string"`),
},
},
false,
Expand All @@ -51,6 +52,39 @@ func TestMarshalOutputs(t *testing.T) {
"test": {
Sensitive: false,
Value: json.RawMessage(`"not_so_sekret"`),
Type: json.RawMessage(`"string"`),
},
},
false,
},
{
map[string]*states.OutputValue{
"mapstring": {
Sensitive: false,
Value: cty.MapVal(map[string]cty.Value{
"beep": cty.StringVal("boop"),
}),
},
"setnumber": {
Sensitive: false,
Value: cty.SetVal([]cty.Value{
cty.NumberIntVal(3),
cty.NumberIntVal(5),
cty.NumberIntVal(7),
cty.NumberIntVal(11),
}),
},
},
map[string]output{
"mapstring": {
Sensitive: false,
Value: json.RawMessage(`{"beep":"boop"}`),
Type: json.RawMessage(`["map","string"]`),
},
"setnumber": {
Sensitive: false,
Value: json.RawMessage(`[3,5,7,11]`),
Type: json.RawMessage(`["set","number"]`),
},
},
false,
Expand All @@ -67,10 +101,8 @@ func TestMarshalOutputs(t *testing.T) {
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
eq := reflect.DeepEqual(got, test.Want)
if !eq {
// printing the output isn't terribly useful, but it does help indicate which test case failed
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
if !cmp.Equal(test.Want, got) {
t.Fatalf("wrong result:\n%s", cmp.Diff(test.Want, got))
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions internal/command/testdata/show-json-sensitive/output.json
Expand Up @@ -9,6 +9,7 @@
"outputs": {
"test": {
"sensitive": true,
"type": "string",
"value": "bar"
}
},
Expand Down Expand Up @@ -71,6 +72,7 @@
"outputs": {
"test": {
"sensitive": true,
"type": "string",
"value": "bar"
}
},
Expand Down
Expand Up @@ -5,6 +5,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "baz"
}
},
Expand Down
2 changes: 2 additions & 0 deletions internal/command/testdata/show-json/basic-create/output.json
Expand Up @@ -9,6 +9,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down Expand Up @@ -62,6 +63,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down
2 changes: 2 additions & 0 deletions internal/command/testdata/show-json/basic-delete/output.json
Expand Up @@ -9,6 +9,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down Expand Up @@ -94,6 +95,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down
2 changes: 2 additions & 0 deletions internal/command/testdata/show-json/basic-update/output.json
Expand Up @@ -9,6 +9,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down Expand Up @@ -73,6 +74,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down
Expand Up @@ -13,6 +13,7 @@
"outputs": {
"foo_id": {
"sensitive": false,
"type": "string",
"value": "placeholder"
}
},
Expand All @@ -37,6 +38,7 @@
"outputs": {
"foo_id": {
"sensitive": false,
"type": "string",
"value": "placeholder"
}
},
Expand Down
2 changes: 2 additions & 0 deletions internal/command/testdata/show-json/modules/output.json
Expand Up @@ -4,6 +4,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "baz"
}
},
Expand Down Expand Up @@ -79,6 +80,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "baz"
}
},
Expand Down
Expand Up @@ -10,6 +10,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down Expand Up @@ -113,6 +114,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down
Expand Up @@ -9,6 +9,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down Expand Up @@ -62,6 +63,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down
Expand Up @@ -9,6 +9,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down Expand Up @@ -62,6 +63,7 @@
"outputs": {
"test": {
"sensitive": false,
"type": "string",
"value": "bar"
}
},
Expand Down
Expand Up @@ -9,6 +9,7 @@
"outputs": {
"test": {
"sensitive": true,
"type": "string",
"value": "boop"
}
},
Expand Down Expand Up @@ -74,6 +75,7 @@
"outputs": {
"test": {
"sensitive": true,
"type": "string",
"value": "boop"
}
},
Expand Down
3 changes: 3 additions & 0 deletions website/docs/internals/json-format.mdx
Expand Up @@ -217,6 +217,7 @@ The following example illustrates the structure of a `<values-representation>`:
"outputs": {
"private_ip": {
"value": "192.168.3.2",
"type": "string",
"sensitive": false
}
},
Expand Down Expand Up @@ -307,6 +308,8 @@ The following example illustrates the structure of a `<values-representation>`:

The translation of attribute and output values is the same intuitive mapping from HCL types to JSON types used by Terraform's [`jsonencode`](/language/functions/jsonencode) function. This mapping does lose some information: lists, sets, and tuples all lower to JSON arrays while maps and objects both lower to JSON objects. Unknown values and null values are both treated as absent or null.

Output values include a `"type"` field, which is a [serialization of the value's type](https://pkg.go.dev/github.com/zclconf/go-cty/cty#Type.MarshalJSON). For primitive types this is a string value, such as `"number"` or `"bool"`. Complex types are represented as a nested JSON array, such as `["map","string"]` or `["object",{"a":"number"}]`. This can be used to reconstruct the output value with the correct type.

Only the "current" object for each resource instance is described. "Deposed" objects are not reflected in this structure at all; in plan representations, you can refer to the change representations for further details.

The intent of this structure is to give a caller access to a similar level of detail as is available to expressions within the configuration itself. This common representation is not suitable for all use-cases because it loses information compared to the data structures it is built from. For more complex needs, use the more elaborate changes and configuration representations.
Expand Down