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 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
@ -246,7 +249,7 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
for _, r := range resources {
for k, ri := range r.Instances {
resource := resource{
current := resource{
Address: r.Addr.String(),
Type: r.Addr.Type,
Name: r.Addr.Name,
@ -255,9 +258,9 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
switch r.Addr.Mode {
case addrs.ManagedResourceMode:
resource.Mode = "managed"
current.Mode = "managed"
case addrs.DataResourceMode:
resource.Mode = "data"
current.Mode = "data"
default:
return ret, fmt.Errorf("resource %s has an unsupported mode %s",
r.Addr.String(),
@ -266,7 +269,7 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
}
if r.EachMode != states.NoEach {
resource.Index = k
current.Index = k
}
schema, _ := schemas.ResourceTypeConfig(
@ -274,33 +277,68 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
r.Addr.Mode,
r.Addr.Type,
)
resource.SchemaVersion = ri.Current.SchemaVersion
if schema == nil {
return nil, fmt.Errorf("no schema found for %s", r.Addr.String())
}
riObj, err := ri.Current.Decode(schema.ImpliedType())
if err != nil {
return nil, err
}
// It is possible that the only instance is deposed
if ri.Current != nil {
current.SchemaVersion = ri.Current.SchemaVersion
resource.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()
if schema == nil {
return nil, fmt.Errorf("no schema found for %s", r.Addr.String())
}
resource.DependsOn = dependencies
riObj, err := ri.Current.Decode(schema.ImpliedType())
if err != nil {
return nil, err
}
current.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()
}
current.DependsOn = dependencies
}
if riObj.Status == states.ObjectTainted {
current.Tainted = true
}
ret = append(ret, current)
}
if riObj.Status == states.ObjectTainted {
resource.Tainted = true
}
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,
}
ret = append(ret, resource)
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 {

View File

@ -158,19 +158,20 @@ func TestMarshalAttributeValues(t *testing.T) {
}
func TestMarshalResources(t *testing.T) {
tests := []struct {
deposedKey := states.NewDeposedKey()
tests := map[string]struct {
Resources map[string]*states.Resource
Schemas *terraform.Schemas
Want []resource
Err bool
}{
{
"nil": {
nil,
nil,
nil,
false,
},
{
"single resource": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.Resource{
@ -211,22 +212,128 @@ func TestMarshalResources(t *testing.T) {
},
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 {
got, err := marshalResources(test.Resources, test.Schemas)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, err := marshalResources(test.Resources, test.Schemas)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
eq := reflect.DeepEqual(got, test.Want)
if !eq {
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
}
eq := reflect.DeepEqual(got, test.Want)
if !eq {
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
}
})
}
}