From 3449a8aa3558ffd898e713f1b55e50bbef132979 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 23 Feb 2021 10:19:24 -0500 Subject: [PATCH] don't marshal state with the wrong schema Instead of returning an error with no context about unexpected attributes or incorrect types, notify users that the schema stored in the state does not match the current provider. User can only encounter this error if the providers have updated their schemas since the state was stored. This would appears when running `terraform show -json` to display the current state, or `terraform show -json planfile` if that plan was created with `-refresh=false`. In either case, the state must be refreshed in order to properly json encoded. --- command/jsonstate/state.go | 6 +- command/jsonstate/state_test.go | 111 +++++++++++++++++++------------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/command/jsonstate/state.go b/command/jsonstate/state.go index bb5ba6a55..2bb5e157c 100644 --- a/command/jsonstate/state.go +++ b/command/jsonstate/state.go @@ -300,7 +300,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module ) } - schema, _ := schemas.ResourceTypeConfig( + schema, version := schemas.ResourceTypeConfig( r.ProviderConfig.Provider, resAddr.Mode, resAddr.Type, @@ -308,6 +308,10 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module // It is possible that the only instance is deposed if ri.Current != nil { + if version != ri.Current.SchemaVersion { + return nil, fmt.Errorf("schema version %d for %s in state does not match version %d from the provider", ri.Current.SchemaVersion, resAddr, version) + } + current.SchemaVersion = ri.Current.SchemaVersion if schema == nil { diff --git a/command/jsonstate/state_test.go b/command/jsonstate/state_test.go index 683d38378..c4b868101 100644 --- a/command/jsonstate/state_test.go +++ b/command/jsonstate/state_test.go @@ -167,9 +167,8 @@ func TestMarshalResources(t *testing.T) { Instances: map[addrs.InstanceKey]*states.ResourceInstance{ addrs.NoKey: { Current: &states.ResourceInstanceObjectSrc{ - SchemaVersion: 1, - Status: states.ObjectReady, - AttrsJSON: []byte(`{"woozles":"confuzles"}`), + Status: states.ObjectReady, + AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, }, }, @@ -182,13 +181,12 @@ func TestMarshalResources(t *testing.T) { testSchemas(), []resource{ resource{ - Address: "test_thing.bar", - Mode: "managed", - Type: "test_thing", - Name: "bar", - Index: addrs.InstanceKey(nil), - ProviderName: "registry.terraform.io/hashicorp/test", - SchemaVersion: 1, + Address: "test_thing.bar", + Mode: "managed", + Type: "test_thing", + Name: "bar", + Index: addrs.InstanceKey(nil), + ProviderName: "registry.terraform.io/hashicorp/test", AttributeValues: attributeValues{ "foozles": json.RawMessage(`null`), "woozles": json.RawMessage(`"confuzles"`), @@ -197,6 +195,35 @@ func TestMarshalResources(t *testing.T) { }, false, }, + "single resource wrong schema": { + map[string]*states.Resource{ + "test_thing.baz": { + Addr: addrs.AbsResource{ + Resource: addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_thing", + Name: "bar", + }, + }, + Instances: map[addrs.InstanceKey]*states.ResourceInstance{ + addrs.NoKey: { + Current: &states.ResourceInstanceObjectSrc{ + SchemaVersion: 1, + Status: states.ObjectReady, + AttrsJSON: []byte(`{"woozles":["confuzles"]}`), + }, + }, + }, + ProviderConfig: addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + }, + }, + testSchemas(), + nil, + true, + }, "resource with count": { map[string]*states.Resource{ "test_thing.bar": { @@ -210,9 +237,8 @@ func TestMarshalResources(t *testing.T) { Instances: map[addrs.InstanceKey]*states.ResourceInstance{ addrs.IntKey(0): { Current: &states.ResourceInstanceObjectSrc{ - SchemaVersion: 1, - Status: states.ObjectReady, - AttrsJSON: []byte(`{"woozles":"confuzles"}`), + Status: states.ObjectReady, + AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, }, }, @@ -225,13 +251,12 @@ func TestMarshalResources(t *testing.T) { testSchemas(), []resource{ resource{ - Address: "test_thing.bar[0]", - Mode: "managed", - Type: "test_thing", - Name: "bar", - Index: addrs.IntKey(0), - ProviderName: "registry.terraform.io/hashicorp/test", - SchemaVersion: 1, + Address: "test_thing.bar[0]", + Mode: "managed", + Type: "test_thing", + Name: "bar", + Index: addrs.IntKey(0), + ProviderName: "registry.terraform.io/hashicorp/test", AttributeValues: attributeValues{ "foozles": json.RawMessage(`null`), "woozles": json.RawMessage(`"confuzles"`), @@ -253,9 +278,8 @@ func TestMarshalResources(t *testing.T) { Instances: map[addrs.InstanceKey]*states.ResourceInstance{ addrs.StringKey("rockhopper"): { Current: &states.ResourceInstanceObjectSrc{ - SchemaVersion: 1, - Status: states.ObjectReady, - AttrsJSON: []byte(`{"woozles":"confuzles"}`), + Status: states.ObjectReady, + AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, }, }, @@ -268,13 +292,12 @@ func TestMarshalResources(t *testing.T) { testSchemas(), []resource{ resource{ - Address: "test_thing.bar[\"rockhopper\"]", - Mode: "managed", - Type: "test_thing", - Name: "bar", - Index: addrs.StringKey("rockhopper"), - ProviderName: "registry.terraform.io/hashicorp/test", - SchemaVersion: 1, + Address: "test_thing.bar[\"rockhopper\"]", + Mode: "managed", + Type: "test_thing", + Name: "bar", + Index: addrs.StringKey("rockhopper"), + ProviderName: "registry.terraform.io/hashicorp/test", AttributeValues: attributeValues{ "foozles": json.RawMessage(`null`), "woozles": json.RawMessage(`"confuzles"`), @@ -297,9 +320,8 @@ func TestMarshalResources(t *testing.T) { addrs.NoKey: { Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{ states.DeposedKey(deposedKey): &states.ResourceInstanceObjectSrc{ - SchemaVersion: 1, - Status: states.ObjectReady, - AttrsJSON: []byte(`{"woozles":"confuzles"}`), + Status: states.ObjectReady, + AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, }, }, @@ -342,15 +364,13 @@ func TestMarshalResources(t *testing.T) { addrs.NoKey: { Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{ states.DeposedKey(deposedKey): &states.ResourceInstanceObjectSrc{ - SchemaVersion: 1, - Status: states.ObjectReady, - AttrsJSON: []byte(`{"woozles":"confuzles"}`), + Status: states.ObjectReady, + AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, }, Current: &states.ResourceInstanceObjectSrc{ - SchemaVersion: 1, - Status: states.ObjectReady, - AttrsJSON: []byte(`{"woozles":"confuzles"}`), + Status: states.ObjectReady, + AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, }, }, @@ -363,13 +383,12 @@ func TestMarshalResources(t *testing.T) { testSchemas(), []resource{ resource{ - Address: "test_thing.bar", - Mode: "managed", - Type: "test_thing", - Name: "bar", - Index: addrs.InstanceKey(nil), - ProviderName: "registry.terraform.io/hashicorp/test", - SchemaVersion: 1, + Address: "test_thing.bar", + Mode: "managed", + Type: "test_thing", + Name: "bar", + Index: addrs.InstanceKey(nil), + ProviderName: "registry.terraform.io/hashicorp/test", AttributeValues: attributeValues{ "foozles": json.RawMessage(`null`), "woozles": json.RawMessage(`"confuzles"`),