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

state/plan: Add output type and relevant_attributes #53

Merged
merged 3 commits into from May 25, 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 go.mod
Expand Up @@ -9,4 +9,5 @@ require (
github.com/mitchellh/copystructure v1.2.0
github.com/sebdah/goldie v1.0.0
github.com/zclconf/go-cty v1.10.0
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b
)
10 changes: 10 additions & 0 deletions go.sum
@@ -1,7 +1,9 @@
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
Expand All @@ -12,6 +14,7 @@ github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
Expand All @@ -23,18 +26,25 @@ github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdk
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0=
github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3 changes: 3 additions & 0 deletions parse_test.go
Expand Up @@ -52,6 +52,9 @@ func testParse(t *testing.T, filename string, typ reflect.Type) {
// Add a newline at the end
actual = append(actual, byte('\n'))

// TODO: Compare the actual struct instead of byte slice
// because JSON does not guarantee consistent key ordering

if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("unexpected: %s", diff)
}
Expand Down
13 changes: 13 additions & 0 deletions plan.go
Expand Up @@ -55,6 +55,19 @@ type Plan struct {

// The Terraform configuration used to make the plan.
Config *Config `json:"configuration,omitempty"`

// RelevantAttributes represents any resource instances and their
// attributes which may have contributed to the planned changes
RelevantAttributes []ResourceAttribute `json:"relevant_attributes,omitempty"`
}

// ResourceAttribute describes a full path to a resource attribute
type ResourceAttribute struct {
// Resource describes resource instance address (e.g. null_resource.foo)
Resource string `json:"resource"`
// Attribute describes the attribute path using a lossy representation
// of cty.Path. (e.g. ["id"] or ["objects", 0, "val"]).
Attribute []json.RawMessage `json:"attribute"`
}

// Validate checks to ensure that the plan is present, and the
Expand Down
5 changes: 3 additions & 2 deletions sanitize/sanitize_state_test.go
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/google/go-cmp/cmp"
tfjson "github.com/hashicorp/terraform-json"
"github.com/zclconf/go-cty-debug/ctydebug"
)

type testStateCase struct {
Expand Down Expand Up @@ -245,11 +246,11 @@ func TestSanitizeStateOutputs(t *testing.T) {
t.Fatal(err)
}

if diff := cmp.Diff(tc.expected, actual); diff != "" {
if diff := cmp.Diff(tc.expected, actual, ctydebug.CmpOptions); diff != "" {
t.Errorf("SanitizeStateOutputs() mismatch (-expected +actual):\n%s", diff)
}

if diff := cmp.Diff(outputCases()[i].old, tc.old); diff != "" {
if diff := cmp.Diff(outputCases()[i].old, tc.old, ctydebug.CmpOptions); diff != "" {
t.Errorf("SanitizeStateOutputs() altered original (-expected +actual):\n%s", diff)
}
})
Expand Down
7 changes: 7 additions & 0 deletions schemas.go
Expand Up @@ -223,6 +223,13 @@ type SchemaAttribute struct {
Sensitive bool `json:"sensitive,omitempty"`
}

// jsonSchemaAttribute describes an attribute within a schema block
// in a middle-step internal representation before marshalled into
// a more useful SchemaAttribute with cty.Type.
//
// This avoid panic on marshalling cty.NilType (from cty upstream)
// which the default Go marshaller cannot ignore because it's a
// not nil-able struct.
type jsonSchemaAttribute struct {
AttributeType json.RawMessage `json:"type,omitempty"`
AttributeNestedType *SchemaNestedAttributeType `json:"nested_type,omitempty"`
Expand Down
28 changes: 28 additions & 0 deletions state.go
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"

"github.com/hashicorp/go-version"
"github.com/zclconf/go-cty/cty"
)

// StateFormatVersionConstraints defines the versions of the JSON state format
Expand Down Expand Up @@ -175,4 +176,31 @@ type StateOutput struct {

// The value of the output.
Value interface{} `json:"value,omitempty"`

// The type of the output.
Type cty.Type `json:"type,omitempty"`
}

// jsonStateOutput describes an output value in a middle-step internal
// representation before marshalled into a more useful StateOutput with cty.Type.
//
// This avoid panic on marshalling cty.NilType (from cty upstream)
// which the default Go marshaller cannot ignore because it's a
// not nil-able struct.
type jsonStateOutput struct {
Sensitive bool `json:"sensitive"`
Value interface{} `json:"value,omitempty"`
Type json.RawMessage `json:"type,omitempty"`
}

func (so *StateOutput) MarshalJSON() ([]byte, error) {
jsonSa := &jsonStateOutput{
Sensitive: so.Sensitive,
Value: so.Value,
}
if so.Type != cty.NilType {
outputType, _ := so.Type.MarshalJSON()
jsonSa.Type = outputType
}
return json.Marshal(jsonSa)
}
40 changes: 40 additions & 0 deletions testdata/120_basic/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions testdata/120_basic/foo/main.tf
@@ -0,0 +1,30 @@
terraform {
required_providers {
null = {
source = "hashicorp/null"
configuration_aliases = [null.aliased]
}
}
}

variable "bar" {
type = string
}

variable "one" {
type = string
}

resource "null_resource" "foo" {
triggers = {
foo = "bar"
}
}

resource "null_resource" "aliased" {
provider = null.aliased
}

output "foo" {
value = "bar"
}
10 changes: 10 additions & 0 deletions testdata/120_basic/module.tf
@@ -0,0 +1,10 @@
module "foo" {
source = "./foo"

bar = "baz"
one = "two"

providers = {
null.aliased = null
}
}
52 changes: 52 additions & 0 deletions testdata/120_basic/outputs.tf
@@ -0,0 +1,52 @@
output "foo" {
sensitive = true
value = "bar"
}

output "string" {
value = "foo"
}

output "list" {
value = [
"foo",
"bar",
]
}

output "map" {
value = {
foo = "bar"
number = 42
}
}

output "referenced" {
value = null_resource.foo.id
}

output "interpolated" {
value = "${null_resource.foo.id}"
}

output "referenced_deep" {
value = {
foo = "bar"
number = 42
map = {
bar = "baz"
id = null_resource.foo.id
}
}
}

output "interpolated_deep" {
value = {
foo = "bar"
number = 42
map = {
bar = "baz"
id = "${null_resource.foo.id}"
}
}
}
1 change: 1 addition & 0 deletions testdata/120_basic/plan.json
@@ -0,0 +1 @@
{"format_version":"1.1","terraform_version":"1.2.0-rc1","variables":{"foo":{"value":"bar"},"map":{"value":{"foo":"bar","number":42}},"number":{"value":42}},"planned_values":{"outputs":{"foo":{"sensitive":true,"value":"bar","type":"string"},"interpolated":{"sensitive":false},"interpolated_deep":{"sensitive":false},"list":{"sensitive":false,"value":["foo","bar"],"type":["tuple",["string","string"]]},"map":{"sensitive":false,"value":{"foo":"bar","number":42},"type":["object",{"foo":"string","number":"number"}]},"referenced":{"sensitive":false},"referenced_deep":{"sensitive":false},"string":{"sensitive":false,"value":"foo","type":"string"}},"root_module":{"resources":[{"address":"null_resource.bar","mode":"managed","type":"null_resource","name":"bar","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.baz[0]","mode":"managed","type":"null_resource","name":"baz","index":0,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.baz[1]","mode":"managed","type":"null_resource","name":"baz","index":1,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.baz[2]","mode":"managed","type":"null_resource","name":"baz","index":2,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"triggers":{"foo":"bar"}},"sensitive_values":{"triggers":{}}}],"child_modules":[{"resources":[{"address":"module.foo.null_resource.aliased","mode":"managed","type":"null_resource","name":"aliased","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"triggers":null},"sensitive_values":{}},{"address":"module.foo.null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"triggers":{"foo":"bar"}},"sensitive_values":{"triggers":{}}}],"address":"module.foo"}]}},"resource_changes":[{"address":"module.foo.null_resource.aliased","module_address":"module.foo","mode":"managed","type":"null_resource","name":"aliased","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{"triggers":null},"after_unknown":{"id":true},"before_sensitive":false,"after_sensitive":{}}},{"address":"module.foo.null_resource.foo","module_address":"module.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{"triggers":{"foo":"bar"}},"after_unknown":{"id":true,"triggers":{}},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.bar","mode":"managed","type":"null_resource","name":"bar","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.baz[0]","mode":"managed","type":"null_resource","name":"baz","index":0,"provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.baz[1]","mode":"managed","type":"null_resource","name":"baz","index":1,"provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.baz[2]","mode":"managed","type":"null_resource","name":"baz","index":2,"provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{"triggers":{"foo":"bar"}},"after_unknown":{"id":true,"triggers":{}},"before_sensitive":false,"after_sensitive":{"triggers":{}}}}],"output_changes":{"foo":{"actions":["create"],"before":null,"after":"bar","after_unknown":false,"before_sensitive":true,"after_sensitive":true},"interpolated":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"interpolated_deep":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"list":{"actions":["create"],"before":null,"after":["foo","bar"],"after_unknown":false,"before_sensitive":false,"after_sensitive":false},"map":{"actions":["create"],"before":null,"after":{"foo":"bar","number":42},"after_unknown":false,"before_sensitive":false,"after_sensitive":false},"referenced":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"referenced_deep":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"string":{"actions":["create"],"before":null,"after":"foo","after_unknown":false,"before_sensitive":false,"after_sensitive":false}},"prior_state":{"format_version":"1.0","terraform_version":"1.2.0","values":{"outputs":{"foo":{"sensitive":true,"value":"bar","type":"string"},"list":{"sensitive":false,"value":["foo","bar"],"type":["tuple",["string","string"]]},"map":{"sensitive":false,"value":{"foo":"bar","number":42},"type":["object",{"foo":"string","number":"number"}]},"string":{"sensitive":false,"value":"foo","type":"string"}},"root_module":{}}},"configuration":{"provider_config":{"aws":{"name":"aws","full_name":"registry.terraform.io/hashicorp/aws","expressions":{"region":{"constant_value":"us-west-2"}}},"aws.east":{"name":"aws","full_name":"registry.terraform.io/hashicorp/aws","alias":"east","expressions":{"region":{"constant_value":"us-east-1"}}},"null":{"name":"null","full_name":"registry.terraform.io/hashicorp/null"}},"root_module":{"outputs":{"foo":{"sensitive":true,"expression":{"constant_value":"bar"}},"interpolated":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"interpolated_deep":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"list":{"expression":{"constant_value":["foo","bar"]}},"map":{"expression":{"constant_value":{"foo":"bar","number":42}}},"referenced":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"referenced_deep":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"string":{"expression":{"constant_value":"foo"}}},"resources":[{"address":"null_resource.bar","mode":"managed","type":"null_resource","name":"bar","provider_config_key":"null","expressions":{"triggers":{"references":["null_resource.foo.id","null_resource.foo"]}},"schema_version":0},{"address":"null_resource.baz","mode":"managed","type":"null_resource","name":"baz","provider_config_key":"null","expressions":{"triggers":{"references":["null_resource.foo.id","null_resource.foo"]}},"schema_version":0,"count_expression":{"constant_value":3}},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_config_key":"null","provisioners":[{"type":"local-exec","expressions":{"command":{"constant_value":"echo hello"}}}],"expressions":{"triggers":{"constant_value":{"foo":"bar"}}},"schema_version":0}],"module_calls":{"foo":{"source":"./foo","expressions":{"bar":{"constant_value":"baz"},"one":{"constant_value":"two"}},"module":{"outputs":{"foo":{"expression":{"constant_value":"bar"}}},"resources":[{"address":"null_resource.aliased","mode":"managed","type":"null_resource","name":"aliased","provider_config_key":"null","schema_version":0},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_config_key":"null","expressions":{"triggers":{"constant_value":{"foo":"bar"}}},"schema_version":0}],"variables":{"bar":{},"one":{}}}}},"variables":{"foo":{"default":"bar","description":"foobar"},"map":{"default":{"foo":"bar","number":42}},"number":{"default":42}}}},"relevant_attributes":[{"resource":"null_resource.foo","attribute":["id"]}]}