diff --git a/.changes/unreleased/ENHANCEMENTS-20230808-111940.yaml b/.changes/unreleased/ENHANCEMENTS-20230808-111940.yaml new file mode 100644 index 0000000000..f6c4c62e90 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20230808-111940.yaml @@ -0,0 +1,8 @@ +kind: ENHANCEMENTS +body: 'helper/schema: Added `Resource` type `EnableLegacyTypeSystemApplyErrors` + field, which will prevent Terraform from demoting data consistency errors + to warning logs during `ApplyResourceChange` (`Create`, `Update`, and `Delete`) + operations with the resource' +time: 2023-08-08T11:19:40.137598-04:00 +custom: + Issue: "1227" diff --git a/.changes/unreleased/ENHANCEMENTS-20230808-111941.yaml b/.changes/unreleased/ENHANCEMENTS-20230808-111941.yaml new file mode 100644 index 0000000000..127f6a5456 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20230808-111941.yaml @@ -0,0 +1,7 @@ +kind: ENHANCEMENTS +body: 'helper/schema: Added `Resource` type `EnableLegacyTypeSystemPlanErrors` + field, which can be used to prevent Terraform from demoting data consistency + errors to warning logs during `PlanResourceChange` operations with the resource' +time: 2023-08-08T11:19:41.137598-04:00 +custom: + Issue: "1227" diff --git a/.changes/unreleased/NOTES-20230808-103724.yaml b/.changes/unreleased/NOTES-20230808-103724.yaml new file mode 100644 index 0000000000..20bb0bb1b2 --- /dev/null +++ b/.changes/unreleased/NOTES-20230808-103724.yaml @@ -0,0 +1,12 @@ +kind: NOTES +body: 'helper/schema: The `Resource` type `EnableApplyLegacyTypeSystemErrors` + and `EnablePlanLegacyTypeSystemErrors` fields can be enabled to more easily + discover resource data consistency errors which Terraform would normally + demote to warning logs. Before enabling the flag in a production release for + a resource, the resource should be exhaustively acceptance tested as there may + be unrecoverable error situations for practitioners. It is recommended to + first enable and test in environments where it is easy to clean up resources, + potentially outside of Terraform.' +time: 2023-08-08T10:37:24.017324-04:00 +custom: + Issue: "1227" diff --git a/helper/schema/grpc_provider.go b/helper/schema/grpc_provider.go index 831d78777a..aa1759e730 100644 --- a/helper/schema/grpc_provider.go +++ b/helper/schema/grpc_provider.go @@ -662,20 +662,23 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot ctx = logging.InitContext(ctx) resp := &tfprotov5.PlanResourceChangeResponse{} + res, ok := s.provider.ResourcesMap[req.TypeName] + if !ok { + resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf("unknown resource type: %s", req.TypeName)) + return resp, nil + } + schemaBlock := s.getResourceSchemaBlock(req.TypeName) + // This is a signal to Terraform Core that we're doing the best we can to // shim the legacy type system of the SDK onto the Terraform type system // but we need it to cut us some slack. This setting should not be taken // forward to any new SDK implementations, since setting it prevents us // from catching certain classes of provider bug that can lead to // confusing downstream errors. - resp.UnsafeToUseLegacyTypeSystem = true //nolint:staticcheck - - res, ok := s.provider.ResourcesMap[req.TypeName] - if !ok { - resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf("unknown resource type: %s", req.TypeName)) - return resp, nil + if !res.EnableLegacyTypeSystemPlanErrors { + //nolint:staticcheck // explicitly for this SDK + resp.UnsafeToUseLegacyTypeSystem = true } - schemaBlock := s.getResourceSchemaBlock(req.TypeName) priorStateVal, err := msgpack.Unmarshal(req.PriorState.MsgPack, schemaBlock.ImpliedType()) if err != nil { @@ -1075,7 +1078,10 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro // forward to any new SDK implementations, since setting it prevents us // from catching certain classes of provider bug that can lead to // confusing downstream errors. - resp.UnsafeToUseLegacyTypeSystem = true //nolint:staticcheck + if !res.EnableLegacyTypeSystemApplyErrors { + //nolint:staticcheck // explicitly for this SDK + resp.UnsafeToUseLegacyTypeSystem = true + } return resp, nil } diff --git a/helper/schema/grpc_provider_test.go b/helper/schema/grpc_provider_test.go index 82292689da..0c89cc1605 100644 --- a/helper/schema/grpc_provider_test.go +++ b/helper/schema/grpc_provider_test.go @@ -575,77 +575,113 @@ func TestUpgradeState_flatmapStateMissingMigrateState(t *testing.T) { } func TestPlanResourceChange(t *testing.T) { - r := &Resource{ - SchemaVersion: 4, - Schema: map[string]*Schema{ - "foo": { - Type: TypeInt, - Optional: true, + t.Parallel() + + testCases := map[string]struct { + TestResource *Resource + ExpectedUnsafeLegacyTypeSystem bool + }{ + "basic": { + TestResource: &Resource{ + SchemaVersion: 4, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + }, + ExpectedUnsafeLegacyTypeSystem: true, + }, + "EnableLegacyTypeSystemPlanErrors": { + TestResource: &Resource{ + EnableLegacyTypeSystemPlanErrors: true, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, }, + ExpectedUnsafeLegacyTypeSystem: false, }, } - server := NewGRPCProviderServer(&Provider{ - ResourcesMap: map[string]*Resource{ - "test": r, - }, - }) + for name, testCase := range testCases { + name, testCase := name, testCase - schema := r.CoreConfigSchema() - priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) - if err != nil { - t.Fatal(err) - } + t.Run(name, func(t *testing.T) { + t.Parallel() - // A propsed state with only the ID unknown will produce a nil diff, and - // should return the propsed state value. - proposedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ - "id": cty.UnknownVal(cty.String), - })) - if err != nil { - t.Fatal(err) - } - proposedState, err := msgpack.Marshal(proposedVal, schema.ImpliedType()) - if err != nil { - t.Fatal(err) - } + server := NewGRPCProviderServer(&Provider{ + ResourcesMap: map[string]*Resource{ + "test": testCase.TestResource, + }, + }) - config, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ - "id": cty.NullVal(cty.String), - })) - if err != nil { - t.Fatal(err) - } - configBytes, err := msgpack.Marshal(config, schema.ImpliedType()) - if err != nil { - t.Fatal(err) - } + schema := testCase.TestResource.CoreConfigSchema() + priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } - testReq := &tfprotov5.PlanResourceChangeRequest{ - TypeName: "test", - PriorState: &tfprotov5.DynamicValue{ - MsgPack: priorState, - }, - ProposedNewState: &tfprotov5.DynamicValue{ - MsgPack: proposedState, - }, - Config: &tfprotov5.DynamicValue{ - MsgPack: configBytes, - }, - } + // A propsed state with only the ID unknown will produce a nil diff, and + // should return the propsed state value. + proposedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + })) + if err != nil { + t.Fatal(err) + } + proposedState, err := msgpack.Marshal(proposedVal, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } - resp, err := server.PlanResourceChange(context.Background(), testReq) - if err != nil { - t.Fatal(err) - } + config, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{ + "id": cty.NullVal(cty.String), + })) + if err != nil { + t.Fatal(err) + } + configBytes, err := msgpack.Marshal(config, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } - plannedStateVal, err := msgpack.Unmarshal(resp.PlannedState.MsgPack, schema.ImpliedType()) - if err != nil { - t.Fatal(err) - } + testReq := &tfprotov5.PlanResourceChangeRequest{ + TypeName: "test", + PriorState: &tfprotov5.DynamicValue{ + MsgPack: priorState, + }, + ProposedNewState: &tfprotov5.DynamicValue{ + MsgPack: proposedState, + }, + Config: &tfprotov5.DynamicValue{ + MsgPack: configBytes, + }, + } - if !cmp.Equal(proposedVal, plannedStateVal, valueComparer) { - t.Fatal(cmp.Diff(proposedVal, plannedStateVal, valueComparer)) + resp, err := server.PlanResourceChange(context.Background(), testReq) + if err != nil { + t.Fatal(err) + } + + plannedStateVal, err := msgpack.Unmarshal(resp.PlannedState.MsgPack, schema.ImpliedType()) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(proposedVal, plannedStateVal, valueComparer) { + t.Fatal(cmp.Diff(proposedVal, plannedStateVal, valueComparer)) + } + + //nolint:staticcheck // explicitly for this SDK + if testCase.ExpectedUnsafeLegacyTypeSystem != resp.UnsafeToUseLegacyTypeSystem { + //nolint:staticcheck // explicitly for this SDK + t.Fatalf("expected UnsafeLegacyTypeSystem %t, got: %t", testCase.ExpectedUnsafeLegacyTypeSystem, resp.UnsafeToUseLegacyTypeSystem) + } + }) } } @@ -730,12 +766,13 @@ func TestPlanResourceChange_bigint(t *testing.T) { } func TestApplyResourceChange(t *testing.T) { - testCases := []struct { - Description string - TestResource *Resource + t.Parallel() + + testCases := map[string]struct { + TestResource *Resource + ExpectedUnsafeLegacyTypeSystem bool }{ - { - Description: "Create", + "Create": { TestResource: &Resource{ SchemaVersion: 4, Schema: map[string]*Schema{ @@ -749,9 +786,9 @@ func TestApplyResourceChange(t *testing.T) { return nil }, }, + ExpectedUnsafeLegacyTypeSystem: true, }, - { - Description: "CreateContext", + "CreateContext": { TestResource: &Resource{ SchemaVersion: 4, Schema: map[string]*Schema{ @@ -765,9 +802,9 @@ func TestApplyResourceChange(t *testing.T) { return nil }, }, + ExpectedUnsafeLegacyTypeSystem: true, }, - { - Description: "CreateWithoutTimeout", + "CreateWithoutTimeout": { TestResource: &Resource{ SchemaVersion: 4, Schema: map[string]*Schema{ @@ -781,9 +818,9 @@ func TestApplyResourceChange(t *testing.T) { return nil }, }, + ExpectedUnsafeLegacyTypeSystem: true, }, - { - Description: "Create_cty", + "Create_cty": { TestResource: &Resource{ SchemaVersion: 4, Schema: map[string]*Schema{ @@ -806,9 +843,9 @@ func TestApplyResourceChange(t *testing.T) { return nil }, }, + ExpectedUnsafeLegacyTypeSystem: true, }, - { - Description: "CreateContext_SchemaFunc", + "CreateContext_SchemaFunc": { TestResource: &Resource{ SchemaFunc: func() map[string]*Schema { return map[string]*Schema{ @@ -823,12 +860,32 @@ func TestApplyResourceChange(t *testing.T) { return nil }, }, + ExpectedUnsafeLegacyTypeSystem: true, + }, + "EnableLegacyTypeSystemApplyErrors": { + TestResource: &Resource{ + EnableLegacyTypeSystemApplyErrors: true, + Schema: map[string]*Schema{ + "foo": { + Type: TypeInt, + Optional: true, + }, + }, + CreateContext: func(_ context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics { + rd.SetId("bar") + return nil + }, + }, + ExpectedUnsafeLegacyTypeSystem: false, }, } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.Description, func(t *testing.T) { + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + server := NewGRPCProviderServer(&Provider{ ResourcesMap: map[string]*Resource{ "test": testCase.TestResource, @@ -892,6 +949,12 @@ func TestApplyResourceChange(t *testing.T) { if id != "bar" { t.Fatalf("incorrect final state: %#v\n", newStateVal) } + + //nolint:staticcheck // explicitly for this SDK + if testCase.ExpectedUnsafeLegacyTypeSystem != resp.UnsafeToUseLegacyTypeSystem { + //nolint:staticcheck // explicitly for this SDK + t.Fatalf("expected UnsafeLegacyTypeSystem %t, got: %t", testCase.ExpectedUnsafeLegacyTypeSystem, resp.UnsafeToUseLegacyTypeSystem) + } }) } } diff --git a/helper/schema/resource.go b/helper/schema/resource.go index 8a1472e45d..7564a0aff2 100644 --- a/helper/schema/resource.go +++ b/helper/schema/resource.go @@ -595,6 +595,51 @@ type Resource struct { // See github.com/hashicorp/terraform-plugin-sdk/issues/655 for more // details. UseJSONNumber bool + + // EnableLegacyTypeSystemApplyErrors when enabled will prevent the SDK from + // setting the legacy type system flag in the protocol during + // ApplyResourceChange (Create, Update, and Delete) operations. Before + // enabling this setting in a production release for a resource, the + // resource should be exhaustively acceptance tested with the setting + // enabled in an environment where it is easy to clean up resources, + // potentially outside of Terraform, since these errors may be unavoidable + // in certain cases. + // + // Disabling the legacy type system protocol flag is an unsafe operation + // when using this SDK as there are certain unavoidable behaviors imposed + // by the SDK, however this option is surfaced to allow provider developers + // to try to discover fixable data inconsistency errors more easily. + // Terraform, when encountering an enabled legacy type system protocol flag, + // will demote certain schema and data consistency errors into warning logs + // containing the text "legacy plugin SDK". Some errors for errant schema + // definitions, such as when an attribute is not marked as Computed as + // expected by Terraform, can only be resolved by migrating to + // terraform-plugin-framework since that SDK does not impose behavior + // changes with it enabled. However, data-based errors typically require + // logic fixes that should be applicable for both SDKs to be resolved. + EnableLegacyTypeSystemApplyErrors bool + + // EnableLegacyTypeSystemPlanErrors when enabled will prevent the SDK from + // setting the legacy type system flag in the protocol during + // PlanResourceChange operations. Before enabling this setting in a + // production release for a resource, the resource should be exhaustively + // acceptance tested with the setting enabled in an environment where it is + // easy to clean up resources, potentially outside of Terraform, since these + // errors may be unavoidable in certain cases. + // + // Disabling the legacy type system protocol flag is an unsafe operation + // when using this SDK as there are certain unavoidable behaviors imposed + // by the SDK, however this option is surfaced to allow provider developers + // to try to discover fixable data inconsistency errors more easily. + // Terraform, when encountering an enabled legacy type system protocol flag, + // will demote certain schema and data consistency errors into warning logs + // containing the text "legacy plugin SDK". Some errors for errant schema + // definitions, such as when an attribute is not marked as Computed as + // expected by Terraform, can only be resolved by migrating to + // terraform-plugin-framework since that SDK does not impose behavior + // changes with it enabled. However, data-based errors typically require + // logic fixes that should be applicable for both SDKs to be resolved. + EnableLegacyTypeSystemPlanErrors bool } // SchemaMap returns the schema information for this Resource whether it is diff --git a/website/data/plugin-sdkv2-nav-data.json b/website/data/plugin-sdkv2-nav-data.json index 27869756df..86afe647a0 100644 --- a/website/data/plugin-sdkv2-nav-data.json +++ b/website/data/plugin-sdkv2-nav-data.json @@ -26,6 +26,10 @@ "title": "Customizing Differences", "path": "resources/customizing-differences" }, + { + "title": "Data Consistency Errors", + "path": "resources/data-consistency-errors" + }, { "title": "Import", "path": "resources/import" }, { "title": "Retries and Customizable Timeouts", diff --git a/website/docs/plugin/sdkv2/resources/data-consistency-errors.mdx b/website/docs/plugin/sdkv2/resources/data-consistency-errors.mdx new file mode 100644 index 0000000000..f555e08ca0 --- /dev/null +++ b/website/docs/plugin/sdkv2/resources/data-consistency-errors.mdx @@ -0,0 +1,112 @@ +--- +page_title: Resources - Data Consistency Errors +description: Fixing data consistency errors caused by this SDK. +--- + +# Resources - Data Consistency Errors + +Resources written with `terraform-plugin-sdk` are by default allowed to perform unexpected data handling operations from Terraform's perspective. Terraform versions 0.12 and later have stricter [data consistency rules](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) than the design and implementation of this SDK, which predated those rules. + +Some of the resource data consistency rules include: + +- Resources should never set or change an attribute value without the schema `Computed` flag. +- Resources should always set an attribute state value to the exact configuration value or prior state value, if not null. + +When Terraform encounters an unexpected data handling behavior from `terraform-plugin-sdk` resources during planning or applying operations, instead of immediately raising an error diagnostic to practitioners (or provider developers during acceptance testing), it will generate a warning log entry. These can be hard for provider developers to know about or discover as most environments are not running Terraform with logging enabled, let alone checking for warnings or errors in those logs. + +There are two challenges with these demoted errors: + +- If a problematic attribute value is referenced by another resource in the same Terraform configuration, Terraform will raise an error diagnostic for the downstream resource due to the unexpected value behavior caused by the upstream resource. This often will cause a confusing bug report in the downstream provider, which cannot be fixed there. +- If the resource is being [migrated to terraform-plugin-framework](/terraform/plugin/framework/migrating), Terraform will always raise the errors, regardless if the attribute is referenced elsewhere. This makes any issues during the framework migration more difficult to triage, as it is unclear whether the resource behavior was previously wrong or if something went wrong when implementing the framework-based logic. + +While it is not completely possible to remove all data consistency errors with this SDK, this page provides instructions for [finding these errors](#finding-data-consistency-errors) and [potential steps for resolving these errors](#resolving-data-consistency-errors). + +## Finding Data Consistency Errors + +This section describes methods for finding data consistency errors. The [Resolving Data Consistency Errors](#resolving-data-consistency-errors) section describes how to potentially fix errors. + +### Checking For Warning Logs + +Enable [Terraform logging](/terraform/internals/debugging). When running Terraform commands, such as `terraform apply`, the `TF_LOG=TRACE` environment variable can be set, such as `TF_LOG=TRACE terraform apply`. When running [acceptance testing](/terraform/plugin/testing/acceptance-tests), the `TF_ACC_LOG` and `TF_ACC_LOG_PATH` [acceptance testing environment variables](/terraform/plugin/testing/acceptance-tests#environment-variables) must be set to save the Terraform log output from a test. + +If there are data consistency errors, Terraform will create warning logs. + +In this example, Terraform raised a data consistency warning log entry: + +```text +TIMESTAMP [WARN] Provider "TYPE" produced an invalid plan for ADDRESS, but we are tolerating it because it is using the legacy plugin SDK. + The following problems may be the cause of any confusing errors from downstream operations: + - .ATTRIBUTE: planned value cty.False for a non-computed attribute +``` + +### Enabling Resource Data Consistency Errors + + + +Always fully verify the resource with acceptance testing before a production release with these settings enabled. If unchecked, these settings can cause unavoidable errors for practitioners and prevent their Terraform workflows from successfully completing. + + + +Enable the `Resource` type `EnableApplyLegacyTypeSystemErrors` and `EnablePlanLegacyTypeSystemErrors` boolean fields and run all available [acceptance testing](/terraform/plugin/testing/acceptance-tests) for the resource. These settings will cause Terraform to raise data consistency errors instead of demoting those to warning logs for the resource. + +In this example, a resource has been enabled to raise data consistency errors instead of warning logs: + +```go +schema.Resource{ + // ... other fields as necessary ... + EnableApplyLegacyTypeSystemErrors: true, + EnablePlanLegacyTypeSystemErrors: true, +} +``` + +If there are unavoidable data consistency errors after reviewing the [Resolving Data Consistency Errors](#resolving-data-consistency-errors) section, disable only the setting(s) causing errors. This ensures that future changes to the resource are not introducing new data consistency issues. + +### Acceptance Testing All Values + +If the resource has unavoidable data consistency errors after reviewing the [Resolving Data Consistency Errors](#resolving-data-consistency-errors) section, a last resort option for checking one type of data consistency error is implementing [acceptance testing](/terraform/plugin/testing/acceptance-tests) that validates every configured attribute has the same exact value in state after being applied. The typical recommendation for state value checking in acceptance testing is only for `Computed: true` attributes, however since this SDK can bypass the Terraform data consistency rules, it is possible for state values to not match configuration values. + +In this example, an acceptance test performs the typically unnecessary state value check of a configured value: + +```go +func TestThingResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + // ... other fields as necessary ... + Steps: []resource.TestStep{ + { + // ... other fields as necessary ... + Config: ` + resource "examplecloud_thing" "test" { + configurable_attribute = "test-value" + } + ` + Checks: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("examplecloud_thing.test", "configurable_attribute", "test-value"), + ), + }, + // ... potentially other steps as necessary ... + }, + }) +} +``` + +The state value check will raise an error if there is an unexpected value difference, which is the same as if Terraform raised the error. Checking state values of configured values can be removed once the resource is [migrated to terraform-plugin-framework](/terraform/plugin/framework/migrating), as Terraform itself will raise any potential error. + +## Resolving Data Consistency Errors + +This section describes Terraform data consistency errors and resolution options. + +### Planned Value For a Non-Computed Attribute + +If the resource is raising this type of error or warning log: + +```text +TIMESTAMP [WARN] Provider "TYPE" produced an invalid plan for ADDRESS, but we are tolerating it because it is using the legacy plugin SDK. + The following problems may be the cause of any confusing errors from downstream operations: + - .ATTRIBUTE: planned value cty.False for a non-computed attribute +``` + +This occurs if the attribute schema definition is `Optional: true` without `Computed: true` set, especially in schemas with the `Default` field set. Terraform requires a provider to set the `Computed: true` flag in the schema if the provider may set the value in state different than the configuration. + +If the value is expected to never be set by configuration, the schema attribute `Optional: true` flag should be replaced with `Computed: true`. + +Otherwise, this may not be resolvable when the resource is implemented with `terraform-plugin-sdk`. Having `Optional: true` while also setting the attribute's `Computed: true` flag in the schema will also enable this SDK's behavior of keeping the prior state value if the configuration value is removed (set to null) during an update. That SDK behavior is unavoidable. This SDK will also raise an implementation error if both `Computed: true` and `Default` are set, since the value will never reset to the default value because of that behavior. If that behavior is not acceptable, this error is unavoidable until the resource is migrated to terraform-plugin-framework, which does not have implicit behaviors when enabling the `Computed: true` flag and instead provider developers are expected to decide whether the prior state preservation behavior should occur or not by using the `UseStateForUnknown` schema plan modifier. diff --git a/website/docs/plugin/sdkv2/resources/index.mdx b/website/docs/plugin/sdkv2/resources/index.mdx index d753b92213..4202a983f8 100644 --- a/website/docs/plugin/sdkv2/resources/index.mdx +++ b/website/docs/plugin/sdkv2/resources/index.mdx @@ -21,6 +21,10 @@ The reality of cloud infrastructure is that it typically takes time to perform o Terraform tracks the state of provisioned resources in its state file, and compares the user-passed configuration against that state. When Terraform detects a discrepancy, it presents the user with the differences between the configuration and the state. Sometimes these scenarios require special handling, which is where [Customizing Differences](/terraform/plugin/sdkv2/resources/customizing-differences) can help. +## Data Consistency Errors + +Terraform has data consistency rules for resources, which may not be easily discovered for resources using this SDK, but problematic for practitioners in their workflow or provider developers when the resource is being [migrated to terraform-plugin-framework](/terraform/plugin/framework/migrating). The [Data Consistency Errors](/terraform/plugin/sdkv2/resources/data-consistency-errors) page discusses the causes for these issues, how to discover them, and how to potentially resolve them. + ## State Migrations Resources define the data types and API interactions required to create, update, and destroy infrastructure with a cloud vendor, while the [Terraform state](/terraform/language/state) stores mapping and metadata information for those remote objects.