From a9da6f0e5b197e0d3dd6b6d35c2a6792220e0101 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Tue, 8 Oct 2019 13:42:34 -0400 Subject: [PATCH] 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 --- command/jsonstate/state.go | 86 ++++++++++++++------ command/jsonstate/state_test.go | 139 ++++++++++++++++++++++++++++---- 2 files changed, 185 insertions(+), 40 deletions(-) diff --git a/command/jsonstate/state.go b/command/jsonstate/state.go index 68ed520a0..1923c6468 100644 --- a/command/jsonstate/state.go +++ b/command/jsonstate/state.go @@ -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 { diff --git a/command/jsonstate/state_test.go b/command/jsonstate/state_test.go index ee7410416..f03dd2d45 100644 --- a/command/jsonstate/state_test.go +++ b/command/jsonstate/state_test.go @@ -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) + } + }) } }