Skip to content

Commit

Permalink
Merge pull request #30945 from hashicorp/alisdair/jsonstate-output-type
Browse files Browse the repository at this point in the history
json-output: Add output type to JSON format
  • Loading branch information
alisdair committed Apr 27, 2022
2 parents e217c0c + 12c8f94 commit b02867b
Show file tree
Hide file tree
Showing 17 changed files with 78 additions and 7 deletions.
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

0 comments on commit b02867b

Please sign in to comment.