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:
parent
1ee851f256
commit
a9da6f0e5b
|
@ -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,33 +277,68 @@ 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
|
|
||||||
|
|
||||||
if schema == nil {
|
// It is possible that the only instance is deposed
|
||||||
return nil, fmt.Errorf("no schema found for %s", r.Addr.String())
|
if ri.Current != nil {
|
||||||
}
|
current.SchemaVersion = ri.Current.SchemaVersion
|
||||||
riObj, err := ri.Current.Decode(schema.ImpliedType())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resource.AttributeValues = marshalAttributeValues(riObj.Value, schema)
|
if schema == nil {
|
||||||
|
return nil, fmt.Errorf("no schema found for %s", r.Addr.String())
|
||||||
if len(riObj.Dependencies) > 0 {
|
|
||||||
dependencies := make([]string, len(riObj.Dependencies))
|
|
||||||
for i, v := range riObj.Dependencies {
|
|
||||||
dependencies[i] = v.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 {
|
for deposedKey, rios := range ri.Deposed {
|
||||||
resource.Tainted = true
|
// 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 {
|
sort.Slice(ret, func(i, j int) bool {
|
||||||
|
|
|
@ -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,22 +212,128 @@ 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 {
|
||||||
got, err := marshalResources(test.Resources, test.Schemas)
|
t.Run(name, func(t *testing.T) {
|
||||||
if test.Err {
|
got, err := marshalResources(test.Resources, test.Schemas)
|
||||||
if err == nil {
|
if test.Err {
|
||||||
t.Fatal("succeeded; want error")
|
if err == nil {
|
||||||
|
t.Fatal("succeeded; want error")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
return
|
eq := reflect.DeepEqual(got, test.Want)
|
||||||
} else if err != nil {
|
if !eq {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue