command/jsonstate: properly marshal deposed resources (#23027)

* command/jsonstate: properly marshal deposed resources

This PR addresses 2 issues: `show -json` would crash if there was not a
`Current` `states.ResourceInstance` for a given resource, and `deposed`
resource instances were not shown at all.

Fixes #22642
This commit is contained in:
Kristin Laemmert 2019-10-08 13:42:34 -04:00 committed by GitHub
parent 1ee851f256
commit a9da6f0e5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 185 additions and 40 deletions

View File

@ -91,6 +91,9 @@ type resource struct {
// Tainted is true if the resource is tainted in terraform state. // Tainted is true if the resource is tainted in terraform state.
Tainted bool `json:"tainted,omitempty"` Tainted bool `json:"tainted,omitempty"`
// Deposed is set if the resource is deposed in terraform state.
DeposedKey string `json:"deposed_key,omitempty"`
} }
// attributeValues is the JSON representation of the attribute values of the // attributeValues is the JSON representation of the attribute values of the
@ -246,7 +249,7 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
for _, r := range resources { for _, r := range resources {
for k, ri := range r.Instances { for k, ri := range r.Instances {
resource := resource{ current := resource{
Address: r.Addr.String(), Address: r.Addr.String(),
Type: r.Addr.Type, Type: r.Addr.Type,
Name: r.Addr.Name, Name: r.Addr.Name,
@ -255,9 +258,9 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
switch r.Addr.Mode { switch r.Addr.Mode {
case addrs.ManagedResourceMode: case addrs.ManagedResourceMode:
resource.Mode = "managed" current.Mode = "managed"
case addrs.DataResourceMode: case addrs.DataResourceMode:
resource.Mode = "data" current.Mode = "data"
default: default:
return ret, fmt.Errorf("resource %s has an unsupported mode %s", return ret, fmt.Errorf("resource %s has an unsupported mode %s",
r.Addr.String(), r.Addr.String(),
@ -266,7 +269,7 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
} }
if r.EachMode != states.NoEach { if r.EachMode != states.NoEach {
resource.Index = k current.Index = k
} }
schema, _ := schemas.ResourceTypeConfig( schema, _ := schemas.ResourceTypeConfig(
@ -274,7 +277,10 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
r.Addr.Mode, r.Addr.Mode,
r.Addr.Type, r.Addr.Type,
) )
resource.SchemaVersion = ri.Current.SchemaVersion
// It is possible that the only instance is deposed
if ri.Current != nil {
current.SchemaVersion = ri.Current.SchemaVersion
if schema == nil { if schema == nil {
return nil, fmt.Errorf("no schema found for %s", r.Addr.String()) return nil, fmt.Errorf("no schema found for %s", r.Addr.String())
@ -284,23 +290,55 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
return nil, err return nil, err
} }
resource.AttributeValues = marshalAttributeValues(riObj.Value, schema) current.AttributeValues = marshalAttributeValues(riObj.Value, schema)
if len(riObj.Dependencies) > 0 { if len(riObj.Dependencies) > 0 {
dependencies := make([]string, len(riObj.Dependencies)) dependencies := make([]string, len(riObj.Dependencies))
for i, v := range riObj.Dependencies { for i, v := range riObj.Dependencies {
dependencies[i] = v.String() dependencies[i] = v.String()
} }
resource.DependsOn = dependencies current.DependsOn = dependencies
} }
if riObj.Status == states.ObjectTainted { if riObj.Status == states.ObjectTainted {
resource.Tainted = true current.Tainted = true
}
ret = append(ret, current)
} }
ret = append(ret, resource) for deposedKey, rios := range ri.Deposed {
// copy the base fields from the current instance
deposed := resource{
Address: current.Address,
Type: current.Type,
Name: current.Name,
ProviderName: current.ProviderName,
Mode: current.Mode,
Index: current.Index,
} }
riObj, err := rios.Decode(schema.ImpliedType())
if err != nil {
return nil, err
}
deposed.AttributeValues = marshalAttributeValues(riObj.Value, schema)
if len(riObj.Dependencies) > 0 {
dependencies := make([]string, len(riObj.Dependencies))
for i, v := range riObj.Dependencies {
dependencies[i] = v.String()
}
deposed.DependsOn = dependencies
}
if riObj.Status == states.ObjectTainted {
deposed.Tainted = true
}
deposed.DeposedKey = deposedKey.String()
ret = append(ret, deposed)
}
}
} }
sort.Slice(ret, func(i, j int) bool { sort.Slice(ret, func(i, j int) bool {

View File

@ -158,19 +158,20 @@ func TestMarshalAttributeValues(t *testing.T) {
} }
func TestMarshalResources(t *testing.T) { func TestMarshalResources(t *testing.T) {
tests := []struct { deposedKey := states.NewDeposedKey()
tests := map[string]struct {
Resources map[string]*states.Resource Resources map[string]*states.Resource
Schemas *terraform.Schemas Schemas *terraform.Schemas
Want []resource Want []resource
Err bool Err bool
}{ }{
{ "nil": {
nil, nil,
nil, nil,
nil, nil,
false, false,
}, },
{ "single resource": {
map[string]*states.Resource{ map[string]*states.Resource{
"test_thing.baz": { "test_thing.baz": {
Addr: addrs.Resource{ Addr: addrs.Resource{
@ -211,9 +212,114 @@ func TestMarshalResources(t *testing.T) {
}, },
false, false,
}, },
"deposed resource": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
EachMode: states.EachList,
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.IntKey(0): {
Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{
states.DeposedKey(deposedKey): &states.ResourceInstanceObjectSrc{
SchemaVersion: 1,
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
},
ProviderConfig: addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
},
},
testSchemas(),
[]resource{
resource{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: addrs.IntKey(0),
ProviderName: "test",
DeposedKey: deposedKey.String(),
AttributeValues: attributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
},
},
false,
},
"deposed and current resource": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
EachMode: states.EachList,
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.IntKey(0): {
Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{
states.DeposedKey(deposedKey): &states.ResourceInstanceObjectSrc{
SchemaVersion: 1,
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
Current: &states.ResourceInstanceObjectSrc{
SchemaVersion: 1,
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
ProviderConfig: addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
},
},
testSchemas(),
[]resource{
resource{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: addrs.IntKey(0),
ProviderName: "test",
SchemaVersion: 1,
AttributeValues: attributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
},
resource{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: addrs.IntKey(0),
ProviderName: "test",
DeposedKey: deposedKey.String(),
AttributeValues: attributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
},
},
false,
},
} }
for _, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, err := marshalResources(test.Resources, test.Schemas) got, err := marshalResources(test.Resources, test.Schemas)
if test.Err { if test.Err {
if err == nil { if err == nil {
@ -227,6 +333,7 @@ func TestMarshalResources(t *testing.T) {
if !eq { if !eq {
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want) t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
} }
})
} }
} }