Skip to content

Commit 80e87d0

Browse files
committed
json-output: Add output type to JSON format
Previously the supported JSON plan and state formats included only serialized output values, which was a lossy serialization of the Terraform type system. This commit adds a type field in the usual cty JSON format, which allows reconstitution of the original value. For example, previously a list(string) and a set(string) containing the same values were indistinguishable. This change serializes these as follows: { "value": ["a","b","c"], "type": ["list","string"] } and: { "value": ["a","b","c"], "type": ["set","string"] }
1 parent e217c0c commit 80e87d0

File tree

17 files changed

+78
-7
lines changed

17 files changed

+78
-7
lines changed

internal/command/jsonplan/plan.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ type change struct {
106106

107107
type output struct {
108108
Sensitive bool `json:"sensitive"`
109+
Type json.RawMessage `json:"type,omitempty"`
109110
Value json.RawMessage `json:"value,omitempty"`
110111
}
111112

internal/command/jsonplan/values.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) {
5757
continue
5858
}
5959

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

7070
if changeV.After != cty.NilVal && changeV.After.IsWhollyKnown() {
71-
after, err = ctyjson.Marshal(changeV.After, changeV.After.Type())
71+
ty := changeV.After.Type()
72+
after, err = ctyjson.Marshal(changeV.After, ty)
73+
if err != nil {
74+
return ret, err
75+
}
76+
afterType, err = ctyjson.MarshalType(ty)
7277
if err != nil {
7378
return ret, err
7479
}
7580
}
7681

7782
ret[oc.Addr.OutputValue.Name] = output{
7883
Value: json.RawMessage(after),
84+
Type: json.RawMessage(afterType),
7985
Sensitive: oc.Sensitive,
8086
}
8187
}

internal/command/jsonplan/values_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func TestMarshalPlannedOutputs(t *testing.T) {
137137
map[string]output{
138138
"bar": {
139139
Sensitive: false,
140+
Type: json.RawMessage(`"string"`),
140141
Value: json.RawMessage(`"after"`),
141142
},
142143
},

internal/command/jsonstate/state.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type stateValues struct {
3838
type output struct {
3939
Sensitive bool `json:"sensitive"`
4040
Value json.RawMessage `json:"value,omitempty"`
41+
Type json.RawMessage `json:"type,omitempty"`
4142
}
4243

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

181182
ret := make(map[string]output)
182183
for k, v := range outputs {
183-
ov, err := ctyjson.Marshal(v.Value, v.Value.Type())
184+
ty := v.Value.Type()
185+
ov, err := ctyjson.Marshal(v.Value, ty)
186+
if err != nil {
187+
return ret, err
188+
}
189+
ot, err := ctyjson.MarshalType(ty)
184190
if err != nil {
185191
return ret, err
186192
}
187193
ret[k] = output{
188194
Value: ov,
195+
Type: ot,
189196
Sensitive: v.Sensitive,
190197
}
191198
}

internal/command/jsonstate/state_test.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func TestMarshalOutputs(t *testing.T) {
3636
"test": {
3737
Sensitive: true,
3838
Value: json.RawMessage(`"sekret"`),
39+
Type: json.RawMessage(`"string"`),
3940
},
4041
},
4142
false,
@@ -51,6 +52,39 @@ func TestMarshalOutputs(t *testing.T) {
5152
"test": {
5253
Sensitive: false,
5354
Value: json.RawMessage(`"not_so_sekret"`),
55+
Type: json.RawMessage(`"string"`),
56+
},
57+
},
58+
false,
59+
},
60+
{
61+
map[string]*states.OutputValue{
62+
"mapstring": {
63+
Sensitive: false,
64+
Value: cty.MapVal(map[string]cty.Value{
65+
"beep": cty.StringVal("boop"),
66+
}),
67+
},
68+
"setnumber": {
69+
Sensitive: false,
70+
Value: cty.SetVal([]cty.Value{
71+
cty.NumberIntVal(3),
72+
cty.NumberIntVal(5),
73+
cty.NumberIntVal(7),
74+
cty.NumberIntVal(11),
75+
}),
76+
},
77+
},
78+
map[string]output{
79+
"mapstring": {
80+
Sensitive: false,
81+
Value: json.RawMessage(`{"beep":"boop"}`),
82+
Type: json.RawMessage(`["map","string"]`),
83+
},
84+
"setnumber": {
85+
Sensitive: false,
86+
Value: json.RawMessage(`[3,5,7,11]`),
87+
Type: json.RawMessage(`["set","number"]`),
5488
},
5589
},
5690
false,
@@ -67,10 +101,8 @@ func TestMarshalOutputs(t *testing.T) {
67101
} else if err != nil {
68102
t.Fatalf("unexpected error: %s", err)
69103
}
70-
eq := reflect.DeepEqual(got, test.Want)
71-
if !eq {
72-
// printing the output isn't terribly useful, but it does help indicate which test case failed
73-
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
104+
if !cmp.Equal(test.Want, got) {
105+
t.Fatalf("wrong result:\n%s", cmp.Diff(test.Want, got))
74106
}
75107
}
76108
}

internal/command/testdata/show-json-sensitive/output.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"outputs": {
1010
"test": {
1111
"sensitive": true,
12+
"type": "string",
1213
"value": "bar"
1314
}
1415
},
@@ -71,6 +72,7 @@
7172
"outputs": {
7273
"test": {
7374
"sensitive": true,
75+
"type": "string",
7476
"value": "bar"
7577
}
7678
},

internal/command/testdata/show-json-state/modules/output.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"outputs": {
66
"test": {
77
"sensitive": false,
8+
"type": "string",
89
"value": "baz"
910
}
1011
},

internal/command/testdata/show-json/basic-create/output.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"outputs": {
1010
"test": {
1111
"sensitive": false,
12+
"type": "string",
1213
"value": "bar"
1314
}
1415
},
@@ -62,6 +63,7 @@
6263
"outputs": {
6364
"test": {
6465
"sensitive": false,
66+
"type": "string",
6567
"value": "bar"
6668
}
6769
},

internal/command/testdata/show-json/basic-delete/output.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"outputs": {
1010
"test": {
1111
"sensitive": false,
12+
"type": "string",
1213
"value": "bar"
1314
}
1415
},
@@ -94,6 +95,7 @@
9495
"outputs": {
9596
"test": {
9697
"sensitive": false,
98+
"type": "string",
9799
"value": "bar"
98100
}
99101
},

internal/command/testdata/show-json/basic-update/output.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"outputs": {
1010
"test": {
1111
"sensitive": false,
12+
"type": "string",
1213
"value": "bar"
1314
}
1415
},
@@ -73,6 +74,7 @@
7374
"outputs": {
7475
"test": {
7576
"sensitive": false,
77+
"type": "string",
7678
"value": "bar"
7779
}
7880
},

0 commit comments

Comments
 (0)