From 3a3be3ed9f62fd1d3e7b4d9d0993569152dc0768 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Fri, 29 Apr 2022 16:14:54 -0400 Subject: [PATCH 1/3] tfsdk: Migrate server to internal/proto6server package Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 This change was accomplished by: - `mkdir internal/proto6server` - `git mv tfsdk/serve* internal/proto6server/` - Replacing `package tfsdk` with `package proto6server` in those moved files - Removing `NewProtocol6Server()`, `Serve()`, and `ServeOpts` in moved files - Adding necessary `tfsdk.` scoping to the now external `tfsdk` package references - Moved necessary unexported methods (e.g. `Schema.tfprotov6Schema()`) to `internal/toproto6` (this package will be expanded further in the future) - Moved attribute plan modification testing (including test plan modifiers) to `internal/proto6server/attribute_plan_modification_test.go` - Moved attribute validation testing (including test validators) to `internal/proto6server/attribute_validation_test.go` - Moved block plan modification testing (including test plan modifiers) to `internal/proto6server/block_plan_modification_test.go` - Moved block validation testing (including test validators) to `internal/proto6server/block_validation_test.go` - Copied tfsdk Config/Plan/State.getAttributeValue methods to internal/proto6server (Config/Plan/State)GetAttributeValue functions temporarily to not export existing methods There are some other planned refactorings in the future for the `internal/proto6server` and `tfsdk` code including: - Creating a shared implementation for the underlying `Config`/`Plan`/`State` details - Creating a `internal/fromproto6` package with protocol version 6 type to framework type conversions - Migrating additional framework type to protocol version 6 code to `internal/toproto6` - Migrating RPC handling into individual code and test files These will be handled separately to reduce review burden, as this change was already very large. --- .changelog/pending.txt | 7 + .../attribute_plan_modification.go | 230 ++ .../attribute_plan_modification_test.go | 1642 +++++++++ internal/proto6server/attribute_validation.go | 253 ++ .../proto6server/attribute_validation_test.go | 917 ++++++ .../proto6server/block_plan_modification.go | 177 + .../block_plan_modification_test.go | 838 +++++ internal/proto6server/block_validation.go | 163 + .../proto6server/block_validation_test.go | 839 +++++ internal/proto6server/config.go | 102 + internal/proto6server/doc.go | 3 + internal/proto6server/plan.go | 102 + internal/proto6server/schema.go | 33 + .../proto6server/schema_plan_modification.go | 75 + internal/proto6server/schema_validation.go | 71 + .../proto6server/schema_validation_test.go | 178 + {tfsdk => internal/proto6server}/serve.go | 226 +- ...erve_data_source_config_validators_test.go | 25 +- .../serve_data_source_one_test.go | 13 +- .../serve_data_source_two_test.go | 13 +- .../serve_data_source_validate_config_test.go | 15 +- .../proto6server}/serve_import.go | 13 +- .../proto6server}/serve_import_test.go | 20 +- .../serve_provider_config_validators_test.go | 21 +- .../proto6server}/serve_provider_test.go | 95 +- .../serve_provider_validate_config_test.go | 11 +- ..._resource_attribute_plan_modifiers_test.go | 51 +- .../serve_resource_config_validators_test.go | 31 +- ...ource_import_state_not_implemented_test.go | 19 +- .../serve_resource_import_state_test.go | 21 +- .../proto6server}/serve_resource_one_test.go | 19 +- .../serve_resource_three_test.go | 23 +- .../proto6server}/serve_resource_two_test.go | 31 +- ...serve_resource_upgrade_state_empty_test.go | 23 +- ...urce_upgrade_state_not_implemented_test.go | 23 +- .../serve_resource_upgrade_state_test.go | 43 +- .../serve_resource_validate_config_test.go | 21 +- .../proto6server}/serve_test.go | 151 +- internal/proto6server/state.go | 102 + internal/toproto6/block.go | 92 + internal/toproto6/block_test.go | 476 +++ internal/toproto6/doc.go | 3 + internal/toproto6/schema.go | 84 + internal/toproto6/schema_attribute.go | 96 + internal/toproto6/schema_attribute_test.go | 445 +++ internal/toproto6/schema_test.go | 580 ++++ providerserver/providerserver.go | 7 +- tfsdk/attribute.go | 546 --- tfsdk/attribute_test.go | 2920 ----------------- tfsdk/attribute_validation_test.go | 66 - tfsdk/block.go | 396 --- tfsdk/block_test.go | 2128 ------------ tfsdk/schema.go | 161 - tfsdk/schema_plan_modification.go | 39 - tfsdk/schema_test.go | 735 ----- tfsdk/schema_validation.go | 23 - tfsdk/serve_opts.go | 92 - tfsdk/serve_opts_test.go | 169 - 58 files changed, 7963 insertions(+), 7735 deletions(-) create mode 100644 .changelog/pending.txt create mode 100644 internal/proto6server/attribute_plan_modification.go create mode 100644 internal/proto6server/attribute_plan_modification_test.go create mode 100644 internal/proto6server/attribute_validation.go create mode 100644 internal/proto6server/attribute_validation_test.go create mode 100644 internal/proto6server/block_plan_modification.go create mode 100644 internal/proto6server/block_plan_modification_test.go create mode 100644 internal/proto6server/block_validation.go create mode 100644 internal/proto6server/block_validation_test.go create mode 100644 internal/proto6server/config.go create mode 100644 internal/proto6server/doc.go create mode 100644 internal/proto6server/plan.go create mode 100644 internal/proto6server/schema.go create mode 100644 internal/proto6server/schema_plan_modification.go create mode 100644 internal/proto6server/schema_validation.go create mode 100644 internal/proto6server/schema_validation_test.go rename {tfsdk => internal/proto6server}/serve.go (92%) rename {tfsdk => internal/proto6server}/serve_data_source_config_validators_test.go (74%) rename {tfsdk => internal/proto6server}/serve_data_source_one_test.go (84%) rename {tfsdk => internal/proto6server}/serve_data_source_two_test.go (84%) rename {tfsdk => internal/proto6server}/serve_data_source_validate_config_test.go (78%) rename {tfsdk => internal/proto6server}/serve_import.go (94%) rename {tfsdk => internal/proto6server}/serve_import_test.go (86%) rename {tfsdk => internal/proto6server}/serve_provider_config_validators_test.go (64%) rename {tfsdk => internal/proto6server}/serve_provider_test.go (84%) rename {tfsdk => internal/proto6server}/serve_provider_validate_config_test.go (69%) rename {tfsdk => internal/proto6server}/serve_resource_attribute_plan_modifiers_test.go (81%) rename {tfsdk => internal/proto6server}/serve_resource_config_validators_test.go (70%) rename {tfsdk => internal/proto6server}/serve_resource_import_state_not_implemented_test.go (73%) rename {tfsdk => internal/proto6server}/serve_resource_import_state_test.go (80%) rename {tfsdk => internal/proto6server}/serve_resource_one_test.go (83%) rename {tfsdk => internal/proto6server}/serve_resource_three_test.go (81%) rename {tfsdk => internal/proto6server}/serve_resource_two_test.go (85%) rename {tfsdk => internal/proto6server}/serve_resource_upgrade_state_empty_test.go (75%) rename {tfsdk => internal/proto6server}/serve_resource_upgrade_state_not_implemented_test.go (72%) rename {tfsdk => internal/proto6server}/serve_resource_upgrade_state_test.go (84%) rename {tfsdk => internal/proto6server}/serve_resource_validate_config_test.go (75%) rename {tfsdk => internal/proto6server}/serve_test.go (97%) create mode 100644 internal/proto6server/state.go create mode 100644 internal/toproto6/block.go create mode 100644 internal/toproto6/block_test.go create mode 100644 internal/toproto6/doc.go create mode 100644 internal/toproto6/schema.go create mode 100644 internal/toproto6/schema_attribute.go create mode 100644 internal/toproto6/schema_attribute_test.go create mode 100644 internal/toproto6/schema_test.go delete mode 100644 tfsdk/attribute_test.go delete mode 100644 tfsdk/attribute_validation_test.go delete mode 100644 tfsdk/block_test.go delete mode 100644 tfsdk/schema_plan_modification.go delete mode 100644 tfsdk/schema_validation.go delete mode 100644 tfsdk/serve_opts.go delete mode 100644 tfsdk/serve_opts_test.go diff --git a/.changelog/pending.txt b/.changelog/pending.txt new file mode 100644 index 000000000..61ac7c348 --- /dev/null +++ b/.changelog/pending.txt @@ -0,0 +1,7 @@ +```release-note:breaking-change +tfsdk: The `NewProtocol6Server()` function, `Serve()` function, and `ServeOpts` type have been removed. Use the `providerserver` package instead. +``` + +```release-note:breaking-change +tfsdk: The `ModifySchemaPlanRequest`, `ModifySchemaPlanResponse`, `ValidateSchemaRequest`, and `ValidateSchemaResponse` types have been removed. These were not intended for provider developer usage. +``` diff --git a/internal/proto6server/attribute_plan_modification.go b/internal/proto6server/attribute_plan_modification.go new file mode 100644 index 000000000..1451f11cc --- /dev/null +++ b/internal/proto6server/attribute_plan_modification.go @@ -0,0 +1,230 @@ +package proto6server + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// AttributeModifyPlan runs all AttributePlanModifiers +// +// TODO: Clean up this abstraction back into an internal Attribute type method. +// The extra Attribute parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func AttributeModifyPlan(ctx context.Context, a tfsdk.Attribute, req tfsdk.ModifyAttributePlanRequest, resp *ModifySchemaPlanResponse) { + ctx = logging.FrameworkWithAttributePath(ctx, req.AttributePath.String()) + + attrConfig, diags := ConfigGetAttributeValue(ctx, req.Config, req.AttributePath) + resp.Diagnostics.Append(diags...) + + // Only on new errors. + if diags.HasError() { + return + } + req.AttributeConfig = attrConfig + + attrState, diags := StateGetAttributeValue(ctx, req.State, req.AttributePath) + resp.Diagnostics.Append(diags...) + + // Only on new errors. + if diags.HasError() { + return + } + req.AttributeState = attrState + + attrPlan, diags := PlanGetAttributeValue(ctx, req.Plan, req.AttributePath) + resp.Diagnostics.Append(diags...) + + // Only on new errors. + if diags.HasError() { + return + } + req.AttributePlan = attrPlan + + var requiresReplace bool + for _, planModifier := range a.PlanModifiers { + modifyResp := &tfsdk.ModifyAttributePlanResponse{ + AttributePlan: req.AttributePlan, + RequiresReplace: requiresReplace, + } + + logging.FrameworkDebug( + ctx, + "Calling provider defined AttributePlanModifier", + map[string]interface{}{ + logging.KeyDescription: planModifier.Description(ctx), + }, + ) + planModifier.Modify(ctx, req, modifyResp) + logging.FrameworkDebug( + ctx, + "Called provider defined AttributePlanModifier", + map[string]interface{}{ + logging.KeyDescription: planModifier.Description(ctx), + }, + ) + + req.AttributePlan = modifyResp.AttributePlan + resp.Diagnostics.Append(modifyResp.Diagnostics...) + requiresReplace = modifyResp.RequiresReplace + + // Only on new errors. + if modifyResp.Diagnostics.HasError() { + return + } + } + + if requiresReplace { + resp.RequiresReplace = append(resp.RequiresReplace, req.AttributePath) + } + + setAttrDiags := resp.Plan.SetAttribute(ctx, req.AttributePath, req.AttributePlan) + resp.Diagnostics.Append(setAttrDiags...) + + if setAttrDiags.HasError() { + return + } + + if a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0 { + return + } + + nm := a.Attributes.GetNestingMode() + switch nm { + case tfsdk.NestingModeList: + l, ok := req.AttributePlan.(types.List) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for idx := range l.Elems { + for name, attr := range a.Attributes.GetAttributes() { + attrReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + AttributeModifyPlan(ctx, attr, attrReq, resp) + } + } + case tfsdk.NestingModeSet: + s, ok := req.AttributePlan.(types.Set) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for _, value := range s.Elems { + tfValue, err := value.ToTerraformValue(ctx) + if err != nil { + err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Plan Modification Error", + "Attribute plan modification cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for name, attr := range a.Attributes.GetAttributes() { + attrReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + AttributeModifyPlan(ctx, attr, attrReq, resp) + } + } + case tfsdk.NestingModeMap: + m, ok := req.AttributePlan.(types.Map) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for key := range m.Elems { + for name, attr := range a.Attributes.GetAttributes() { + attrReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithElementKeyString(key).WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + AttributeModifyPlan(ctx, attr, attrReq, resp) + } + } + case tfsdk.NestingModeSingle: + o, ok := req.AttributePlan.(types.Object) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + if len(o.Attrs) == 0 { + return + } + + for name, attr := range a.Attributes.GetAttributes() { + attrReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + AttributeModifyPlan(ctx, attr, attrReq, resp) + } + default: + err := fmt.Errorf("unknown attribute nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Plan Modification Error", + "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } +} diff --git a/internal/proto6server/attribute_plan_modification_test.go b/internal/proto6server/attribute_plan_modification_test.go new file mode 100644 index 000000000..ee8572925 --- /dev/null +++ b/internal/proto6server/attribute_plan_modification_test.go @@ -0,0 +1,1642 @@ +package proto6server + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestAttributeModifyPlan(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + req tfsdk.ModifyAttributePlanRequest + resp ModifySchemaPlanResponse // Plan automatically copied from req + expectedResp ModifySchemaPlanResponse + }{ + "config-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "config-error-previous-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "plan-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + }, + }, + "plan-error-previous-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + }, + }, + "state-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "state-error-previous-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "no-plan-modifiers": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "attribute-plan": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "MODIFIED_TWO"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + }, + }, + "attribute-plan-previous-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "MODIFIED_TWO"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }, + }, + }, + }, + }, + }, + }, + "requires-replacement": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "newtestvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "newtestvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "newtestvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test"), + }, + }, + }, + "requires-replacement-previous-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "newtestvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "newtestvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "newtestvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test"), + }, + }, + }, + "requires-replacement-passthrough": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + tfsdk.RequiresReplace(), + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testAttrPlanValueModifierOne{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testAttrPlanValueModifierOne{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTATTRTWO"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test"), + }, + }, + }, + "requires-replacement-unset": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testRequiresReplaceFalseModifier{}, + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testRequiresReplaceFalseModifier{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testRequiresReplaceFalseModifier{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "warnings": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + // Diagnostics.Append() deduplicates, so the warning will only + // be here once unless the test implementation is changed to + // different modifiers or the modifier itself is changed. + diag.NewWarningDiagnostic( + "Warning diag", + "This is a warning", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + }, + }, + "warnings-previous-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + // Diagnostics.Append() deduplicates, so the warning will only + // be here once unless the test implementation is changed to + // different modifiers or the modifier itself is changed. + diag.NewWarningDiagnostic( + "Warning diag", + "This is a warning", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, + }, + }, + }, + }, + }, + }, + "error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Error diag", + "This is an error", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + }, + }, + "error-previous-error": { + req: tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + State: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + }, + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + diag.NewErrorDiagnostic( + "Error diag", + "This is an error", + ), + }, + Plan: tfsdk.Plan{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, + }, + }, + }, + }, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + attribute, err := tc.req.Config.Schema.AttributeAtPath(tc.req.AttributePath) + + if err != nil { + t.Fatalf("Unexpected error getting %s", err) + } + + tc.resp.Plan = tc.req.Plan + + AttributeModifyPlan(context.Background(), attribute, tc.req, &tc.resp) + + if diff := cmp.Diff(tc.expectedResp, tc.resp); diff != "" { + t.Errorf("Unexpected response (-wanted, +got): %s", diff) + } + }) + } +} diff --git a/internal/proto6server/attribute_validation.go b/internal/proto6server/attribute_validation.go new file mode 100644 index 000000000..46d9bc70b --- /dev/null +++ b/internal/proto6server/attribute_validation.go @@ -0,0 +1,253 @@ +package proto6server + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// AttributeValidate performs all Attribute validation. +// +// TODO: Clean up this abstraction back into an internal Attribute type method. +// The extra Attribute parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func AttributeValidate(ctx context.Context, a tfsdk.Attribute, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + ctx = logging.FrameworkWithAttributePath(ctx, req.AttributePath.String()) + + if (a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0) && a.Type == nil { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Attribute Definition", + "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", + ) + + return + } + + if a.Attributes != nil && len(a.Attributes.GetAttributes()) > 0 && a.Type != nil { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Attribute Definition", + "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", + ) + + return + } + + if !a.Required && !a.Optional && !a.Computed { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Attribute Definition", + "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", + ) + + return + } + + attributeConfig, diags := ConfigGetAttributeValue(ctx, req.Config, req.AttributePath) + resp.Diagnostics.Append(diags...) + + if diags.HasError() { + return + } + + req.AttributeConfig = attributeConfig + + for _, validator := range a.Validators { + logging.FrameworkDebug( + ctx, + "Calling provider defined AttributeValidator", + map[string]interface{}{ + logging.KeyDescription: validator.Description(ctx), + }, + ) + validator.Validate(ctx, req, resp) + logging.FrameworkDebug( + ctx, + "Called provider defined AttributeValidator", + map[string]interface{}{ + logging.KeyDescription: validator.Description(ctx), + }, + ) + } + + AttributeValidateNestedAttributes(ctx, a, req, resp) + + if a.DeprecationMessage != "" && attributeConfig != nil { + tfValue, err := attributeConfig.ToTerraformValue(ctx) + if err != nil { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot convert value. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + if !tfValue.IsNull() { + resp.Diagnostics.AddAttributeWarning( + req.AttributePath, + "Attribute Deprecated", + a.DeprecationMessage, + ) + } + } +} + +// AttributeValidateNestedAttributes performs all nested Attributes validation. +// +// TODO: Clean up this abstraction back into an internal Attribute type method. +// The extra Attribute parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func AttributeValidateNestedAttributes(ctx context.Context, a tfsdk.Attribute, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + if a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0 { + return + } + + nm := a.Attributes.GetNestingMode() + switch nm { + case tfsdk.NestingModeList: + l, ok := req.AttributeConfig.(types.List) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for idx := range l.Elems { + for nestedName, nestedAttr := range a.Attributes.GetAttributes() { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(nestedName), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + AttributeValidate(ctx, nestedAttr, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + } + case tfsdk.NestingModeSet: + s, ok := req.AttributeConfig.(types.Set) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for _, value := range s.Elems { + tfValue, err := value.ToTerraformValue(ctx) + if err != nil { + err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for nestedName, nestedAttr := range a.Attributes.GetAttributes() { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(nestedName), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + AttributeValidate(ctx, nestedAttr, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + } + case tfsdk.NestingModeMap: + m, ok := req.AttributeConfig.(types.Map) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for key := range m.Elems { + for nestedName, nestedAttr := range a.Attributes.GetAttributes() { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithElementKeyString(key).WithAttributeName(nestedName), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + AttributeValidate(ctx, nestedAttr, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + } + case tfsdk.NestingModeSingle: + o, ok := req.AttributeConfig.(types.Object) + + if !ok { + err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + if !o.Null && !o.Unknown { + for nestedName, nestedAttr := range a.Attributes.GetAttributes() { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithAttributeName(nestedName), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + AttributeValidate(ctx, nestedAttr, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + } + default: + err := fmt.Errorf("unknown attribute validation nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Attribute Validation Error", + "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } +} diff --git a/internal/proto6server/attribute_validation_test.go b/internal/proto6server/attribute_validation_test.go new file mode 100644 index 000000000..e221e541b --- /dev/null +++ b/internal/proto6server/attribute_validation_test.go @@ -0,0 +1,917 @@ +package proto6server + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestAttributeValidate(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + req tfsdk.ValidateAttributeRequest + resp tfsdk.ValidateAttributeResponse + }{ + "no-attributes-or-type": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Invalid Attribute Definition", + "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", + ), + }, + }, + }, + "both-attributes-and-type": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "testing": { + Type: types.StringType, + Optional: true, + }, + }), + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Invalid Attribute Definition", + "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", + ), + }, + }, + }, + "missing-required-optional-and-computed": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Invalid Attribute Definition", + "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", + ), + }, + }, + }, + "config-error": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", + ), + }, + }, + }, + "no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "deprecation-message-known": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Optional: true, + DeprecationMessage: "Use something else instead.", + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Attribute Deprecated", + "Use something else instead.", + ), + }, + }, + }, + "deprecation-message-null": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, nil), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Optional: true, + DeprecationMessage: "Use something else instead.", + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "deprecation-message-unknown": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Optional: true, + DeprecationMessage: "Use something else instead.", + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Attribute Deprecated", + "Use something else instead.", + ), + }, + }, + }, + "warnings": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testWarningAttributeValidator{}, + testWarningAttributeValidator{}, + }, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testWarningDiagnostic1, + testWarningDiagnostic2, + }, + }, + }, + "errors": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + testErrorAttributeValidator{}, + }, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + testErrorDiagnostic2, + }, + }, + }, + "type-with-validate-error": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: testtypes.StringTypeWithValidateError{}, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test")), + }, + }, + }, + "type-with-validate-warning": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.String, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue(tftypes.String, "testvalue"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Type: testtypes.StringTypeWithValidateWarning{}, + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test")), + }, + }, + }, + "nested-attr-list-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, tfsdk.ListNestedAttributesOptions{}), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-attr-list-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }, tfsdk.ListNestedAttributesOptions{}), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, + "nested-attr-map-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "testkey": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, tfsdk.MapNestedAttributesOptions{}), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-attr-map-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "testkey": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }, tfsdk.MapNestedAttributesOptions{}), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, + "nested-attr-set-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, tfsdk.SetNestedAttributesOptions{}), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-attr-set-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }, tfsdk.SetNestedAttributesOptions{}), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, + "nested-attr-single-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "nested-attr-single-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test": { + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }), + Required: true, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + var got tfsdk.ValidateAttributeResponse + attribute, err := tc.req.Config.Schema.AttributeAtPath(tc.req.AttributePath) + + if err != nil { + t.Fatalf("Unexpected error getting %s", err) + } + + AttributeValidate(context.Background(), attribute, tc.req, &got) + + if diff := cmp.Diff(got, tc.resp); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} + +var ( + testErrorDiagnostic1 = diag.NewErrorDiagnostic( + "Error Diagnostic 1", + "This is an error.", + ) + testErrorDiagnostic2 = diag.NewErrorDiagnostic( + "Error Diagnostic 2", + "This is an error.", + ) + testWarningDiagnostic1 = diag.NewWarningDiagnostic( + "Warning Diagnostic 1", + "This is a warning.", + ) + testWarningDiagnostic2 = diag.NewWarningDiagnostic( + "Warning Diagnostic 2", + "This is a warning.", + ) +) + +type testErrorAttributeValidator struct { + tfsdk.AttributeValidator +} + +func (v testErrorAttributeValidator) Description(ctx context.Context) string { + return "validation that always returns an error" +} + +func (v testErrorAttributeValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v testErrorAttributeValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.Append(testErrorDiagnostic1) + } else { + resp.Diagnostics.Append(testErrorDiagnostic2) + } +} + +type testWarningAttributeValidator struct { + tfsdk.AttributeValidator +} + +func (v testWarningAttributeValidator) Description(ctx context.Context) string { + return "validation that always returns a warning" +} + +func (v testWarningAttributeValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v testWarningAttributeValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + if len(resp.Diagnostics) == 0 { + resp.Diagnostics.Append(testWarningDiagnostic1) + } else { + resp.Diagnostics.Append(testWarningDiagnostic2) + } +} diff --git a/internal/proto6server/block_plan_modification.go b/internal/proto6server/block_plan_modification.go new file mode 100644 index 000000000..09aa83fd5 --- /dev/null +++ b/internal/proto6server/block_plan_modification.go @@ -0,0 +1,177 @@ +package proto6server + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// BlockModifyPlan performs all Block plan modification. +// +// TODO: Clean up this abstraction back into an internal Block type method. +// The extra Block parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func BlockModifyPlan(ctx context.Context, b tfsdk.Block, req tfsdk.ModifyAttributePlanRequest, resp *ModifySchemaPlanResponse) { + attributeConfig, diags := ConfigGetAttributeValue(ctx, req.Config, req.AttributePath) + resp.Diagnostics.Append(diags...) + + if diags.HasError() { + return + } + + req.AttributeConfig = attributeConfig + + attributePlan, diags := PlanGetAttributeValue(ctx, req.Plan, req.AttributePath) + resp.Diagnostics.Append(diags...) + + if diags.HasError() { + return + } + + req.AttributePlan = attributePlan + + attributeState, diags := StateGetAttributeValue(ctx, req.State, req.AttributePath) + resp.Diagnostics.Append(diags...) + + if diags.HasError() { + return + } + + req.AttributeState = attributeState + + var requiresReplace bool + for _, planModifier := range b.PlanModifiers { + modifyResp := &tfsdk.ModifyAttributePlanResponse{ + AttributePlan: req.AttributePlan, + RequiresReplace: requiresReplace, + } + + planModifier.Modify(ctx, req, modifyResp) + + req.AttributePlan = modifyResp.AttributePlan + resp.Diagnostics.Append(modifyResp.Diagnostics...) + requiresReplace = modifyResp.RequiresReplace + + // Only on new errors. + if modifyResp.Diagnostics.HasError() { + return + } + } + + if requiresReplace { + resp.RequiresReplace = append(resp.RequiresReplace, req.AttributePath) + } + + setAttrDiags := resp.Plan.SetAttribute(ctx, req.AttributePath, req.AttributePlan) + resp.Diagnostics.Append(setAttrDiags...) + + if setAttrDiags.HasError() { + return + } + + nm := b.NestingMode + switch nm { + case tfsdk.BlockNestingModeList: + l, ok := req.AttributePlan.(types.List) + + if !ok { + err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Plan Modification Error", + "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for idx := range l.Elems { + for name, attr := range b.Attributes { + attrReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + AttributeModifyPlan(ctx, attr, attrReq, resp) + } + + for name, block := range b.Blocks { + blockReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + BlockModifyPlan(ctx, block, blockReq, resp) + } + } + case tfsdk.BlockNestingModeSet: + s, ok := req.AttributePlan.(types.Set) + + if !ok { + err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Plan Modification Error", + "Block plan modification cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for _, value := range s.Elems { + tfValue, err := value.ToTerraformValue(ctx) + if err != nil { + err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Plan Modification Error", + "Block plan modification cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for name, attr := range b.Attributes { + attrReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + AttributeModifyPlan(ctx, attr, attrReq, resp) + } + + for name, block := range b.Blocks { + blockReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), + Config: req.Config, + Plan: resp.Plan, + ProviderMeta: req.ProviderMeta, + State: req.State, + } + + BlockModifyPlan(ctx, block, blockReq, resp) + } + } + default: + err := fmt.Errorf("unknown block plan modification nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Plan Modification Error", + "Block plan modification cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } +} diff --git a/internal/proto6server/block_plan_modification_test.go b/internal/proto6server/block_plan_modification_test.go new file mode 100644 index 000000000..8a1632af0 --- /dev/null +++ b/internal/proto6server/block_plan_modification_test.go @@ -0,0 +1,838 @@ +package proto6server + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestBlockModifyPlan(t *testing.T) { + t.Parallel() + + schema := func(blockPlanModifiers tfsdk.AttributePlanModifiers, nestedAttrPlanModifiers tfsdk.AttributePlanModifiers) tfsdk.Schema { + return tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + PlanModifiers: nestedAttrPlanModifiers, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + PlanModifiers: blockPlanModifiers, + }, + }, + } + } + + schemaTfValue := func(nestedAttrValue string) tftypes.Value { + return tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, nestedAttrValue), + }, + ), + }, + ), + }, + ) + } + + var schemaNullTfValue tftypes.Value = tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + nil, + ), + }, + ) + + type modifyAttributePlanValues struct { + config string + plan string + state string + } + + modifyAttributePlanRequest := func(attrPath *tftypes.AttributePath, schema tfsdk.Schema, values modifyAttributePlanValues) tfsdk.ModifyAttributePlanRequest { + return tfsdk.ModifyAttributePlanRequest{ + AttributePath: attrPath, + Config: tfsdk.Config{ + Raw: schemaTfValue(values.config), + Schema: schema, + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue(values.plan), + Schema: schema, + }, + State: tfsdk.State{ + Raw: schemaTfValue(values.state), + Schema: schema, + }, + } + } + + testCases := map[string]struct { + req tfsdk.ModifyAttributePlanRequest + resp ModifySchemaPlanResponse // Plan automatically copied from req + expectedResp ModifySchemaPlanResponse + }{ + "no-plan-modifiers": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, nil), + modifyAttributePlanValues{ + config: "testvalue", + plan: "testvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaTfValue("testvalue"), + Schema: schema(nil, nil), + }, + }, + }, + "block-modified": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + testBlockPlanModifierNullList{}, + }, nil), + modifyAttributePlanValues{ + config: "TESTATTRONE", + plan: "TESTATTRONE", + state: "TESTATTRONE", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaNullTfValue, + Schema: schema([]tfsdk.AttributePlanModifier{ + testBlockPlanModifierNullList{}, + }, nil), + }, + }, + }, + "block-modified-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + testBlockPlanModifierNullList{}, + }, nil), + modifyAttributePlanValues{ + config: "TESTATTRONE", + plan: "TESTATTRONE", + state: "TESTATTRONE", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaNullTfValue, + Schema: schema([]tfsdk.AttributePlanModifier{ + testBlockPlanModifierNullList{}, + }, nil), + }, + }, + }, + "block-requires-replacement": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, nil), + modifyAttributePlanValues{ + config: "newtestvalue", + plan: "newtestvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaTfValue("newtestvalue"), + Schema: schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, nil), + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test"), + }, + }, + }, + "block-requires-replacement-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, nil), + modifyAttributePlanValues{ + config: "newtestvalue", + plan: "newtestvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("newtestvalue"), + Schema: schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }, nil), + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test"), + }, + }, + }, + "block-requires-replacement-passthrough": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testBlockPlanModifierNullList{}, + }, nil), + modifyAttributePlanValues{ + config: "newtestvalue", + plan: "newtestvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaNullTfValue, + Schema: schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testBlockPlanModifierNullList{}, + }, nil), + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test"), + }, + }, + }, + "block-requires-replacement-unset": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testRequiresReplaceFalseModifier{}, + }, nil), + modifyAttributePlanValues{ + config: "newtestvalue", + plan: "newtestvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaTfValue("newtestvalue"), + Schema: schema([]tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testRequiresReplaceFalseModifier{}, + }, nil), + }, + }, + }, + "block-warnings": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, nil), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + // Diagnostics.Append() deduplicates, so the warning will only + // be here once unless the test implementation is changed to + // different modifiers or the modifier itself is changed. + diag.NewWarningDiagnostic( + "Warning diag", + "This is a warning", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema([]tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, nil), + }, + }, + }, + "block-warnings-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, nil), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + // Diagnostics.Append() deduplicates, so the warning will only + // be here once unless the test implementation is changed to + // different modifiers or the modifier itself is changed. + diag.NewWarningDiagnostic( + "Warning diag", + "This is a warning", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema([]tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }, nil), + }, + }, + }, + "block-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, nil), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Error diag", + "This is an error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema([]tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, nil), + }, + }, + }, + "block-error-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema([]tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, nil), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + diag.NewErrorDiagnostic( + "Error diag", + "This is an error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema([]tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }, nil), + }, + }, + }, + "nested-attribute-modified": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }), + modifyAttributePlanValues{ + config: "TESTATTRONE", + plan: "TESTATTRONE", + state: "TESTATTRONE", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaTfValue("MODIFIED_TWO"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }), + }, + }, + }, + "nested-attribute-modified-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }), + modifyAttributePlanValues{ + config: "TESTATTRONE", + plan: "TESTATTRONE", + state: "TESTATTRONE", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("MODIFIED_TWO"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + testAttrPlanValueModifierOne{}, + testAttrPlanValueModifierTwo{}, + }), + }, + }, + }, + "nested-attribute-requires-replacement": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }), + modifyAttributePlanValues{ + config: "newtestvalue", + plan: "newtestvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaTfValue("newtestvalue"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }), + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr"), + }, + }, + }, + "nested-attribute-requires-replacement-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }), + modifyAttributePlanValues{ + config: "newtestvalue", + plan: "newtestvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("newtestvalue"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + }), + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr"), + }, + }, + }, + "nested-attribute-requires-replacement-passthrough": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testAttrPlanValueModifierOne{}, + }), + modifyAttributePlanValues{ + config: "TESTATTRONE", + plan: "TESTATTRONE", + state: "previousvalue", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTATTRTWO"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testAttrPlanValueModifierOne{}, + }), + }, + RequiresReplace: []*tftypes.AttributePath{ + tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr"), + }, + }, + }, + "nested-attribute-requires-replacement-unset": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testRequiresReplaceFalseModifier{}, + }), + modifyAttributePlanValues{ + config: "newtestvalue", + plan: "newtestvalue", + state: "testvalue", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Plan: tfsdk.Plan{ + Raw: schemaTfValue("newtestvalue"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + tfsdk.RequiresReplace(), + testRequiresReplaceFalseModifier{}, + }), + }, + }, + }, + "nested-attribute-warnings": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + // Diagnostics.Append() deduplicates, so the warning will only + // be here once unless the test implementation is changed to + // different modifiers or the modifier itself is changed. + diag.NewWarningDiagnostic( + "Warning diag", + "This is a warning", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }), + }, + }, + }, + "nested-attribute-warnings-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + // Diagnostics.Append() deduplicates, so the warning will only + // be here once unless the test implementation is changed to + // different modifiers or the modifier itself is changed. + diag.NewWarningDiagnostic( + "Warning diag", + "This is a warning", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + testWarningDiagModifier{}, + testWarningDiagModifier{}, + }), + }, + }, + }, + "nested-attribute-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{}, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Error diag", + "This is an error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }), + }, + }, + }, + "nested-attribute-error-previous-error": { + req: modifyAttributePlanRequest( + tftypes.NewAttributePath().WithAttributeName("test"), + schema(nil, []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }), + modifyAttributePlanValues{ + config: "TESTDIAG", + plan: "TESTDIAG", + state: "TESTDIAG", + }, + ), + resp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + }, + }, + expectedResp: ModifySchemaPlanResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Previous error diag", + "This was a previous error", + ), + diag.NewErrorDiagnostic( + "Error diag", + "This is an error", + ), + }, + Plan: tfsdk.Plan{ + Raw: schemaTfValue("TESTDIAG"), + Schema: schema(nil, []tfsdk.AttributePlanModifier{ + testErrorDiagModifier{}, + testErrorDiagModifier{}, + }), + }, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + block, err := SchemaBlockAtPath(tc.req.Config.Schema, tc.req.AttributePath) + + if err != nil { + t.Fatalf("Unexpected error getting %s", err) + } + + tc.resp.Plan = tc.req.Plan + + BlockModifyPlan(context.Background(), block, tc.req, &tc.resp) + + if diff := cmp.Diff(tc.expectedResp, tc.resp); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} + +type testBlockPlanModifierNullList struct{} + +func (t testBlockPlanModifierNullList) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + _, ok := req.AttributePlan.(types.List) + if !ok { + return + } + + resp.AttributePlan = types.List{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_attr": types.StringType, + }, + }, + Null: true, + } +} + +func (t testBlockPlanModifierNullList) Description(ctx context.Context) string { + return "This plan modifier is for use during testing only" +} + +func (t testBlockPlanModifierNullList) MarkdownDescription(ctx context.Context) string { + return "This plan modifier is for use during testing only" +} diff --git a/internal/proto6server/block_validation.go b/internal/proto6server/block_validation.go new file mode 100644 index 000000000..029302ec6 --- /dev/null +++ b/internal/proto6server/block_validation.go @@ -0,0 +1,163 @@ +package proto6server + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// BlockValidate performs all Block validation. +// +// TODO: Clean up this abstraction back into an internal Block type method. +// The extra Block parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func BlockValidate(ctx context.Context, b tfsdk.Block, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + attributeConfig, diags := ConfigGetAttributeValue(ctx, req.Config, req.AttributePath) + resp.Diagnostics.Append(diags...) + + if diags.HasError() { + return + } + + req.AttributeConfig = attributeConfig + + for _, validator := range b.Validators { + validator.Validate(ctx, req, resp) + } + + nm := b.NestingMode + switch nm { + case tfsdk.BlockNestingModeList: + l, ok := req.AttributeConfig.(types.List) + + if !ok { + err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Validation Error", + "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for idx := range l.Elems { + for name, attr := range b.Attributes { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + AttributeValidate(ctx, attr, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + + for name, block := range b.Blocks { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + BlockValidate(ctx, block, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + } + case tfsdk.BlockNestingModeSet: + s, ok := req.AttributeConfig.(types.Set) + + if !ok { + err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Validation Error", + "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for _, value := range s.Elems { + tfValue, err := value.ToTerraformValue(ctx) + if err != nil { + err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Validation Error", + "Block validation cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + for name, attr := range b.Attributes { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + AttributeValidate(ctx, attr, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + + for name, block := range b.Blocks { + nestedAttrReq := tfsdk.ValidateAttributeRequest{ + AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), + Config: req.Config, + } + nestedAttrResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + BlockValidate(ctx, block, nestedAttrReq, nestedAttrResp) + + resp.Diagnostics = nestedAttrResp.Diagnostics + } + } + default: + err := fmt.Errorf("unknown block validation nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Validation Error", + "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + if b.DeprecationMessage != "" && attributeConfig != nil { + tfValue, err := attributeConfig.ToTerraformValue(ctx) + + if err != nil { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Block Validation Error", + "Block validation cannot convert value. Report this to the provider developer:\n\n"+err.Error(), + ) + + return + } + + if !tfValue.IsNull() { + resp.Diagnostics.AddAttributeWarning( + req.AttributePath, + "Block Deprecated", + b.DeprecationMessage, + ) + } + } +} diff --git a/internal/proto6server/block_validation_test.go b/internal/proto6server/block_validation_test.go new file mode 100644 index 000000000..ecba3997c --- /dev/null +++ b/internal/proto6server/block_validation_test.go @@ -0,0 +1,839 @@ +package proto6server + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestBlockValidate(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + req tfsdk.ValidateAttributeRequest + resp tfsdk.ValidateAttributeResponse + }{ + "deprecation-message-known": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, + DeprecationMessage: "Use something else instead.", + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Block Deprecated", + "Use something else instead.", + ), + }, + }, + }, + "deprecation-message-null": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + nil, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, + DeprecationMessage: "Use something else instead.", + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "deprecation-message-unknown": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + tftypes.UnknownValue, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, + DeprecationMessage: "Use something else instead.", + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewAttributeWarningDiagnostic( + tftypes.NewAttributePath().WithAttributeName("test"), + "Block Deprecated", + "Use something else instead.", + ), + }, + }, + }, + "warnings": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + Validators: []tfsdk.AttributeValidator{ + testWarningAttributeValidator{}, + testWarningAttributeValidator{}, + }, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testWarningDiagnostic1, + testWarningDiagnostic2, + }, + }, + }, + "errors": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + testErrorAttributeValidator{}, + }, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + testErrorDiagnostic2, + }, + }, + }, + "nested-attr-warnings": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testWarningAttributeValidator{}, + testWarningAttributeValidator{}, + }, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testWarningDiagnostic1, + testWarningDiagnostic2, + }, + }, + }, + "nested-attr-errors": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + testErrorAttributeValidator{}, + }, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + testErrorDiagnostic2, + }, + }, + }, + "nested-attr-type-with-validate-error": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: testtypes.StringTypeWithValidateError{}, + Required: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr")), + }, + }, + }, + "nested-attr-type-with-validate-warning": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: testtypes.StringTypeWithValidateWarning{}, + Required: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr")), + }, + }, + }, + "list-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "list-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, + "set-no-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{}, + }, + "set-validation": { + req: tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), + Config: tfsdk.Config{ + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "test": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attr": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), + }, + ), + }, + ), + }, + ), + Schema: tfsdk.Schema{ + Blocks: map[string]tfsdk.Block{ + "test": { + Attributes: map[string]tfsdk.Attribute{ + "nested_attr": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + }, + }, + }, + }, + resp: tfsdk.ValidateAttributeResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + }, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + var got tfsdk.ValidateAttributeResponse + block, err := SchemaBlockAtPath(tc.req.Config.Schema, tc.req.AttributePath) + + if err != nil { + t.Fatalf("Unexpected error getting %s", err) + } + + BlockValidate(context.Background(), block, tc.req, &got) + + if diff := cmp.Diff(got, tc.resp); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} diff --git a/internal/proto6server/config.go b/internal/proto6server/config.go new file mode 100644 index 000000000..7e5d06317 --- /dev/null +++ b/internal/proto6server/config.go @@ -0,0 +1,102 @@ +package proto6server + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// ConfigGetAttributeValue is a duplicate of tfsdk.Config.getAttributeValue, +// except it calls a local duplicate to Config.terraformValueAtPath as well. +// It is duplicated to prevent any oddities with trying to use +// tfsdk.Config.GetAttribute, which has some potentially undesirable logic. +// Refer to the tfsdk package for the large amount of testing done there. +// +// TODO: Clean up this abstraction back into an internal Config type method. +// The extra Config parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func ConfigGetAttributeValue(ctx context.Context, c tfsdk.Config, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + attrType, err := c.Schema.AttributeTypeAtPath(path) + if err != nil { + diags.AddAttributeError( + path, + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + // if the whole config is nil, the value of a valid attribute is also + // nil + if c.Raw.IsNull() { + return nil, nil + } + + tfValue, err := ConfigTerraformValueAtPath(c, path) + + // Ignoring ErrInvalidStep will allow this method to return a null value of the type. + if err != nil && !errors.Is(err, tftypes.ErrInvalidStep) { + diags.AddAttributeError( + path, + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + // TODO: If ErrInvalidStep, check parent paths for unknown value. + // If found, convert this value to an unknown value. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/186 + + if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { + logging.FrameworkTrace(ctx, "Type implements TypeWithValidate") + logging.FrameworkDebug(ctx, "Calling provider defined Type Validate") + diags.Append(attrTypeWithValidate.Validate(ctx, tfValue, path)...) + logging.FrameworkDebug(ctx, "Called provider defined Type Validate") + + if diags.HasError() { + return nil, diags + } + } + + attrValue, err := attrType.ValueFromTerraform(ctx, tfValue) + + if err != nil { + diags.AddAttributeError( + path, + "Configuration Read Error", + "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrValue, diags +} + +// ConfigTerraformValueAtPath is a duplicate of +// tfsdk.Config.terraformValueAtPath. +// +// TODO: Clean up this abstraction back into an internal Config type method. +// The extra Config parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func ConfigTerraformValueAtPath(c tfsdk.Config, path *tftypes.AttributePath) (tftypes.Value, error) { + rawValue, remaining, err := tftypes.WalkAttributePath(c.Raw, path) + if err != nil { + return tftypes.Value{}, fmt.Errorf("%v still remains in the path: %w", remaining, err) + } + attrValue, ok := rawValue.(tftypes.Value) + if !ok { + return tftypes.Value{}, fmt.Errorf("got non-tftypes.Value result %v", rawValue) + } + return attrValue, err +} diff --git a/internal/proto6server/doc.go b/internal/proto6server/doc.go new file mode 100644 index 000000000..94aa9305d --- /dev/null +++ b/internal/proto6server/doc.go @@ -0,0 +1,3 @@ +// Package proto6server contains the provider server implementation compatible +// with protocol version 6 (tfprotov6.ProviderServer). +package proto6server diff --git a/internal/proto6server/plan.go b/internal/proto6server/plan.go new file mode 100644 index 000000000..60fda1d03 --- /dev/null +++ b/internal/proto6server/plan.go @@ -0,0 +1,102 @@ +package proto6server + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// PlanGetAttributeValue is a duplicate of tfsdk.Plan.getAttributeValue, +// except it calls a local duplicate to Plan.terraformValueAtPath as well. +// It is duplicated to prevent any oddities with trying to use +// tfsdk.Plan.GetAttribute, which has some potentially undesirable logic. +// Refer to the tfsdk package for the large amount of testing done there. +// +// TODO: Clean up this abstraction back into an internal Plan type method. +// The extra Plan parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func PlanGetAttributeValue(ctx context.Context, p tfsdk.Plan, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + attrType, err := p.Schema.AttributeTypeAtPath(path) + if err != nil { + err = fmt.Errorf("error getting attribute type in schema: %w", err) + diags.AddAttributeError( + path, + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + // if the whole plan is nil, the value of a valid attribute is also nil + if p.Raw.IsNull() { + return nil, nil + } + + tfValue, err := PlanTerraformValueAtPath(p, path) + + // Ignoring ErrInvalidStep will allow this method to return a null value of the type. + if err != nil && !errors.Is(err, tftypes.ErrInvalidStep) { + diags.AddAttributeError( + path, + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + // TODO: If ErrInvalidStep, check parent paths for unknown value. + // If found, convert this value to an unknown value. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/186 + + if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { + logging.FrameworkTrace(ctx, "Type implements TypeWithValidate") + logging.FrameworkDebug(ctx, "Calling provider defined Type Validate") + diags.Append(attrTypeWithValidate.Validate(ctx, tfValue, path)...) + logging.FrameworkDebug(ctx, "Called provider defined Type Validate") + + if diags.HasError() { + return nil, diags + } + } + + attrValue, err := attrType.ValueFromTerraform(ctx, tfValue) + + if err != nil { + diags.AddAttributeError( + path, + "Plan Read Error", + "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrValue, diags +} + +// PlanTerraformValueAtPath is a duplicate of +// tfsdk.Plan.terraformValueAtPath. +// +// TODO: Clean up this abstraction back into an internal Plan type method. +// The extra Plan parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func PlanTerraformValueAtPath(p tfsdk.Plan, path *tftypes.AttributePath) (tftypes.Value, error) { + rawValue, remaining, err := tftypes.WalkAttributePath(p.Raw, path) + if err != nil { + return tftypes.Value{}, fmt.Errorf("%v still remains in the path: %w", remaining, err) + } + attrValue, ok := rawValue.(tftypes.Value) + if !ok { + return tftypes.Value{}, fmt.Errorf("got non-tftypes.Value result %v", rawValue) + } + return attrValue, err +} diff --git a/internal/proto6server/schema.go b/internal/proto6server/schema.go new file mode 100644 index 000000000..7cf9e8dad --- /dev/null +++ b/internal/proto6server/schema.go @@ -0,0 +1,33 @@ +package proto6server + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// SchemaBlockAtPath returns the Block at the passed path. If the path points +// to an element or attribute of a complex type, rather than to a Block, +// it will return an ErrPathInsideAtomicAttribute error. +// +// TODO: Clean up this abstraction back into an internal Schema type method. +// The extra Schema parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func SchemaBlockAtPath(s tfsdk.Schema, path *tftypes.AttributePath) (tfsdk.Block, error) { + res, remaining, err := tftypes.WalkAttributePath(s, path) + if err != nil { + return tfsdk.Block{}, fmt.Errorf("%v still remains in the path: %w", remaining, err) + } + + switch r := res.(type) { + // TODO: Temporarily not checked while this is only used in testing. + // case nestedBlock: + // return Block{}, ErrPathInsideAtomicAttribute + case tfsdk.Block: + return r, nil + default: + return tfsdk.Block{}, fmt.Errorf("got unexpected type %T", res) + } +} diff --git a/internal/proto6server/schema_plan_modification.go b/internal/proto6server/schema_plan_modification.go new file mode 100644 index 000000000..9fab6148b --- /dev/null +++ b/internal/proto6server/schema_plan_modification.go @@ -0,0 +1,75 @@ +package proto6server + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// ModifySchemaPlanRequest represents a request for a schema to run all +// attribute plan modification functions. +type ModifySchemaPlanRequest struct { + // Config is the configuration the user supplied for the resource. + Config tfsdk.Config + + // State is the current state of the resource. + State tfsdk.State + + // Plan is the planned new state for the resource. + Plan tfsdk.Plan + + // ProviderMeta is metadata from the provider_meta block of the module. + ProviderMeta tfsdk.Config +} + +// ModifySchemaPlanResponse represents a response to a ModifySchemaPlanRequest. +type ModifySchemaPlanResponse struct { + // Plan is the planned new state for the resource. + Plan tfsdk.Plan + + // RequiresReplace is a list of tftypes.AttributePaths that require the + // resource to be replaced. They should point to the specific field + // that changed that requires the resource to be destroyed and + // recreated. + RequiresReplace []*tftypes.AttributePath + + // Diagnostics report errors or warnings related to running all attribute + // plan modifiers. Returning an empty slice indicates a successful + // plan modification with no warnings or errors generated. + Diagnostics diag.Diagnostics +} + +// SchemaModifyPlan runs all AttributePlanModifiers in all schema attributes +// and blocks. +// +// TODO: Clean up this abstraction back into an internal Schema type method. +// The extra Schema parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func SchemaModifyPlan(ctx context.Context, s tfsdk.Schema, req ModifySchemaPlanRequest, resp *ModifySchemaPlanResponse) { + for name, attr := range s.Attributes { + attrReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName(name), + Config: req.Config, + State: req.State, + Plan: req.Plan, + ProviderMeta: req.ProviderMeta, + } + + AttributeModifyPlan(ctx, attr, attrReq, resp) + } + + for name, block := range s.Blocks { + blockReq := tfsdk.ModifyAttributePlanRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName(name), + Config: req.Config, + State: req.State, + Plan: req.Plan, + ProviderMeta: req.ProviderMeta, + } + + BlockModifyPlan(ctx, block, blockReq, resp) + } +} diff --git a/internal/proto6server/schema_validation.go b/internal/proto6server/schema_validation.go new file mode 100644 index 000000000..d6e72617e --- /dev/null +++ b/internal/proto6server/schema_validation.go @@ -0,0 +1,71 @@ +package proto6server + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// ValidateSchemaRequest repesents a request for validating a Schema. +type ValidateSchemaRequest struct { + // Config contains the entire configuration of the data source, provider, or resource. + // + // This configuration may contain unknown values if a user uses + // interpolation or other functionality that would prevent Terraform + // from knowing the value at request time. + Config tfsdk.Config +} + +// ValidateSchemaResponse represents a response to a +// ValidateSchemaRequest. +type ValidateSchemaResponse struct { + // Diagnostics report errors or warnings related to validating the schema. + // An empty slice indicates success, with no warnings or errors generated. + Diagnostics diag.Diagnostics +} + +// SchemaValidate performs all Attribute and Block validation. +// +// TODO: Clean up this abstraction back into an internal Schema type method. +// The extra Schema parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func SchemaValidate(ctx context.Context, s tfsdk.Schema, req ValidateSchemaRequest, resp *ValidateSchemaResponse) { + for name, attribute := range s.Attributes { + + attributeReq := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName(name), + Config: req.Config, + } + attributeResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + AttributeValidate(ctx, attribute, attributeReq, attributeResp) + + resp.Diagnostics = attributeResp.Diagnostics + } + + for name, block := range s.Blocks { + attributeReq := tfsdk.ValidateAttributeRequest{ + AttributePath: tftypes.NewAttributePath().WithAttributeName(name), + Config: req.Config, + } + attributeResp := &tfsdk.ValidateAttributeResponse{ + Diagnostics: resp.Diagnostics, + } + + BlockValidate(ctx, block, attributeReq, attributeResp) + + resp.Diagnostics = attributeResp.Diagnostics + } + + if s.DeprecationMessage != "" { + resp.Diagnostics.AddWarning( + "Deprecated", + s.DeprecationMessage, + ) + } +} diff --git a/internal/proto6server/schema_validation_test.go b/internal/proto6server/schema_validation_test.go new file mode 100644 index 000000000..745ecb682 --- /dev/null +++ b/internal/proto6server/schema_validation_test.go @@ -0,0 +1,178 @@ +package proto6server + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestSchemaValidate(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + req ValidateSchemaRequest + resp ValidateSchemaResponse + }{ + "no-validation": { + req: ValidateSchemaRequest{ + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attr1": tftypes.String, + "attr2": tftypes.String, + }, + }, map[string]tftypes.Value{ + "attr1": tftypes.NewValue(tftypes.String, "attr1value"), + "attr2": tftypes.NewValue(tftypes.String, "attr2value"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "attr1": { + Type: types.StringType, + Required: true, + }, + "attr2": { + Type: types.StringType, + Required: true, + }, + }, + }, + }, + }, + resp: ValidateSchemaResponse{}, + }, + "deprecation-message": { + req: ValidateSchemaRequest{ + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attr1": tftypes.String, + "attr2": tftypes.String, + }, + }, map[string]tftypes.Value{ + "attr1": tftypes.NewValue(tftypes.String, "attr1value"), + "attr2": tftypes.NewValue(tftypes.String, "attr2value"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "attr1": { + Type: types.StringType, + Required: true, + }, + "attr2": { + Type: types.StringType, + Required: true, + }, + }, + DeprecationMessage: "Use something else instead.", + }, + }, + }, + resp: ValidateSchemaResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewWarningDiagnostic( + "Deprecated", + "Use something else instead.", + ), + }, + }, + }, + "warnings": { + req: ValidateSchemaRequest{ + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attr1": tftypes.String, + "attr2": tftypes.String, + }, + }, map[string]tftypes.Value{ + "attr1": tftypes.NewValue(tftypes.String, "attr1value"), + "attr2": tftypes.NewValue(tftypes.String, "attr2value"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "attr1": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testWarningAttributeValidator{}, + }, + }, + "attr2": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testWarningAttributeValidator{}, + }, + }, + }, + }, + }, + }, + resp: ValidateSchemaResponse{ + Diagnostics: diag.Diagnostics{ + testWarningDiagnostic1, + testWarningDiagnostic2, + }, + }, + }, + "errors": { + req: ValidateSchemaRequest{ + Config: tfsdk.Config{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attr1": tftypes.String, + "attr2": tftypes.String, + }, + }, map[string]tftypes.Value{ + "attr1": tftypes.NewValue(tftypes.String, "attr1value"), + "attr2": tftypes.NewValue(tftypes.String, "attr2value"), + }), + Schema: tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "attr1": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + "attr2": { + Type: types.StringType, + Required: true, + Validators: []tfsdk.AttributeValidator{ + testErrorAttributeValidator{}, + }, + }, + }, + }, + }, + }, + resp: ValidateSchemaResponse{ + Diagnostics: diag.Diagnostics{ + testErrorDiagnostic1, + testErrorDiagnostic2, + }, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + var got ValidateSchemaResponse + SchemaValidate(context.Background(), tc.req.Config.Schema, tc.req, &got) + + if diff := cmp.Diff(got, tc.resp); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} diff --git a/tfsdk/serve.go b/internal/proto6server/serve.go similarity index 92% rename from tfsdk/serve.go rename to internal/proto6server/serve.go index b6f21e53c..66e609f12 100644 --- a/tfsdk/serve.go +++ b/internal/proto6server/serve.go @@ -1,4 +1,4 @@ -package tfsdk +package proto6server import ( "context" @@ -10,8 +10,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/logging" "github.com/hashicorp/terraform-plugin-framework/internal/proto6" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -23,46 +24,11 @@ var _ tfprotov6.ProviderServer = &Server{} // intended for provider developer usage. This will be moved to an internal // package in the next minor version. type Server struct { - Provider Provider + Provider tfsdk.Provider contextCancels []context.CancelFunc contextCancelsMu sync.Mutex } -// NewProtocol6Server returns a tfprotov6.ProviderServer implementation based -// on the passed Provider implementation. -// -// Deprecated: Use providerserver.NewProtocol6 instead. This will be removed in -// the next minor version. -func NewProtocol6Server(p Provider) tfprotov6.ProviderServer { - return &Server{ - Provider: p, - } -} - -// Serve serves a provider, blocking until the context is canceled. -// -// Deprecated: Use providerserver.Serve() instead. This will be removed in the -// next minor version. -func Serve(ctx context.Context, providerFunc func() Provider, opts ServeOpts) error { - err := opts.validate(ctx) - - if err != nil { - return fmt.Errorf("unable to validate ServeOpts: %w", err) - } - - var tf6serverOpts []tf6server.ServeOpt - - if opts.Debug { - tf6serverOpts = append(tf6serverOpts, tf6server.WithManagedDebug()) - } - - return tf6server.Serve(opts.address(ctx), func() tfprotov6.ProviderServer { - return &Server{ - Provider: providerFunc(), - } - }, tf6serverOpts...) -} - func (s *Server) registerContext(in context.Context) context.Context { ctx, cancel := context.WithCancel(in) s.contextCancelsMu.Lock() @@ -80,7 +46,7 @@ func (s *Server) cancelRegisteredContexts(_ context.Context) { s.contextCancels = nil } -func (s *Server) getResourceType(ctx context.Context, typ string) (ResourceType, diag.Diagnostics) { +func (s *Server) getResourceType(ctx context.Context, typ string) (tfsdk.ResourceType, diag.Diagnostics) { // TODO: Cache GetResources call in GetProviderSchema and reference cache instead // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/299 logging.FrameworkDebug(ctx, "Calling provider defined Provider GetResources") @@ -100,7 +66,7 @@ func (s *Server) getResourceType(ctx context.Context, typ string) (ResourceType, return resourceType, diags } -func (s *Server) getDataSourceType(ctx context.Context, typ string) (DataSourceType, diag.Diagnostics) { +func (s *Server) getDataSourceType(ctx context.Context, typ string) (tfsdk.DataSourceType, diag.Diagnostics) { // TODO: Cache GetDataSources call in GetProviderSchema and reference cache instead // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/299 logging.FrameworkDebug(ctx, "Calling provider defined Provider GetDataSources") @@ -157,7 +123,7 @@ func (s *Server) getProviderSchema(ctx context.Context, resp *getProviderSchemaR return } // convert the provider schema to a *tfprotov6.Schema - provider6Schema, err := providerSchema.tfprotov6Schema(ctx) + provider6Schema, err := toproto6.Schema(ctx, providerSchema) if err != nil { resp.Diagnostics.AddError( "Error converting provider schema", @@ -173,7 +139,7 @@ func (s *Server) getProviderSchema(ctx context.Context, resp *getProviderSchemaR // if we have a provider_meta schema, get it var providerMeta6Schema *tfprotov6.Schema - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") providerMetaSchema, diags := pm.GetMetaSchema(ctx) @@ -184,7 +150,7 @@ func (s *Server) getProviderSchema(ctx context.Context, resp *getProviderSchemaR return } - pm6Schema, err := providerMetaSchema.tfprotov6Schema(ctx) + pm6Schema, err := toproto6.Schema(ctx, providerMetaSchema) if err != nil { resp.Diagnostics.AddError( "Error converting provider_meta schema", @@ -215,7 +181,7 @@ func (s *Server) getProviderSchema(ctx context.Context, resp *getProviderSchemaR if resp.Diagnostics.HasError() { return } - schema6, err := schema.tfprotov6Schema(ctx) + schema6, err := toproto6.Schema(ctx, schema) if err != nil { resp.Diagnostics.AddError( "Error converting resource schema", @@ -246,7 +212,7 @@ func (s *Server) getProviderSchema(ctx context.Context, resp *getProviderSchemaR if resp.Diagnostics.HasError() { return } - schema6, err := schema.tfprotov6Schema(ctx) + schema6, err := toproto6.Schema(ctx, schema) if err != nil { resp.Diagnostics.AddError( "Error converting data sourceschema", @@ -318,17 +284,17 @@ func (s *Server) validateProviderConfig(ctx context.Context, req *tfprotov6.Vali return } - vpcReq := ValidateProviderConfigRequest{ - Config: Config{ + vpcReq := tfsdk.ValidateProviderConfigRequest{ + Config: tfsdk.Config{ Raw: config, Schema: schema, }, } - if provider, ok := s.Provider.(ProviderWithConfigValidators); ok { + if provider, ok := s.Provider.(tfsdk.ProviderWithConfigValidators); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithConfigValidators") for _, configValidator := range provider.ConfigValidators(ctx) { - vpcRes := &ValidateProviderConfigResponse{ + vpcRes := &tfsdk.ValidateProviderConfigResponse{ Diagnostics: resp.Diagnostics, } @@ -352,9 +318,9 @@ func (s *Server) validateProviderConfig(ctx context.Context, req *tfprotov6.Vali } } - if provider, ok := s.Provider.(ProviderWithValidateConfig); ok { + if provider, ok := s.Provider.(tfsdk.ProviderWithValidateConfig); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithValidateConfig") - vpcRes := &ValidateProviderConfigResponse{ + vpcRes := &tfsdk.ValidateProviderConfigResponse{ Diagnostics: resp.Diagnostics, } @@ -366,7 +332,7 @@ func (s *Server) validateProviderConfig(ctx context.Context, req *tfprotov6.Vali } validateSchemaReq := ValidateSchemaRequest{ - Config: Config{ + Config: tfsdk.Config{ Raw: config, Schema: schema, }, @@ -375,7 +341,7 @@ func (s *Server) validateProviderConfig(ctx context.Context, req *tfprotov6.Vali Diagnostics: resp.Diagnostics, } - schema.validate(ctx, validateSchemaReq, &validateSchemaResp) + SchemaValidate(ctx, schema, validateSchemaReq, &validateSchemaResp) resp.Diagnostics = validateSchemaResp.Diagnostics } @@ -416,14 +382,14 @@ func (s *Server) configureProvider(ctx context.Context, req *tfprotov6.Configure ) return } - r := ConfigureProviderRequest{ + r := tfsdk.ConfigureProviderRequest{ TerraformVersion: req.TerraformVersion, - Config: Config{ + Config: tfsdk.Config{ Raw: config, Schema: schema, }, } - res := &ConfigureProviderResponse{} + res := &tfsdk.ConfigureProviderResponse{} logging.FrameworkDebug(ctx, "Calling provider defined Provider Configure") s.Provider.Configure(ctx, r, res) logging.FrameworkDebug(ctx, "Called provider defined Provider Configure") @@ -499,17 +465,17 @@ func (s *Server) validateResourceConfig(ctx context.Context, req *tfprotov6.Vali return } - vrcReq := ValidateResourceConfigRequest{ - Config: Config{ + vrcReq := tfsdk.ValidateResourceConfigRequest{ + Config: tfsdk.Config{ Raw: config, Schema: resourceSchema, }, } - if resource, ok := resource.(ResourceWithConfigValidators); ok { + if resource, ok := resource.(tfsdk.ResourceWithConfigValidators); ok { logging.FrameworkTrace(ctx, "Resource implements ResourceWithConfigValidators") for _, configValidator := range resource.ConfigValidators(ctx) { - vrcRes := &ValidateResourceConfigResponse{ + vrcRes := &tfsdk.ValidateResourceConfigResponse{ Diagnostics: resp.Diagnostics, } @@ -533,9 +499,9 @@ func (s *Server) validateResourceConfig(ctx context.Context, req *tfprotov6.Vali } } - if resource, ok := resource.(ResourceWithValidateConfig); ok { + if resource, ok := resource.(tfsdk.ResourceWithValidateConfig); ok { logging.FrameworkTrace(ctx, "Resource implements ResourceWithValidateConfig") - vrcRes := &ValidateResourceConfigResponse{ + vrcRes := &tfsdk.ValidateResourceConfigResponse{ Diagnostics: resp.Diagnostics, } @@ -547,7 +513,7 @@ func (s *Server) validateResourceConfig(ctx context.Context, req *tfprotov6.Vali } validateSchemaReq := ValidateSchemaRequest{ - Config: Config{ + Config: tfsdk.Config{ Raw: config, Schema: resourceSchema, }, @@ -556,7 +522,7 @@ func (s *Server) validateResourceConfig(ctx context.Context, req *tfprotov6.Vali Diagnostics: resp.Diagnostics, } - resourceSchema.validate(ctx, validateSchemaReq, &validateSchemaResp) + SchemaValidate(ctx, resourceSchema, validateSchemaReq, &validateSchemaResp) resp.Diagnostics = validateSchemaResp.Diagnostics } @@ -677,7 +643,7 @@ func (s *Server) upgradeResourceState(ctx context.Context, req *tfprotov6.Upgrad return } - resourceWithUpgradeState, ok := resource.(ResourceWithUpgradeState) + resourceWithUpgradeState, ok := resource.(tfsdk.ResourceWithUpgradeState) if !ok { resp.Diagnostics.AddError( @@ -697,7 +663,7 @@ func (s *Server) upgradeResourceState(ctx context.Context, req *tfprotov6.Upgrad // Panic prevention if resourceStateUpgraders == nil { - resourceStateUpgraders = make(map[int64]ResourceStateUpgrader, 0) + resourceStateUpgraders = make(map[int64]tfsdk.ResourceStateUpgrader, 0) } resourceStateUpgrader, ok := resourceStateUpgraders[req.Version] @@ -712,7 +678,7 @@ func (s *Server) upgradeResourceState(ctx context.Context, req *tfprotov6.Upgrad return } - upgradeResourceStateRequest := UpgradeResourceStateRequest{ + upgradeResourceStateRequest := tfsdk.UpgradeResourceStateRequest{ RawState: req.RawState, } @@ -732,14 +698,14 @@ func (s *Server) upgradeResourceState(ctx context.Context, req *tfprotov6.Upgrad return } - upgradeResourceStateRequest.State = &State{ + upgradeResourceStateRequest.State = &tfsdk.State{ Raw: rawStateValue, Schema: *resourceStateUpgrader.PriorSchema, } } - upgradeResourceStateResponse := UpgradeResourceStateResponse{ - State: State{ + upgradeResourceStateResponse := tfsdk.UpgradeResourceStateResponse{ + State: tfsdk.State{ Schema: resourceSchema, }, } @@ -842,13 +808,13 @@ func (s *Server) readResource(ctx context.Context, req *tfprotov6.ReadResourceRe ) return } - readReq := ReadResourceRequest{ - State: State{ + readReq := tfsdk.ReadResourceRequest{ + State: tfsdk.State{ Raw: state, Schema: resourceSchema, }, } - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") pmSchema, diags := pm.GetMetaSchema(ctx) @@ -857,7 +823,7 @@ func (s *Server) readResource(ctx context.Context, req *tfprotov6.ReadResourceRe if resp.Diagnostics.HasError() { return } - readReq.ProviderMeta = Config{ + readReq.ProviderMeta = tfsdk.Config{ Schema: pmSchema, Raw: tftypes.NewValue(pmSchema.TerraformType(ctx), nil), } @@ -874,8 +840,8 @@ func (s *Server) readResource(ctx context.Context, req *tfprotov6.ReadResourceRe readReq.ProviderMeta.Raw = pmValue } } - readResp := ReadResourceResponse{ - State: State{ + readResp := tfsdk.ReadResourceResponse{ + State: tfsdk.State{ Raw: state, Schema: resourceSchema, }, @@ -899,7 +865,7 @@ func (s *Server) readResource(ctx context.Context, req *tfprotov6.ReadResourceRe resp.NewState = &newState } -func markComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resourceSchema Schema) func(*tftypes.AttributePath, tftypes.Value) (tftypes.Value, error) { +func markComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resourceSchema tfsdk.Schema) func(*tftypes.AttributePath, tftypes.Value) (tftypes.Value, error) { return func(path *tftypes.AttributePath, val tftypes.Value) (tftypes.Value, error) { ctx = logging.FrameworkWithAttributePath(ctx, path.String()) @@ -917,7 +883,7 @@ func markComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resour } attribute, err := resourceSchema.AttributeAtPath(path) if err != nil { - if errors.Is(err, ErrPathInsideAtomicAttribute) { + if errors.Is(err, tfsdk.ErrPathInsideAtomicAttribute) { // ignore attributes/elements inside schema.Attributes, they have no schema of their own logging.FrameworkTrace(ctx, "attribute is a non-schema attribute, not marking unknown") return val, nil @@ -1091,20 +1057,20 @@ func (s *Server) planResourceChange(ctx context.Context, req *tfprotov6.PlanReso // represents a resource being deleted and there's no point. if !plan.IsNull() { modifySchemaPlanReq := ModifySchemaPlanRequest{ - Config: Config{ + Config: tfsdk.Config{ Schema: resourceSchema, Raw: config, }, - State: State{ + State: tfsdk.State{ Schema: resourceSchema, Raw: state, }, - Plan: Plan{ + Plan: tfsdk.Plan{ Schema: resourceSchema, Raw: plan, }, } - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") pmSchema, diags := pm.GetMetaSchema(ctx) @@ -1115,7 +1081,7 @@ func (s *Server) planResourceChange(ctx context.Context, req *tfprotov6.PlanReso return } } - modifySchemaPlanReq.ProviderMeta = Config{ + modifySchemaPlanReq.ProviderMeta = tfsdk.Config{ Schema: pmSchema, Raw: tftypes.NewValue(pmSchema.TerraformType(ctx), nil), } @@ -1134,14 +1100,14 @@ func (s *Server) planResourceChange(ctx context.Context, req *tfprotov6.PlanReso } modifySchemaPlanResp := ModifySchemaPlanResponse{ - Plan: Plan{ + Plan: tfsdk.Plan{ Schema: resourceSchema, Raw: plan, }, Diagnostics: resp.Diagnostics, } - resourceSchema.modifyPlan(ctx, modifySchemaPlanReq, &modifySchemaPlanResp) + SchemaModifyPlan(ctx, resourceSchema, modifySchemaPlanReq, &modifySchemaPlanResp) resp.RequiresReplace = append(resp.RequiresReplace, modifySchemaPlanResp.RequiresReplace...) plan = modifySchemaPlanResp.Plan.Raw resp.Diagnostics = modifySchemaPlanResp.Diagnostics @@ -1158,24 +1124,24 @@ func (s *Server) planResourceChange(ctx context.Context, req *tfprotov6.PlanReso // delete resources, e.g. to inform practitioners that the resource // _can't_ be deleted in the API and will just be removed from // Terraform's state - var modifyPlanResp ModifyResourcePlanResponse - if resource, ok := resource.(ResourceWithModifyPlan); ok { + var modifyPlanResp tfsdk.ModifyResourcePlanResponse + if resource, ok := resource.(tfsdk.ResourceWithModifyPlan); ok { logging.FrameworkTrace(ctx, "Resource implements ResourceWithModifyPlan") - modifyPlanReq := ModifyResourcePlanRequest{ - Config: Config{ + modifyPlanReq := tfsdk.ModifyResourcePlanRequest{ + Config: tfsdk.Config{ Schema: resourceSchema, Raw: config, }, - State: State{ + State: tfsdk.State{ Schema: resourceSchema, Raw: state, }, - Plan: Plan{ + Plan: tfsdk.Plan{ Schema: resourceSchema, Raw: plan, }, } - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") pmSchema, diags := pm.GetMetaSchema(ctx) @@ -1184,7 +1150,7 @@ func (s *Server) planResourceChange(ctx context.Context, req *tfprotov6.PlanReso if resp.Diagnostics.HasError() { return } - modifyPlanReq.ProviderMeta = Config{ + modifyPlanReq.ProviderMeta = tfsdk.Config{ Schema: pmSchema, Raw: tftypes.NewValue(pmSchema.TerraformType(ctx), nil), } @@ -1202,8 +1168,8 @@ func (s *Server) planResourceChange(ctx context.Context, req *tfprotov6.PlanReso } } - modifyPlanResp = ModifyResourcePlanResponse{ - Plan: Plan{ + modifyPlanResp = tfsdk.ModifyResourcePlanResponse{ + Plan: tfsdk.Plan{ Schema: resourceSchema, Raw: plan, }, @@ -1373,17 +1339,17 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe switch { case create && !update && !destroy: logging.FrameworkTrace(ctx, "running create") - createReq := CreateResourceRequest{ - Config: Config{ + createReq := tfsdk.CreateResourceRequest{ + Config: tfsdk.Config{ Schema: resourceSchema, Raw: config, }, - Plan: Plan{ + Plan: tfsdk.Plan{ Schema: resourceSchema, Raw: plan, }, } - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") pmSchema, diags := pm.GetMetaSchema(ctx) @@ -1392,7 +1358,7 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe if resp.Diagnostics.HasError() { return } - createReq.ProviderMeta = Config{ + createReq.ProviderMeta = tfsdk.Config{ Schema: pmSchema, Raw: tftypes.NewValue(pmSchema.TerraformType(ctx), nil), } @@ -1409,8 +1375,8 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe createReq.ProviderMeta.Raw = pmValue } } - createResp := CreateResourceResponse{ - State: State{ + createResp := tfsdk.CreateResourceResponse{ + State: tfsdk.State{ Schema: resourceSchema, Raw: priorState, }, @@ -1431,21 +1397,21 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe resp.NewState = &newState case !create && update && !destroy: logging.FrameworkTrace(ctx, "running update") - updateReq := UpdateResourceRequest{ - Config: Config{ + updateReq := tfsdk.UpdateResourceRequest{ + Config: tfsdk.Config{ Schema: resourceSchema, Raw: config, }, - Plan: Plan{ + Plan: tfsdk.Plan{ Schema: resourceSchema, Raw: plan, }, - State: State{ + State: tfsdk.State{ Schema: resourceSchema, Raw: priorState, }, } - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") pmSchema, diags := pm.GetMetaSchema(ctx) @@ -1454,7 +1420,7 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe if resp.Diagnostics.HasError() { return } - updateReq.ProviderMeta = Config{ + updateReq.ProviderMeta = tfsdk.Config{ Schema: pmSchema, Raw: tftypes.NewValue(pmSchema.TerraformType(ctx), nil), } @@ -1471,8 +1437,8 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe updateReq.ProviderMeta.Raw = pmValue } } - updateResp := UpdateResourceResponse{ - State: State{ + updateResp := tfsdk.UpdateResourceResponse{ + State: tfsdk.State{ Schema: resourceSchema, Raw: priorState, }, @@ -1493,13 +1459,13 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe resp.NewState = &newState case !create && !update && destroy: logging.FrameworkTrace(ctx, "running delete") - destroyReq := DeleteResourceRequest{ - State: State{ + destroyReq := tfsdk.DeleteResourceRequest{ + State: tfsdk.State{ Schema: resourceSchema, Raw: priorState, }, } - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") pmSchema, diags := pm.GetMetaSchema(ctx) @@ -1508,7 +1474,7 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe if resp.Diagnostics.HasError() { return } - destroyReq.ProviderMeta = Config{ + destroyReq.ProviderMeta = tfsdk.Config{ Schema: pmSchema, Raw: tftypes.NewValue(pmSchema.TerraformType(ctx), nil), } @@ -1525,8 +1491,8 @@ func (s *Server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe destroyReq.ProviderMeta.Raw = pmValue } } - destroyResp := DeleteResourceResponse{ - State: State{ + destroyResp := tfsdk.DeleteResourceResponse{ + State: tfsdk.State{ Schema: resourceSchema, Raw: priorState, }, @@ -1623,17 +1589,17 @@ func (s *Server) validateDataResourceConfig(ctx context.Context, req *tfprotov6. return } - vrcReq := ValidateDataSourceConfigRequest{ - Config: Config{ + vrcReq := tfsdk.ValidateDataSourceConfigRequest{ + Config: tfsdk.Config{ Raw: config, Schema: dataSourceSchema, }, } - if dataSource, ok := dataSource.(DataSourceWithConfigValidators); ok { + if dataSource, ok := dataSource.(tfsdk.DataSourceWithConfigValidators); ok { logging.FrameworkTrace(ctx, "DataSource implements DataSourceWithConfigValidators") for _, configValidator := range dataSource.ConfigValidators(ctx) { - vrcRes := &ValidateDataSourceConfigResponse{ + vrcRes := &tfsdk.ValidateDataSourceConfigResponse{ Diagnostics: resp.Diagnostics, } @@ -1657,9 +1623,9 @@ func (s *Server) validateDataResourceConfig(ctx context.Context, req *tfprotov6. } } - if dataSource, ok := dataSource.(DataSourceWithValidateConfig); ok { + if dataSource, ok := dataSource.(tfsdk.DataSourceWithValidateConfig); ok { logging.FrameworkTrace(ctx, "DataSource implements DataSourceWithValidateConfig") - vrcRes := &ValidateDataSourceConfigResponse{ + vrcRes := &tfsdk.ValidateDataSourceConfigResponse{ Diagnostics: resp.Diagnostics, } @@ -1671,7 +1637,7 @@ func (s *Server) validateDataResourceConfig(ctx context.Context, req *tfprotov6. } validateSchemaReq := ValidateSchemaRequest{ - Config: Config{ + Config: tfsdk.Config{ Raw: config, Schema: dataSourceSchema, }, @@ -1680,7 +1646,7 @@ func (s *Server) validateDataResourceConfig(ctx context.Context, req *tfprotov6. Diagnostics: resp.Diagnostics, } - dataSourceSchema.validate(ctx, validateSchemaReq, &validateSchemaResp) + SchemaValidate(ctx, dataSourceSchema, validateSchemaReq, &validateSchemaResp) resp.Diagnostics = validateSchemaResp.Diagnostics } @@ -1735,13 +1701,13 @@ func (s *Server) readDataSource(ctx context.Context, req *tfprotov6.ReadDataSour ) return } - readReq := ReadDataSourceRequest{ - Config: Config{ + readReq := tfsdk.ReadDataSourceRequest{ + Config: tfsdk.Config{ Raw: config, Schema: dataSourceSchema, }, } - if pm, ok := s.Provider.(ProviderWithProviderMeta); ok { + if pm, ok := s.Provider.(tfsdk.ProviderWithProviderMeta); ok { logging.FrameworkTrace(ctx, "Provider implements ProviderWithProviderMeta") logging.FrameworkDebug(ctx, "Calling provider defined Provider GetMetaSchema") pmSchema, diags := pm.GetMetaSchema(ctx) @@ -1750,7 +1716,7 @@ func (s *Server) readDataSource(ctx context.Context, req *tfprotov6.ReadDataSour if resp.Diagnostics.HasError() { return } - readReq.ProviderMeta = Config{ + readReq.ProviderMeta = tfsdk.Config{ Schema: pmSchema, Raw: tftypes.NewValue(pmSchema.TerraformType(ctx), nil), } @@ -1767,8 +1733,8 @@ func (s *Server) readDataSource(ctx context.Context, req *tfprotov6.ReadDataSour readReq.ProviderMeta.Raw = pmValue } } - readResp := ReadDataSourceResponse{ - State: State{ + readResp := tfsdk.ReadDataSourceResponse{ + State: tfsdk.State{ Schema: dataSourceSchema, // default to the config values // they should be of the same type diff --git a/tfsdk/serve_data_source_config_validators_test.go b/internal/proto6server/serve_data_source_config_validators_test.go similarity index 74% rename from tfsdk/serve_data_source_config_validators_test.go rename to internal/proto6server/serve_data_source_config_validators_test.go index 7f82068b8..d3d038833 100644 --- a/tfsdk/serve_data_source_config_validators_test.go +++ b/internal/proto6server/serve_data_source_config_validators_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeDataSourceTypeConfigValidators struct{} -func (dt testServeDataSourceTypeConfigValidators) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeDataSourceTypeConfigValidators) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "string": { Type: types.StringType, Optional: true, @@ -23,7 +24,7 @@ func (dt testServeDataSourceTypeConfigValidators) GetSchema(_ context.Context) ( }, nil } -func (dt testServeDataSourceTypeConfigValidators) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { +func (dt testServeDataSourceTypeConfigValidators) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -59,14 +60,14 @@ type testServeDataSourceConfigValidators struct { provider *testServeProvider } -func (r testServeDataSourceConfigValidators) Read(ctx context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { +func (r testServeDataSourceConfigValidators) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeDataSourceConfigValidators) ConfigValidators(ctx context.Context) []DataSourceConfigValidator { +func (r testServeDataSourceConfigValidators) ConfigValidators(ctx context.Context) []tfsdk.DataSourceConfigValidator { r.provider.validateDataSourceConfigCalledDataSourceType = "test_config_validators" - return []DataSourceConfigValidator{ + return []tfsdk.DataSourceConfigValidator{ newTestDataSourceConfigValidator(r.provider.validateDataSourceConfigImpl), // Verify multiple validators newTestDataSourceConfigValidator(r.provider.validateDataSourceConfigImpl), @@ -74,9 +75,9 @@ func (r testServeDataSourceConfigValidators) ConfigValidators(ctx context.Contex } type testDataSourceConfigValidator struct { - DataSourceConfigValidator + tfsdk.DataSourceConfigValidator - impl func(context.Context, ValidateDataSourceConfigRequest, *ValidateDataSourceConfigResponse) + impl func(context.Context, tfsdk.ValidateDataSourceConfigRequest, *tfsdk.ValidateDataSourceConfigResponse) } func (v testDataSourceConfigValidator) Description(ctx context.Context) string { @@ -85,10 +86,10 @@ func (v testDataSourceConfigValidator) Description(ctx context.Context) string { func (v testDataSourceConfigValidator) MarkdownDescription(ctx context.Context) string { return "**test** data source config validator" } -func (v testDataSourceConfigValidator) Validate(ctx context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { +func (v testDataSourceConfigValidator) Validate(ctx context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { v.impl(ctx, req, resp) } -func newTestDataSourceConfigValidator(impl func(context.Context, ValidateDataSourceConfigRequest, *ValidateDataSourceConfigResponse)) testDataSourceConfigValidator { +func newTestDataSourceConfigValidator(impl func(context.Context, tfsdk.ValidateDataSourceConfigRequest, *tfsdk.ValidateDataSourceConfigResponse)) testDataSourceConfigValidator { return testDataSourceConfigValidator{impl: impl} } diff --git a/tfsdk/serve_data_source_one_test.go b/internal/proto6server/serve_data_source_one_test.go similarity index 84% rename from tfsdk/serve_data_source_one_test.go rename to internal/proto6server/serve_data_source_one_test.go index eb4ef901c..eae951a14 100644 --- a/tfsdk/serve_data_source_one_test.go +++ b/internal/proto6server/serve_data_source_one_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeDataSourceTypeOne struct{} -func (dt testServeDataSourceTypeOne) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeDataSourceTypeOne) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "current_time": { Type: types.StringType, Computed: true, @@ -31,7 +32,7 @@ func (dt testServeDataSourceTypeOne) GetSchema(_ context.Context) (Schema, diag. }, nil } -func (dt testServeDataSourceTypeOne) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { +func (dt testServeDataSourceTypeOne) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -79,7 +80,7 @@ type testServeDataSourceOne struct { provider *testServeProvider } -func (r testServeDataSourceOne) Read(ctx context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { +func (r testServeDataSourceOne) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { r.provider.readDataSourceConfigValue = req.Config.Raw r.provider.readDataSourceConfigSchema = req.Config.Schema r.provider.readDataSourceProviderMetaValue = req.ProviderMeta.Raw diff --git a/tfsdk/serve_data_source_two_test.go b/internal/proto6server/serve_data_source_two_test.go similarity index 84% rename from tfsdk/serve_data_source_two_test.go rename to internal/proto6server/serve_data_source_two_test.go index 86507bb32..ed0766682 100644 --- a/tfsdk/serve_data_source_two_test.go +++ b/internal/proto6server/serve_data_source_two_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeDataSourceTypeTwo struct{} -func (dt testServeDataSourceTypeTwo) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeDataSourceTypeTwo) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "family": { Type: types.StringType, Optional: true, @@ -33,7 +34,7 @@ func (dt testServeDataSourceTypeTwo) GetSchema(_ context.Context) (Schema, diag. }, nil } -func (dt testServeDataSourceTypeTwo) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { +func (dt testServeDataSourceTypeTwo) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -83,7 +84,7 @@ type testServeDataSourceTwo struct { provider *testServeProvider } -func (r testServeDataSourceTwo) Read(ctx context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { +func (r testServeDataSourceTwo) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { r.provider.readDataSourceConfigValue = req.Config.Raw r.provider.readDataSourceConfigSchema = req.Config.Schema r.provider.readDataSourceProviderMetaValue = req.ProviderMeta.Raw diff --git a/tfsdk/serve_data_source_validate_config_test.go b/internal/proto6server/serve_data_source_validate_config_test.go similarity index 78% rename from tfsdk/serve_data_source_validate_config_test.go rename to internal/proto6server/serve_data_source_validate_config_test.go index cac311e82..1ec9da0c2 100644 --- a/tfsdk/serve_data_source_validate_config_test.go +++ b/internal/proto6server/serve_data_source_validate_config_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeDataSourceTypeValidateConfig struct{} -func (dt testServeDataSourceTypeValidateConfig) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeDataSourceTypeValidateConfig) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "string": { Type: types.StringType, Optional: true, @@ -23,7 +24,7 @@ func (dt testServeDataSourceTypeValidateConfig) GetSchema(_ context.Context) (Sc }, nil } -func (dt testServeDataSourceTypeValidateConfig) NewDataSource(_ context.Context, p Provider) (DataSource, diag.Diagnostics) { +func (dt testServeDataSourceTypeValidateConfig) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -59,11 +60,11 @@ type testServeDataSourceValidateConfig struct { provider *testServeProvider } -func (r testServeDataSourceValidateConfig) Read(ctx context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { +func (r testServeDataSourceValidateConfig) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeDataSourceValidateConfig) ValidateConfig(ctx context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { +func (r testServeDataSourceValidateConfig) ValidateConfig(ctx context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { r.provider.validateDataSourceConfigCalledDataSourceType = "test_validate_config" r.provider.validateDataSourceConfigImpl(ctx, req, resp) } diff --git a/tfsdk/serve_import.go b/internal/proto6server/serve_import.go similarity index 94% rename from tfsdk/serve_import.go rename to internal/proto6server/serve_import.go index ebe543d00..03aac0895 100644 --- a/tfsdk/serve_import.go +++ b/internal/proto6server/serve_import.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -15,7 +16,7 @@ import ( // currently designed for the most common use case of single resource import. type importedResource struct { Private []byte - State State + State tfsdk.State TypeName string } @@ -90,7 +91,7 @@ func (s *Server) importResourceState(ctx context.Context, req *tfprotov6.ImportR return } - resourceWithImportState, ok := resource.(ResourceWithImportState) + resourceWithImportState, ok := resource.(tfsdk.ResourceWithImportState) if !ok { // If there is a feature request for customizing this messaging, @@ -111,11 +112,11 @@ func (s *Server) importResourceState(ctx context.Context, req *tfprotov6.ImportR } emptyState := tftypes.NewValue(resourceSchema.TerraformType(ctx), nil) - importReq := ImportResourceStateRequest{ + importReq := tfsdk.ImportResourceStateRequest{ ID: req.ID, } - importResp := ImportResourceStateResponse{ - State: State{ + importResp := tfsdk.ImportResourceStateResponse{ + State: tfsdk.State{ Raw: emptyState, Schema: resourceSchema, }, diff --git a/tfsdk/serve_import_test.go b/internal/proto6server/serve_import_test.go similarity index 86% rename from tfsdk/serve_import_test.go rename to internal/proto6server/serve_import_test.go index 78e91f40b..046ccf7e1 100644 --- a/tfsdk/serve_import_test.go +++ b/internal/proto6server/serve_import_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -15,7 +16,7 @@ func TestServerImportResourceState(t *testing.T) { type testCase struct { req *tfprotov6.ImportResourceStateRequest - impl func(context.Context, ImportResourceStateRequest, *ImportResourceStateResponse) + impl func(context.Context, tfsdk.ImportResourceStateRequest, *tfsdk.ImportResourceStateResponse) resp *tfprotov6.ImportResourceStateResponse } @@ -27,7 +28,7 @@ func TestServerImportResourceState(t *testing.T) { TypeName: "test_import_state", }, - impl: func(ctx context.Context, req ImportResourceStateRequest, resp *ImportResourceStateResponse) { + impl: func(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { state := testServeResourceImportStateData{ Id: req.ID, } @@ -66,8 +67,8 @@ func TestServerImportResourceState(t *testing.T) { TypeName: "test_import_state", }, - impl: func(ctx context.Context, req ImportResourceStateRequest, resp *ImportResourceStateResponse) { - ResourceImportStatePassthroughID(ctx, tftypes.NewAttributePath().WithAttributeName("id"), req, resp) + impl: func(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + tfsdk.ResourceImportStatePassthroughID(ctx, tftypes.NewAttributePath().WithAttributeName("id"), req, resp) }, resp: &tfprotov6.ImportResourceStateResponse{ @@ -101,8 +102,8 @@ func TestServerImportResourceState(t *testing.T) { TypeName: "test_import_state", }, - impl: func(ctx context.Context, req ImportResourceStateRequest, resp *ImportResourceStateResponse) { - ResourceImportStateNotImplemented(ctx, "", resp) + impl: func(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + tfsdk.ResourceImportStateNotImplemented(ctx, "", resp) }, resp: &tfprotov6.ImportResourceStateResponse{ @@ -121,7 +122,7 @@ func TestServerImportResourceState(t *testing.T) { TypeName: "test_import_state", }, - impl: func(ctx context.Context, req ImportResourceStateRequest, resp *ImportResourceStateResponse) { + impl: func(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { resp.State.Raw = tftypes.NewValue(tftypes.String, "this should never work") }, @@ -142,7 +143,8 @@ func TestServerImportResourceState(t *testing.T) { TypeName: "test_import_state", }, - impl: func(ctx context.Context, req ImportResourceStateRequest, resp *ImportResourceStateResponse) {}, + impl: func(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + }, resp: &tfprotov6.ImportResourceStateResponse{ Diagnostics: []*tfprotov6.Diagnostic{ diff --git a/tfsdk/serve_provider_config_validators_test.go b/internal/proto6server/serve_provider_config_validators_test.go similarity index 64% rename from tfsdk/serve_provider_config_validators_test.go rename to internal/proto6server/serve_provider_config_validators_test.go index d2f07cdb0..acbff2f40 100644 --- a/tfsdk/serve_provider_config_validators_test.go +++ b/internal/proto6server/serve_provider_config_validators_test.go @@ -1,9 +1,10 @@ -package tfsdk +package proto6server import ( "context" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -12,9 +13,9 @@ type testServeProviderWithConfigValidators struct { *testServeProvider } -func (t *testServeProviderWithConfigValidators) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (t *testServeProviderWithConfigValidators) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "string": { Type: types.StringType, Optional: true, @@ -29,8 +30,8 @@ var testServeProviderWithConfigValidatorsType = tftypes.Object{ }, } -func (p testServeProviderWithConfigValidators) ConfigValidators(ctx context.Context) []ProviderConfigValidator { - return []ProviderConfigValidator{ +func (p testServeProviderWithConfigValidators) ConfigValidators(ctx context.Context) []tfsdk.ProviderConfigValidator { + return []tfsdk.ProviderConfigValidator{ newTestProviderConfigValidator(p.validateProviderConfigImpl), // Verify multiple validators newTestProviderConfigValidator(p.validateProviderConfigImpl), @@ -38,9 +39,9 @@ func (p testServeProviderWithConfigValidators) ConfigValidators(ctx context.Cont } type testProviderConfigValidator struct { - ProviderConfigValidator + tfsdk.ProviderConfigValidator - impl func(context.Context, ValidateProviderConfigRequest, *ValidateProviderConfigResponse) + impl func(context.Context, tfsdk.ValidateProviderConfigRequest, *tfsdk.ValidateProviderConfigResponse) } func (v testProviderConfigValidator) Description(ctx context.Context) string { @@ -49,10 +50,10 @@ func (v testProviderConfigValidator) Description(ctx context.Context) string { func (v testProviderConfigValidator) MarkdownDescription(ctx context.Context) string { return "**test** provider config validator" } -func (v testProviderConfigValidator) Validate(ctx context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { +func (v testProviderConfigValidator) Validate(ctx context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { v.impl(ctx, req, resp) } -func newTestProviderConfigValidator(impl func(context.Context, ValidateProviderConfigRequest, *ValidateProviderConfigResponse)) testProviderConfigValidator { +func newTestProviderConfigValidator(impl func(context.Context, tfsdk.ValidateProviderConfigRequest, *tfsdk.ValidateProviderConfigResponse)) testProviderConfigValidator { return testProviderConfigValidator{impl: impl} } diff --git a/tfsdk/serve_provider_test.go b/internal/proto6server/serve_provider_test.go similarity index 84% rename from tfsdk/serve_provider_test.go rename to internal/proto6server/serve_provider_test.go index 477e58fd9..b0f28f1dc 100644 --- a/tfsdk/serve_provider_test.go +++ b/internal/proto6server/serve_provider_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,78 +13,78 @@ import ( type testServeProvider struct { // validate provider config request - validateProviderConfigImpl func(context.Context, ValidateProviderConfigRequest, *ValidateProviderConfigResponse) + validateProviderConfigImpl func(context.Context, tfsdk.ValidateProviderConfigRequest, *tfsdk.ValidateProviderConfigResponse) // configure configuredVal tftypes.Value - configuredSchema Schema + configuredSchema tfsdk.Schema configuredTFVersion string // validate resource config request validateResourceConfigCalledResourceType string - validateResourceConfigImpl func(context.Context, ValidateResourceConfigRequest, *ValidateResourceConfigResponse) + validateResourceConfigImpl func(context.Context, tfsdk.ValidateResourceConfigRequest, *tfsdk.ValidateResourceConfigResponse) // upgrade resource state upgradeResourceStateCalledResourceType string // read resource request readResourceCurrentStateValue tftypes.Value - readResourceCurrentStateSchema Schema + readResourceCurrentStateSchema tfsdk.Schema readResourceProviderMetaValue tftypes.Value - readResourceProviderMetaSchema Schema - readResourceImpl func(context.Context, ReadResourceRequest, *ReadResourceResponse) + readResourceProviderMetaSchema tfsdk.Schema + readResourceImpl func(context.Context, tfsdk.ReadResourceRequest, *tfsdk.ReadResourceResponse) readResourceCalledResourceType string // plan resource change planResourceChangeCalledResourceType string planResourceChangeCalledAction string planResourceChangePriorStateValue tftypes.Value - planResourceChangePriorStateSchema Schema + planResourceChangePriorStateSchema tfsdk.Schema planResourceChangeProposedNewStateValue tftypes.Value - planResourceChangeProposedNewStateSchema Schema + planResourceChangeProposedNewStateSchema tfsdk.Schema planResourceChangeConfigValue tftypes.Value - planResourceChangeConfigSchema Schema + planResourceChangeConfigSchema tfsdk.Schema planResourceChangeProviderMetaValue tftypes.Value - planResourceChangeProviderMetaSchema Schema - modifyPlanFunc func(context.Context, ModifyResourcePlanRequest, *ModifyResourcePlanResponse) + planResourceChangeProviderMetaSchema tfsdk.Schema + modifyPlanFunc func(context.Context, tfsdk.ModifyResourcePlanRequest, *tfsdk.ModifyResourcePlanResponse) // apply resource change applyResourceChangeCalledResourceType string applyResourceChangeCalledAction string applyResourceChangePriorStateValue tftypes.Value - applyResourceChangePriorStateSchema Schema + applyResourceChangePriorStateSchema tfsdk.Schema applyResourceChangePlannedStateValue tftypes.Value - applyResourceChangePlannedStateSchema Schema + applyResourceChangePlannedStateSchema tfsdk.Schema applyResourceChangeConfigValue tftypes.Value - applyResourceChangeConfigSchema Schema + applyResourceChangeConfigSchema tfsdk.Schema applyResourceChangeProviderMetaValue tftypes.Value - applyResourceChangeProviderMetaSchema Schema - createFunc func(context.Context, CreateResourceRequest, *CreateResourceResponse) - updateFunc func(context.Context, UpdateResourceRequest, *UpdateResourceResponse) - deleteFunc func(context.Context, DeleteResourceRequest, *DeleteResourceResponse) + applyResourceChangeProviderMetaSchema tfsdk.Schema + createFunc func(context.Context, tfsdk.CreateResourceRequest, *tfsdk.CreateResourceResponse) + updateFunc func(context.Context, tfsdk.UpdateResourceRequest, *tfsdk.UpdateResourceResponse) + deleteFunc func(context.Context, tfsdk.DeleteResourceRequest, *tfsdk.DeleteResourceResponse) // import resource state importResourceStateCalledResourceType string - importStateFunc func(context.Context, ImportResourceStateRequest, *ImportResourceStateResponse) + importStateFunc func(context.Context, tfsdk.ImportResourceStateRequest, *tfsdk.ImportResourceStateResponse) // validate data source config request validateDataSourceConfigCalledDataSourceType string - validateDataSourceConfigImpl func(context.Context, ValidateDataSourceConfigRequest, *ValidateDataSourceConfigResponse) + validateDataSourceConfigImpl func(context.Context, tfsdk.ValidateDataSourceConfigRequest, *tfsdk.ValidateDataSourceConfigResponse) // read data source request readDataSourceConfigValue tftypes.Value - readDataSourceConfigSchema Schema + readDataSourceConfigSchema tfsdk.Schema readDataSourceProviderMetaValue tftypes.Value - readDataSourceProviderMetaSchema Schema - readDataSourceImpl func(context.Context, ReadDataSourceRequest, *ReadDataSourceResponse) + readDataSourceProviderMetaSchema tfsdk.Schema + readDataSourceImpl func(context.Context, tfsdk.ReadDataSourceRequest, *tfsdk.ReadDataSourceResponse) readDataSourceCalledDataSourceType string } -func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ +func (t *testServeProvider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ Version: 1, DeprecationMessage: "Deprecated in favor of other_resource", - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "required": { Type: types.StringType, Required: true, @@ -206,7 +207,7 @@ func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnosti }, // TODO: add tuples when we support them "single-nested-attributes": { - Attributes: SingleNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "foo": { Type: types.StringType, Optional: true, @@ -220,7 +221,7 @@ func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnosti Optional: true, }, "list-nested-attributes": { - Attributes: ListNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ "foo": { Type: types.StringType, Optional: true, @@ -230,11 +231,11 @@ func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnosti Type: types.NumberType, Required: true, }, - }, ListNestedAttributesOptions{}), + }, tfsdk.ListNestedAttributesOptions{}), Optional: true, }, "map-nested-attributes": { - Attributes: MapNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ "foo": { Type: types.StringType, Optional: true, @@ -244,11 +245,11 @@ func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnosti Type: types.NumberType, Required: true, }, - }, MapNestedAttributesOptions{}), + }, tfsdk.MapNestedAttributesOptions{}), Optional: true, }, "set-nested-attributes": { - Attributes: SetNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ "foo": { Type: types.StringType, Optional: true, @@ -258,13 +259,13 @@ func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnosti Type: types.NumberType, Required: true, }, - }, SetNestedAttributesOptions{}), + }, tfsdk.SetNestedAttributesOptions{}), Optional: true, }, }, - Blocks: map[string]Block{ + Blocks: map[string]tfsdk.Block{ "list-nested-blocks": { - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "foo": { Type: types.StringType, Optional: true, @@ -275,10 +276,10 @@ func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnosti Required: true, }, }, - NestingMode: BlockNestingModeList, + NestingMode: tfsdk.BlockNestingModeList, }, "set-nested-blocks": { - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "foo": { Type: types.StringType, Optional: true, @@ -289,7 +290,7 @@ func (t *testServeProvider) GetSchema(_ context.Context) (Schema, diag.Diagnosti Required: true, }, }, - NestingMode: BlockNestingModeSet, + NestingMode: tfsdk.BlockNestingModeSet, }, }, }, nil @@ -631,8 +632,8 @@ var testServeProviderProviderType = tftypes.Object{ }, } -func (t *testServeProvider) GetResources(_ context.Context) (map[string]ResourceType, diag.Diagnostics) { - return map[string]ResourceType{ +func (t *testServeProvider) GetResources(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ "test_one": testServeResourceTypeOne{}, "test_two": testServeResourceTypeTwo{}, "test_three": testServeResourceTypeThree{}, @@ -647,8 +648,8 @@ func (t *testServeProvider) GetResources(_ context.Context) (map[string]Resource }, nil } -func (t *testServeProvider) GetDataSources(_ context.Context) (map[string]DataSourceType, diag.Diagnostics) { - return map[string]DataSourceType{ +func (t *testServeProvider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { + return map[string]tfsdk.DataSourceType{ "test_one": testServeDataSourceTypeOne{}, "test_two": testServeDataSourceTypeTwo{}, "test_config_validators": testServeDataSourceTypeConfigValidators{}, @@ -656,7 +657,7 @@ func (t *testServeProvider) GetDataSources(_ context.Context) (map[string]DataSo }, nil } -func (t *testServeProvider) Configure(_ context.Context, req ConfigureProviderRequest, _ *ConfigureProviderResponse) { +func (t *testServeProvider) Configure(_ context.Context, req tfsdk.ConfigureProviderRequest, _ *tfsdk.ConfigureProviderResponse) { t.configuredVal = req.Config.Raw t.configuredSchema = req.Config.Schema t.configuredTFVersion = req.TerraformVersion @@ -666,10 +667,10 @@ type testServeProviderWithMetaSchema struct { *testServeProvider } -func (t *testServeProviderWithMetaSchema) GetMetaSchema(context.Context) (Schema, diag.Diagnostics) { - return Schema{ +func (t *testServeProviderWithMetaSchema) GetMetaSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ Version: 2, - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "foo": { Type: types.StringType, Required: true, diff --git a/tfsdk/serve_provider_validate_config_test.go b/internal/proto6server/serve_provider_validate_config_test.go similarity index 69% rename from tfsdk/serve_provider_validate_config_test.go rename to internal/proto6server/serve_provider_validate_config_test.go index 7f4f553ed..26d07e861 100644 --- a/tfsdk/serve_provider_validate_config_test.go +++ b/internal/proto6server/serve_provider_validate_config_test.go @@ -1,9 +1,10 @@ -package tfsdk +package proto6server import ( "context" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -12,9 +13,9 @@ type testServeProviderWithValidateConfig struct { *testServeProvider } -func (t *testServeProviderWithValidateConfig) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (t *testServeProviderWithValidateConfig) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "string": { Type: types.StringType, Optional: true, @@ -29,6 +30,6 @@ var testServeProviderWithValidateConfigType = tftypes.Object{ }, } -func (p testServeProviderWithValidateConfig) ValidateConfig(ctx context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { +func (p testServeProviderWithValidateConfig) ValidateConfig(ctx context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { p.validateProviderConfigImpl(ctx, req, resp) } diff --git a/tfsdk/serve_resource_attribute_plan_modifiers_test.go b/internal/proto6server/serve_resource_attribute_plan_modifiers_test.go similarity index 81% rename from tfsdk/serve_resource_attribute_plan_modifiers_test.go rename to internal/proto6server/serve_resource_attribute_plan_modifiers_test.go index acae0ee72..bfe5260bd 100644 --- a/tfsdk/serve_resource_attribute_plan_modifiers_test.go +++ b/internal/proto6server/serve_resource_attribute_plan_modifiers_test.go @@ -1,4 +1,4 @@ -package tfsdk +package proto6server import ( "context" @@ -6,19 +6,20 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ +func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ Version: 1, - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "name": { Required: true, Type: types.StringType, - PlanModifiers: []AttributePlanModifier{ + PlanModifiers: []tfsdk.AttributePlanModifier{ testWarningDiagModifier{}, // For the purposes of testing, these plan modifiers behave // differently for certain values of the attribute. @@ -30,7 +31,7 @@ func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Contex "size": { Required: true, Type: types.NumberType, - PlanModifiers: []AttributePlanModifier{RequiresReplaceIf(func(ctx context.Context, state, config attr.Value, path *tftypes.AttributePath) (bool, diag.Diagnostics) { + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplaceIf(func(ctx context.Context, state, config attr.Value, path *tftypes.AttributePath) (bool, diag.Diagnostics) { if state == nil && config == nil { return false, nil } @@ -38,11 +39,11 @@ func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Contex return true, nil } var stateVal, configVal types.Number - diags := ValueAs(ctx, state, &stateVal) + diags := tfsdk.ValueAs(ctx, state, &stateVal) if diags.HasError() { return false, diags } - diags.Append(ValueAs(ctx, config, &configVal)...) + diags.Append(tfsdk.ValueAs(ctx, config, &configVal)...) if diags.HasError() { return false, diags } @@ -57,22 +58,22 @@ func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Contex }}, "scratch_disk": { Optional: true, - Attributes: SingleNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "id": { Required: true, Type: types.StringType, - PlanModifiers: []AttributePlanModifier{ + PlanModifiers: []tfsdk.AttributePlanModifier{ testAttrPlanValueModifierTwo{}, }, }, "interface": { Required: true, Type: types.StringType, - PlanModifiers: []AttributePlanModifier{RequiresReplace()}, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, }, "filesystem": { Optional: true, - Attributes: SingleNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "size": { Optional: true, Type: types.NumberType, @@ -80,7 +81,7 @@ func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Contex "format": { Optional: true, Type: types.StringType, - PlanModifiers: []AttributePlanModifier{RequiresReplace()}, + PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, }, }), }, @@ -89,7 +90,7 @@ func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Contex "region": { Optional: true, Type: types.StringType, - PlanModifiers: []AttributePlanModifier{testAttrDefaultValueModifier{}}, + PlanModifiers: []tfsdk.AttributePlanModifier{testAttrDefaultValueModifier{}}, }, "computed_string_no_modifiers": { Computed: true, @@ -99,7 +100,7 @@ func (rt testServeResourceTypeAttributePlanModifiers) GetSchema(_ context.Contex }, nil } -func (rt testServeResourceTypeAttributePlanModifiers) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (rt testServeResourceTypeAttributePlanModifiers) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -208,7 +209,7 @@ type testServeResourceTypeAttributePlanModifiers struct{} type testWarningDiagModifier struct{} -func (t testWarningDiagModifier) Modify(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { +func (t testWarningDiagModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { resp.Diagnostics.AddWarning( "Warning diag", "This is a warning", @@ -225,7 +226,7 @@ func (t testWarningDiagModifier) MarkdownDescription(ctx context.Context) string type testErrorDiagModifier struct{} -func (t testErrorDiagModifier) Modify(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { +func (t testErrorDiagModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { resp.Diagnostics.AddError( "Error diag", "This is an error", @@ -242,7 +243,7 @@ func (t testErrorDiagModifier) MarkdownDescription(ctx context.Context) string { type testAttrPlanValueModifierOne struct{} -func (t testAttrPlanValueModifierOne) Modify(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { +func (t testAttrPlanValueModifierOne) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { attrVal, ok := req.AttributePlan.(types.String) if !ok { return @@ -265,7 +266,7 @@ func (t testAttrPlanValueModifierOne) MarkdownDescription(ctx context.Context) s type testAttrPlanValueModifierTwo struct{} -func (t testAttrPlanValueModifierTwo) Modify(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { +func (t testAttrPlanValueModifierTwo) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { attrVal, ok := req.AttributePlan.(types.String) if !ok { return @@ -288,7 +289,7 @@ func (t testAttrPlanValueModifierTwo) MarkdownDescription(ctx context.Context) s type testAttrDefaultValueModifier struct{} -func (t testAttrDefaultValueModifier) Modify(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { +func (t testAttrDefaultValueModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { if req.AttributeState == nil && req.AttributeConfig == nil { return } @@ -313,7 +314,7 @@ func (t testAttrDefaultValueModifier) MarkdownDescription(ctx context.Context) s type testRequiresReplaceFalseModifier struct{} // Modify sets RequiresReplace on the response to true. -func (m testRequiresReplaceFalseModifier) Modify(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { +func (m testRequiresReplaceFalseModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { resp.RequiresReplace = false } @@ -327,18 +328,18 @@ func (m testRequiresReplaceFalseModifier) MarkdownDescription(ctx context.Contex return "Always unsets requires replace." } -func (r testServeAttributePlanModifiers) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeAttributePlanModifiers) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeAttributePlanModifiers) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeAttributePlanModifiers) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeAttributePlanModifiers) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeAttributePlanModifiers) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeAttributePlanModifiers) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeAttributePlanModifiers) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } diff --git a/tfsdk/serve_resource_config_validators_test.go b/internal/proto6server/serve_resource_config_validators_test.go similarity index 70% rename from tfsdk/serve_resource_config_validators_test.go rename to internal/proto6server/serve_resource_config_validators_test.go index b97009ba2..84aa77304 100644 --- a/tfsdk/serve_resource_config_validators_test.go +++ b/internal/proto6server/serve_resource_config_validators_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeResourceTypeConfigValidators struct{} -func (dt testServeResourceTypeConfigValidators) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeResourceTypeConfigValidators) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "string": { Type: types.StringType, Optional: true, @@ -23,7 +24,7 @@ func (dt testServeResourceTypeConfigValidators) GetSchema(_ context.Context) (Sc }, nil } -func (dt testServeResourceTypeConfigValidators) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (dt testServeResourceTypeConfigValidators) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -59,23 +60,23 @@ type testServeResourceConfigValidators struct { provider *testServeProvider } -func (r testServeResourceConfigValidators) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceConfigValidators) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceConfigValidators) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceConfigValidators) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceConfigValidators) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceConfigValidators) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceConfigValidators) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceConfigValidators) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceConfigValidators) ConfigValidators(ctx context.Context) []ResourceConfigValidator { +func (r testServeResourceConfigValidators) ConfigValidators(ctx context.Context) []tfsdk.ResourceConfigValidator { r.provider.validateResourceConfigCalledResourceType = "test_config_validators" - return []ResourceConfigValidator{ + return []tfsdk.ResourceConfigValidator{ newTestResourceConfigValidator(r.provider.validateResourceConfigImpl), // Verify multiple validators newTestResourceConfigValidator(r.provider.validateResourceConfigImpl), @@ -83,9 +84,9 @@ func (r testServeResourceConfigValidators) ConfigValidators(ctx context.Context) } type testResourceConfigValidator struct { - ResourceConfigValidator + tfsdk.ResourceConfigValidator - impl func(context.Context, ValidateResourceConfigRequest, *ValidateResourceConfigResponse) + impl func(context.Context, tfsdk.ValidateResourceConfigRequest, *tfsdk.ValidateResourceConfigResponse) } func (v testResourceConfigValidator) Description(ctx context.Context) string { @@ -94,10 +95,10 @@ func (v testResourceConfigValidator) Description(ctx context.Context) string { func (v testResourceConfigValidator) MarkdownDescription(ctx context.Context) string { return "**test** resource config validator" } -func (v testResourceConfigValidator) Validate(ctx context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { +func (v testResourceConfigValidator) Validate(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { v.impl(ctx, req, resp) } -func newTestResourceConfigValidator(impl func(context.Context, ValidateResourceConfigRequest, *ValidateResourceConfigResponse)) testResourceConfigValidator { +func newTestResourceConfigValidator(impl func(context.Context, tfsdk.ValidateResourceConfigRequest, *tfsdk.ValidateResourceConfigResponse)) testResourceConfigValidator { return testResourceConfigValidator{impl: impl} } diff --git a/tfsdk/serve_resource_import_state_not_implemented_test.go b/internal/proto6server/serve_resource_import_state_not_implemented_test.go similarity index 73% rename from tfsdk/serve_resource_import_state_not_implemented_test.go rename to internal/proto6server/serve_resource_import_state_not_implemented_test.go index c8173de67..556e053dd 100644 --- a/tfsdk/serve_resource_import_state_not_implemented_test.go +++ b/internal/proto6server/serve_resource_import_state_not_implemented_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeResourceTypeImportStateNotImplemented struct{} -func (dt testServeResourceTypeImportStateNotImplemented) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeResourceTypeImportStateNotImplemented) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Type: types.StringType, Computed: true, @@ -23,7 +24,7 @@ func (dt testServeResourceTypeImportStateNotImplemented) GetSchema(_ context.Con }, nil } -func (dt testServeResourceTypeImportStateNotImplemented) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (dt testServeResourceTypeImportStateNotImplemented) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -53,15 +54,15 @@ type testServeResourceImportStateNotImplemented struct { provider *testServeProvider } -func (r testServeResourceImportStateNotImplemented) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceImportStateNotImplemented) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceImportStateNotImplemented) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceImportStateNotImplemented) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceImportStateNotImplemented) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceImportStateNotImplemented) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceImportStateNotImplemented) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceImportStateNotImplemented) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } diff --git a/tfsdk/serve_resource_import_state_test.go b/internal/proto6server/serve_resource_import_state_test.go similarity index 80% rename from tfsdk/serve_resource_import_state_test.go rename to internal/proto6server/serve_resource_import_state_test.go index 33e67424d..c5c3b845a 100644 --- a/tfsdk/serve_resource_import_state_test.go +++ b/internal/proto6server/serve_resource_import_state_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeResourceTypeImportState struct{} -func (dt testServeResourceTypeImportState) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeResourceTypeImportState) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Type: types.StringType, Computed: true, @@ -31,7 +32,7 @@ func (dt testServeResourceTypeImportState) GetSchema(_ context.Context) (Schema, }, nil } -func (dt testServeResourceTypeImportState) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (dt testServeResourceTypeImportState) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -85,19 +86,19 @@ type testServeResourceImportState struct { provider *testServeProvider } -func (r testServeResourceImportState) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceImportState) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceImportState) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceImportState) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceImportState) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceImportState) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceImportState) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceImportState) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceImportState) ImportState(ctx context.Context, req ImportResourceStateRequest, resp *ImportResourceStateResponse) { +func (r testServeResourceImportState) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { r.provider.importResourceStateCalledResourceType = "test_import_state" r.provider.importStateFunc(ctx, req, resp) } diff --git a/tfsdk/serve_resource_one_test.go b/internal/proto6server/serve_resource_one_test.go similarity index 83% rename from tfsdk/serve_resource_one_test.go rename to internal/proto6server/serve_resource_one_test.go index 9348c16ae..1679e322e 100644 --- a/tfsdk/serve_resource_one_test.go +++ b/internal/proto6server/serve_resource_one_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,10 +13,10 @@ import ( type testServeResourceTypeOne struct{} -func (rt testServeResourceTypeOne) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ +func (rt testServeResourceTypeOne) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ Version: 1, - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "name": { Required: true, Type: types.StringType, @@ -32,7 +33,7 @@ func (rt testServeResourceTypeOne) GetSchema(_ context.Context) (Schema, diag.Di }, nil } -func (rt testServeResourceTypeOne) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (rt testServeResourceTypeOne) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -81,7 +82,7 @@ type testServeResourceOne struct { provider *testServeProvider } -func (r testServeResourceOne) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceOne) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { r.provider.applyResourceChangePlannedStateValue = req.Plan.Raw r.provider.applyResourceChangePlannedStateSchema = req.Plan.Schema r.provider.applyResourceChangeConfigValue = req.Config.Raw @@ -93,7 +94,7 @@ func (r testServeResourceOne) Create(ctx context.Context, req CreateResourceRequ r.provider.createFunc(ctx, req, resp) } -func (r testServeResourceOne) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceOne) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { r.provider.readResourceCurrentStateValue = req.State.Raw r.provider.readResourceCurrentStateSchema = req.State.Schema r.provider.readResourceProviderMetaValue = req.ProviderMeta.Raw @@ -102,7 +103,7 @@ func (r testServeResourceOne) Read(ctx context.Context, req ReadResourceRequest, r.provider.readResourceImpl(ctx, req, resp) } -func (r testServeResourceOne) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceOne) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { r.provider.applyResourceChangePriorStateValue = req.State.Raw r.provider.applyResourceChangePriorStateSchema = req.State.Schema r.provider.applyResourceChangePlannedStateValue = req.Plan.Raw @@ -116,7 +117,7 @@ func (r testServeResourceOne) Update(ctx context.Context, req UpdateResourceRequ r.provider.updateFunc(ctx, req, resp) } -func (r testServeResourceOne) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceOne) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { r.provider.applyResourceChangePriorStateValue = req.State.Raw r.provider.applyResourceChangePriorStateSchema = req.State.Schema r.provider.applyResourceChangeProviderMetaValue = req.ProviderMeta.Raw diff --git a/tfsdk/serve_resource_three_test.go b/internal/proto6server/serve_resource_three_test.go similarity index 81% rename from tfsdk/serve_resource_three_test.go rename to internal/proto6server/serve_resource_three_test.go index 0f50b725f..b2885e6d0 100644 --- a/tfsdk/serve_resource_three_test.go +++ b/internal/proto6server/serve_resource_three_test.go @@ -1,19 +1,20 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func (rt testServeResourceTypeThree) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ +func (rt testServeResourceTypeThree) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ Version: 1, - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "name": { Required: true, Type: types.StringType, @@ -28,7 +29,7 @@ func (rt testServeResourceTypeThree) GetSchema(_ context.Context) (Schema, diag. }, "map_nested": { Required: true, - Attributes: MapNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ "computed_string": { Computed: true, Type: types.StringType, @@ -37,13 +38,13 @@ func (rt testServeResourceTypeThree) GetSchema(_ context.Context) (Schema, diag. Optional: true, Type: types.StringType, }, - }, MapNestedAttributesOptions{}), + }, tfsdk.MapNestedAttributesOptions{}), }, }, }, nil } -func (rt testServeResourceTypeThree) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (rt testServeResourceTypeThree) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -121,18 +122,18 @@ type testServeResourceThree struct { type testServeResourceTypeThree struct{} -func (r testServeResourceThree) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceThree) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceThree) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceThree) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceThree) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceThree) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceThree) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceThree) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } diff --git a/tfsdk/serve_resource_two_test.go b/internal/proto6server/serve_resource_two_test.go similarity index 85% rename from tfsdk/serve_resource_two_test.go rename to internal/proto6server/serve_resource_two_test.go index ce8346e55..a40938fde 100644 --- a/tfsdk/serve_resource_two_test.go +++ b/internal/proto6server/serve_resource_two_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeResourceTypeTwo struct{} -func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Optional: true, Computed: true, @@ -23,7 +24,7 @@ func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (Schema, diag.Di "disks": { Optional: true, Computed: true, - Attributes: ListNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ "name": { Required: true, Type: types.StringType, @@ -36,12 +37,12 @@ func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (Schema, diag.Di Required: true, Type: types.BoolType, }, - }, ListNestedAttributesOptions{}), + }, tfsdk.ListNestedAttributesOptions{}), }, }, - Blocks: map[string]Block{ + Blocks: map[string]tfsdk.Block{ "list_nested_blocks": { - Attributes: map[string]Attribute{ + Attributes: map[string]tfsdk.Attribute{ "required_bool": { Required: true, Type: types.BoolType, @@ -55,13 +56,13 @@ func (rt testServeResourceTypeTwo) GetSchema(_ context.Context) (Schema, diag.Di Type: types.StringType, }, }, - NestingMode: BlockNestingModeList, + NestingMode: tfsdk.BlockNestingModeList, }, }, }, nil } -func (rt testServeResourceTypeTwo) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (rt testServeResourceTypeTwo) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -162,7 +163,7 @@ type testServeResourceTwo struct { provider *testServeProvider } -func (r testServeResourceTwo) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceTwo) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { r.provider.applyResourceChangePlannedStateValue = req.Plan.Raw r.provider.applyResourceChangePlannedStateSchema = req.Plan.Schema r.provider.applyResourceChangeConfigValue = req.Config.Raw @@ -174,7 +175,7 @@ func (r testServeResourceTwo) Create(ctx context.Context, req CreateResourceRequ r.provider.createFunc(ctx, req, resp) } -func (r testServeResourceTwo) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceTwo) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { r.provider.readResourceCurrentStateValue = req.State.Raw r.provider.readResourceCurrentStateSchema = req.State.Schema r.provider.readResourceProviderMetaValue = req.ProviderMeta.Raw @@ -183,7 +184,7 @@ func (r testServeResourceTwo) Read(ctx context.Context, req ReadResourceRequest, r.provider.readResourceImpl(ctx, req, resp) } -func (r testServeResourceTwo) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceTwo) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { r.provider.applyResourceChangePriorStateValue = req.State.Raw r.provider.applyResourceChangePriorStateSchema = req.State.Schema r.provider.applyResourceChangePlannedStateValue = req.Plan.Raw @@ -197,7 +198,7 @@ func (r testServeResourceTwo) Update(ctx context.Context, req UpdateResourceRequ r.provider.updateFunc(ctx, req, resp) } -func (r testServeResourceTwo) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceTwo) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { r.provider.applyResourceChangePriorStateValue = req.State.Raw r.provider.applyResourceChangePriorStateSchema = req.State.Schema r.provider.applyResourceChangeProviderMetaValue = req.ProviderMeta.Raw @@ -207,7 +208,7 @@ func (r testServeResourceTwo) Delete(ctx context.Context, req DeleteResourceRequ r.provider.deleteFunc(ctx, req, resp) } -func (r testServeResourceTwo) ModifyPlan(ctx context.Context, req ModifyResourcePlanRequest, resp *ModifyResourcePlanResponse) { +func (r testServeResourceTwo) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { r.provider.planResourceChangePriorStateValue = req.State.Raw r.provider.planResourceChangePriorStateSchema = req.State.Schema r.provider.planResourceChangeProposedNewStateValue = req.Plan.Raw diff --git a/tfsdk/serve_resource_upgrade_state_empty_test.go b/internal/proto6server/serve_resource_upgrade_state_empty_test.go similarity index 75% rename from tfsdk/serve_resource_upgrade_state_empty_test.go rename to internal/proto6server/serve_resource_upgrade_state_empty_test.go index 750ad0f59..ab2ba86a6 100644 --- a/tfsdk/serve_resource_upgrade_state_empty_test.go +++ b/internal/proto6server/serve_resource_upgrade_state_empty_test.go @@ -1,22 +1,23 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -var _ ResourceWithUpgradeState = testServeResourceUpgradeStateEmpty{} +var _ tfsdk.ResourceWithUpgradeState = testServeResourceUpgradeStateEmpty{} type testServeResourceTypeUpgradeStateEmpty struct{} -func (t testServeResourceTypeUpgradeStateEmpty) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (t testServeResourceTypeUpgradeStateEmpty) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Type: types.StringType, Computed: true, @@ -34,7 +35,7 @@ func (t testServeResourceTypeUpgradeStateEmpty) GetSchema(_ context.Context) (Sc }, nil } -func (t testServeResourceTypeUpgradeStateEmpty) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (t testServeResourceTypeUpgradeStateEmpty) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -75,19 +76,19 @@ type testServeResourceUpgradeStateEmpty struct { provider *testServeProvider } -func (r testServeResourceUpgradeStateEmpty) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceUpgradeStateEmpty) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateEmpty) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceUpgradeStateEmpty) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateEmpty) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceUpgradeStateEmpty) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateEmpty) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceUpgradeStateEmpty) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateEmpty) UpgradeState(ctx context.Context) map[int64]ResourceStateUpgrader { +func (r testServeResourceUpgradeStateEmpty) UpgradeState(ctx context.Context) map[int64]tfsdk.ResourceStateUpgrader { return nil } diff --git a/tfsdk/serve_resource_upgrade_state_not_implemented_test.go b/internal/proto6server/serve_resource_upgrade_state_not_implemented_test.go similarity index 72% rename from tfsdk/serve_resource_upgrade_state_not_implemented_test.go rename to internal/proto6server/serve_resource_upgrade_state_not_implemented_test.go index 6fbe34a0d..6331ea648 100644 --- a/tfsdk/serve_resource_upgrade_state_not_implemented_test.go +++ b/internal/proto6server/serve_resource_upgrade_state_not_implemented_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeResourceTypeUpgradeStateNotImplemented struct{} -func (t testServeResourceTypeUpgradeStateNotImplemented) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (t testServeResourceTypeUpgradeStateNotImplemented) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Type: types.StringType, Computed: true, @@ -32,7 +33,7 @@ func (t testServeResourceTypeUpgradeStateNotImplemented) GetSchema(_ context.Con }, nil } -func (t testServeResourceTypeUpgradeStateNotImplemented) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (t testServeResourceTypeUpgradeStateNotImplemented) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -73,18 +74,18 @@ type testServeResourceUpgradeStateNotImplemented struct { provider *testServeProvider } -func (r testServeResourceUpgradeStateNotImplemented) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceUpgradeStateNotImplemented) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateNotImplemented) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceUpgradeStateNotImplemented) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateNotImplemented) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceUpgradeStateNotImplemented) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateNotImplemented) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceUpgradeStateNotImplemented) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeStateNotImplemented) ImportState(ctx context.Context, req ImportResourceStateRequest, resp *ImportResourceStateResponse) { - ResourceImportStateNotImplemented(ctx, "intentionally not implemented", resp) +func (r testServeResourceUpgradeStateNotImplemented) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + tfsdk.ResourceImportStateNotImplemented(ctx, "intentionally not implemented", resp) } diff --git a/tfsdk/serve_resource_upgrade_state_test.go b/internal/proto6server/serve_resource_upgrade_state_test.go similarity index 84% rename from tfsdk/serve_resource_upgrade_state_test.go rename to internal/proto6server/serve_resource_upgrade_state_test.go index 27c72773a..b1d716398 100644 --- a/tfsdk/serve_resource_upgrade_state_test.go +++ b/internal/proto6server/serve_resource_upgrade_state_test.go @@ -1,4 +1,4 @@ -package tfsdk +package proto6server import ( "context" @@ -6,18 +6,19 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) -var _ ResourceWithUpgradeState = testServeResourceUpgradeState{} +var _ tfsdk.ResourceWithUpgradeState = testServeResourceUpgradeState{} type testServeResourceTypeUpgradeState struct{} -func (t testServeResourceTypeUpgradeState) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (t testServeResourceTypeUpgradeState) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Type: types.StringType, Computed: true, @@ -35,7 +36,7 @@ func (t testServeResourceTypeUpgradeState) GetSchema(_ context.Context) (Schema, }, nil } -func (t testServeResourceTypeUpgradeState) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (t testServeResourceTypeUpgradeState) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -111,24 +112,24 @@ type testServeResourceUpgradeState struct { provider *testServeProvider } -func (r testServeResourceUpgradeState) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceUpgradeState) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeState) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceUpgradeState) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeState) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceUpgradeState) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeState) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceUpgradeState) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceUpgradeState) UpgradeState(ctx context.Context) map[int64]ResourceStateUpgrader { +func (r testServeResourceUpgradeState) UpgradeState(ctx context.Context) map[int64]tfsdk.ResourceStateUpgrader { r.provider.upgradeResourceStateCalledResourceType = "test_upgrade_state" - return map[int64]ResourceStateUpgrader{ + return map[int64]tfsdk.ResourceStateUpgrader{ 0: { // Successful state upgrade using RawState.Unmarshal() and DynamicValue - StateUpgrader: func(ctx context.Context, req UpgradeResourceStateRequest, resp *UpgradeResourceStateResponse) { + StateUpgrader: func(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { rawStateValue, err := req.RawState.Unmarshal(testServeResourceTypeUpgradeStateTftypeV0) if err != nil { @@ -199,7 +200,7 @@ func (r testServeResourceUpgradeState) UpgradeState(ctx context.Context) map[int }, }, 1: { // Successful state upgrade using RawState.JSON and DynamicValue - StateUpgrader: func(ctx context.Context, req UpgradeResourceStateRequest, resp *UpgradeResourceStateResponse) { + StateUpgrader: func(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { var rawState testServeResourceUpgradeStateDataV1 if err := json.Unmarshal(req.RawState.JSON, &rawState); err != nil { @@ -238,8 +239,8 @@ func (r testServeResourceUpgradeState) UpgradeState(ctx context.Context) map[int }, }, 2: { // Successful state upgrade with PriorSchema and State - PriorSchema: &Schema{ - Attributes: map[string]Attribute{ + PriorSchema: &tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Type: types.StringType, Computed: true, @@ -254,7 +255,7 @@ func (r testServeResourceUpgradeState) UpgradeState(ctx context.Context) map[int }, }, }, - StateUpgrader: func(ctx context.Context, req UpgradeResourceStateRequest, resp *UpgradeResourceStateResponse) { + StateUpgrader: func(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { var priorStateData testServeResourceUpgradeStateDataV2 resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...) @@ -277,8 +278,8 @@ func (r testServeResourceUpgradeState) UpgradeState(ctx context.Context) map[int }, }, 3: { // Incorrect PriorSchema - PriorSchema: &Schema{ - Attributes: map[string]Attribute{ + PriorSchema: &tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "id": { Type: types.StringType, Computed: true, @@ -293,12 +294,12 @@ func (r testServeResourceUpgradeState) UpgradeState(ctx context.Context) map[int }, }, }, - StateUpgrader: func(ctx context.Context, req UpgradeResourceStateRequest, resp *UpgradeResourceStateResponse) { + StateUpgrader: func(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { // Expect error before reaching this logic. }, }, 4: { // Missing upgraded resource data - StateUpgrader: func(ctx context.Context, req UpgradeResourceStateRequest, resp *UpgradeResourceStateResponse) { + StateUpgrader: func(ctx context.Context, req tfsdk.UpgradeResourceStateRequest, resp *tfsdk.UpgradeResourceStateResponse) { // Purposfully not setting resp.DynamicValue or resp.State }, }, diff --git a/tfsdk/serve_resource_validate_config_test.go b/internal/proto6server/serve_resource_validate_config_test.go similarity index 75% rename from tfsdk/serve_resource_validate_config_test.go rename to internal/proto6server/serve_resource_validate_config_test.go index 41a40fb08..58c81222c 100644 --- a/tfsdk/serve_resource_validate_config_test.go +++ b/internal/proto6server/serve_resource_validate_config_test.go @@ -1,10 +1,11 @@ -package tfsdk +package proto6server import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -12,9 +13,9 @@ import ( type testServeResourceTypeValidateConfig struct{} -func (dt testServeResourceTypeValidateConfig) GetSchema(_ context.Context) (Schema, diag.Diagnostics) { - return Schema{ - Attributes: map[string]Attribute{ +func (dt testServeResourceTypeValidateConfig) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ "string": { Type: types.StringType, Optional: true, @@ -23,7 +24,7 @@ func (dt testServeResourceTypeValidateConfig) GetSchema(_ context.Context) (Sche }, nil } -func (dt testServeResourceTypeValidateConfig) NewResource(_ context.Context, p Provider) (Resource, diag.Diagnostics) { +func (dt testServeResourceTypeValidateConfig) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { provider, ok := p.(*testServeProvider) if !ok { prov, ok := p.(*testServeProviderWithMetaSchema) @@ -59,20 +60,20 @@ type testServeResourceValidateConfig struct { provider *testServeProvider } -func (r testServeResourceValidateConfig) Create(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { +func (r testServeResourceValidateConfig) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceValidateConfig) Read(ctx context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { +func (r testServeResourceValidateConfig) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceValidateConfig) Update(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { +func (r testServeResourceValidateConfig) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceValidateConfig) Delete(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { +func (r testServeResourceValidateConfig) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Intentionally blank. Not expected to be called during testing. } -func (r testServeResourceValidateConfig) ValidateConfig(ctx context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { +func (r testServeResourceValidateConfig) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { r.provider.validateResourceConfigCalledResourceType = "test_validate_config" r.provider.validateResourceConfigImpl(ctx, req, resp) } diff --git a/tfsdk/serve_test.go b/internal/proto6server/serve_test.go similarity index 97% rename from tfsdk/serve_test.go rename to internal/proto6server/serve_test.go index ded6c5aeb..f0cc78cda 100644 --- a/tfsdk/serve_test.go +++ b/internal/proto6server/serve_test.go @@ -1,4 +1,4 @@ -package tfsdk +package proto6server import ( "context" @@ -11,6 +11,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -59,8 +60,8 @@ func TestServerCancelInFlightContexts(t *testing.T) { func TestMarkComputedNilsAsUnknown(t *testing.T) { t.Parallel() - s := Schema{ - Attributes: map[string]Attribute{ + s := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ // values should be left alone "string-value": { Type: types.StringType, @@ -116,7 +117,7 @@ func TestMarkComputedNilsAsUnknown(t *testing.T) { }, // nil nested attributes should be unknown "nested-nil-optional-computed": { - Attributes: SingleNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ "string-nil": { Type: types.StringType, Optional: true, @@ -133,7 +134,7 @@ func TestMarkComputedNilsAsUnknown(t *testing.T) { }, // non-nil nested attributes should be left alone on the top level "nested-value-optional-computed": { - Attributes: SingleNestedAttributes(map[string]Attribute{ + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ // nested computed attributes should be unknown "string-nil": { Type: types.StringType, @@ -358,7 +359,7 @@ func TestServerValidateProviderConfig(t *testing.T) { type testCase struct { // request input config tftypes.Value - provider Provider + provider tfsdk.Provider providerType tftypes.Type // response expectations @@ -606,7 +607,8 @@ func TestServerValidateProviderConfig(t *testing.T) { }), provider: &testServeProviderWithConfigValidators{ &testServeProvider{ - validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) {}, + validateProviderConfigImpl: func(_ context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { + }, }, }, providerType: testServeProviderWithConfigValidatorsType, @@ -617,7 +619,7 @@ func TestServerValidateProviderConfig(t *testing.T) { }), provider: &testServeProviderWithConfigValidators{ &testServeProvider{ - validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { + validateProviderConfigImpl: func(_ context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { if len(resp.Diagnostics) == 0 { resp.Diagnostics.AddError( "This is an error", @@ -654,7 +656,7 @@ func TestServerValidateProviderConfig(t *testing.T) { }), provider: &testServeProviderWithConfigValidators{ &testServeProvider{ - validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { + validateProviderConfigImpl: func(_ context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { if len(resp.Diagnostics) == 0 { resp.Diagnostics.AddAttributeWarning( tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), @@ -713,7 +715,8 @@ func TestServerValidateProviderConfig(t *testing.T) { }), provider: &testServeProviderWithValidateConfig{ &testServeProvider{ - validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) {}, + validateProviderConfigImpl: func(_ context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { + }, }, }, providerType: testServeProviderWithValidateConfigType, @@ -724,7 +727,7 @@ func TestServerValidateProviderConfig(t *testing.T) { }), provider: &testServeProviderWithValidateConfig{ &testServeProvider{ - validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { + validateProviderConfigImpl: func(_ context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { resp.Diagnostics.AddError( "This is an error", "Oops.", @@ -748,7 +751,7 @@ func TestServerValidateProviderConfig(t *testing.T) { }), provider: &testServeProviderWithValidateConfig{ &testServeProvider{ - validateProviderConfigImpl: func(_ context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) { + validateProviderConfigImpl: func(_ context.Context, req tfsdk.ValidateProviderConfigRequest, resp *tfsdk.ValidateProviderConfigResponse) { resp.Diagnostics.AddAttributeWarning( tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), "This is a warning", @@ -1191,7 +1194,7 @@ func TestServerValidateResourceConfig(t *testing.T) { resource string resourceType tftypes.Type - impl func(context.Context, ValidateResourceConfigRequest, *ValidateResourceConfigResponse) + impl func(context.Context, tfsdk.ValidateResourceConfigRequest, *tfsdk.ValidateResourceConfigResponse) // response expectations expectedDiags []*tfprotov6.Diagnostic @@ -1214,7 +1217,8 @@ func TestServerValidateResourceConfig(t *testing.T) { resource: "test_config_validators", resourceType: testServeResourceTypeConfigValidatorsType, - impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) {}, + impl: func(_ context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { + }, }, "config_validators_one_diag": { config: tftypes.NewValue(testServeResourceTypeConfigValidatorsType, map[string]tftypes.Value{ @@ -1223,7 +1227,7 @@ func TestServerValidateResourceConfig(t *testing.T) { resource: "test_config_validators", resourceType: testServeResourceTypeConfigValidatorsType, - impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { if len(resp.Diagnostics) == 0 { resp.Diagnostics.AddError( "This is an error", @@ -1258,7 +1262,7 @@ func TestServerValidateResourceConfig(t *testing.T) { resource: "test_config_validators", resourceType: testServeResourceTypeConfigValidatorsType, - impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { if len(resp.Diagnostics) == 0 { resp.Diagnostics.AddAttributeWarning( tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), @@ -1315,7 +1319,8 @@ func TestServerValidateResourceConfig(t *testing.T) { resource: "test_validate_config", resourceType: testServeResourceTypeValidateConfigType, - impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) {}, + impl: func(_ context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { + }, }, "validate_config_one_diag": { config: tftypes.NewValue(testServeResourceTypeValidateConfigType, map[string]tftypes.Value{ @@ -1324,7 +1329,7 @@ func TestServerValidateResourceConfig(t *testing.T) { resource: "test_validate_config", resourceType: testServeResourceTypeValidateConfigType, - impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { resp.Diagnostics.AddError( "This is an error", "Oops.", @@ -1346,7 +1351,7 @@ func TestServerValidateResourceConfig(t *testing.T) { resource: "test_validate_config", resourceType: testServeResourceTypeValidateConfigType, - impl: func(_ context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { resp.Diagnostics.AddAttributeWarning( tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), "This is a warning", @@ -1603,7 +1608,7 @@ func TestServerUpgradeResourceState(t *testing.T) { }, }, TypeName: "test_upgrade_state_not_implemented", // Framework should allow non-ResourceWithUpgradeState - Version: 1, // Must match current Schema version to trigger framework implementation + Version: 1, // Must match current tfsdk.Schema version to trigger framework implementation }, expectedResponse: &tfprotov6.UpgradeResourceStateResponse{ Diagnostics: []*tfprotov6.Diagnostic{ @@ -1626,7 +1631,7 @@ func TestServerUpgradeResourceState(t *testing.T) { "required_attribute": "true", }), TypeName: "test_upgrade_state_not_implemented", // Framework should allow non-ResourceWithUpgradeState - Version: 1, // Must match current Schema version to trigger framework implementation + Version: 1, // Must match current tfsdk.Schema version to trigger framework implementation }, expectedResponse: &tfprotov6.UpgradeResourceStateResponse{ UpgradedState: testNewDynamicValue(t, schemaType, map[string]tftypes.Value{ @@ -1642,7 +1647,7 @@ func TestServerUpgradeResourceState(t *testing.T) { JSON: []byte(`{"nonexistent_attribute":"value"}`), }, TypeName: "test_upgrade_state_not_implemented", // Framework should allow non-ResourceWithUpgradeState - Version: 1, // Must match current Schema version to trigger framework implementation + Version: 1, // Must match current tfsdk.Schema version to trigger framework implementation }, expectedResponse: &tfprotov6.UpgradeResourceStateResponse{ Diagnostics: []*tfprotov6.Diagnostic{ @@ -1758,7 +1763,7 @@ func TestServerReadResource(t *testing.T) { resource string resourceType tftypes.Type - impl func(context.Context, ReadResourceRequest, *ReadResourceResponse) + impl func(context.Context, tfsdk.ReadResourceRequest, *tfsdk.ReadResourceResponse) // response expectations expectedNewState tftypes.Value @@ -1776,7 +1781,7 @@ func TestServerReadResource(t *testing.T) { resource: "test_one", resourceType: testServeResourceTypeOneType, - impl: func(_ context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "foo"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -1813,7 +1818,7 @@ func TestServerReadResource(t *testing.T) { "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), }), - impl: func(_ context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "my name"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -1844,7 +1849,7 @@ func TestServerReadResource(t *testing.T) { resource: "test_one", resourceType: testServeResourceTypeOneType, - impl: func(_ context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, nil) }, @@ -1873,7 +1878,7 @@ func TestServerReadResource(t *testing.T) { resource: "test_two", resourceType: testServeResourceTypeTwoType, - impl: func(_ context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "123foo"), "disks": tftypes.NewValue(tftypes.List{ @@ -1986,7 +1991,7 @@ func TestServerReadResource(t *testing.T) { resource: "test_two", resourceType: testServeResourceTypeTwoType, - impl: func(_ context.Context, req ReadResourceRequest, resp *ReadResourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "123foo"), "disks": tftypes.NewValue(tftypes.List{ @@ -2113,7 +2118,7 @@ func TestServerReadResource(t *testing.T) { testServer := &Server{ Provider: s, } - var pmSchema Schema + var pmSchema tfsdk.Schema if tc.providerMeta.Type() != nil { sWithMeta := &testServeProviderWithMetaSchema{s} testServer.Provider = sWithMeta @@ -2214,7 +2219,7 @@ func TestServerPlanResourceChange(t *testing.T) { resource string resourceType tftypes.Type - modifyPlanFunc func(context.Context, ModifyResourcePlanRequest, *ModifyResourcePlanResponse) + modifyPlanFunc func(context.Context, tfsdk.ModifyResourcePlanRequest, *tfsdk.ModifyResourcePlanResponse) // response expectations expectedPlannedState tftypes.Value @@ -2838,7 +2843,7 @@ func TestServerPlanResourceChange(t *testing.T) { }), }), }), - modifyPlanFunc: func(ctx context.Context, req ModifyResourcePlanRequest, resp *ModifyResourcePlanResponse) { + modifyPlanFunc: func(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { resp.Plan.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "123456"), "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ @@ -3049,7 +3054,7 @@ func TestServerPlanResourceChange(t *testing.T) { }), }), }), - modifyPlanFunc: func(ctx context.Context, req ModifyResourcePlanRequest, resp *ModifyResourcePlanResponse) { + modifyPlanFunc: func(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { resp.RequiresReplace = []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("id")} }, expectedRequiresReplace: []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("id")}, @@ -3205,7 +3210,7 @@ func TestServerPlanResourceChange(t *testing.T) { }), }), }), - modifyPlanFunc: func(ctx context.Context, req ModifyResourcePlanRequest, resp *ModifyResourcePlanResponse) { + modifyPlanFunc: func(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { resp.RequiresReplace = []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("id")} resp.Diagnostics.AddWarning("I'm warning you", "You have been warned") }, @@ -3369,7 +3374,7 @@ func TestServerPlanResourceChange(t *testing.T) { }), }), }), - modifyPlanFunc: func(ctx context.Context, req ModifyResourcePlanRequest, resp *ModifyResourcePlanResponse) { + modifyPlanFunc: func(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) { resp.RequiresReplace = []*tftypes.AttributePath{tftypes.NewAttributePath().WithAttributeName("id")} resp.Diagnostics.AddError("This is an error", "More details about the error") }, @@ -4263,9 +4268,9 @@ func TestServerApplyResourceChange(t *testing.T) { action string resourceType tftypes.Type - create func(context.Context, CreateResourceRequest, *CreateResourceResponse) - update func(context.Context, UpdateResourceRequest, *UpdateResourceResponse) - destroy func(context.Context, DeleteResourceRequest, *DeleteResourceResponse) + create func(context.Context, tfsdk.CreateResourceRequest, *tfsdk.CreateResourceResponse) + update func(context.Context, tfsdk.UpdateResourceRequest, *tfsdk.UpdateResourceResponse) + destroy func(context.Context, tfsdk.DeleteResourceRequest, *tfsdk.DeleteResourceResponse) // response expectations expectedNewState tftypes.Value @@ -4292,7 +4297,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "create", resourceType: testServeResourceTypeOneType, - create: func(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { + create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -4327,7 +4332,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "create", resourceType: testServeResourceTypeOneType, - create: func(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { + create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -4386,7 +4391,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "update", resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { + update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -4436,7 +4441,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "update", resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { + update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -4499,7 +4504,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "update", resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { + update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -4544,7 +4549,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "delete", resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Removing the state prior to the framework should not generate errors resp.State.RemoveResource(ctx) }, @@ -4561,7 +4566,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "delete", resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // Removing the state prior to the framework should not generate errors resp.State.RemoveResource(ctx) resp.Diagnostics.AddAttributeWarning( @@ -4591,7 +4596,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "delete", resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { resp.Diagnostics.AddError( "This is an error", "Something went wrong, keep the old state around", @@ -4623,7 +4628,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "delete", resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // The framework should automatically call resp.State.RemoveResource() }, expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), @@ -4639,7 +4644,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "delete", resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // The framework should automatically call resp.State.RemoveResource() resp.Diagnostics.AddAttributeWarning( tftypes.NewAttributePath().WithAttributeName("created_timestamp"), @@ -4668,7 +4673,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "delete", resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { // The framework should NOT automatically call resp.State.RemoveResource() resp.Diagnostics.AddError( "This is an error", @@ -4728,7 +4733,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_two", action: "create", resourceType: testServeResourceTypeTwoType, - create: func(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { + create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "test-instance"), "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ @@ -4984,7 +4989,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_two", action: "update", resourceType: testServeResourceTypeTwoType, - update: func(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { + update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "test-instance"), "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ @@ -5169,7 +5174,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_two", action: "delete", resourceType: testServeResourceTypeTwoType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, nil) }, expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, nil), @@ -5195,7 +5200,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "create", resourceType: testServeResourceTypeOneType, - create: func(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { + create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -5244,7 +5249,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "update", resourceType: testServeResourceTypeOneType, - update: func(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { + update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{ "name": tftypes.NewValue(tftypes.String, "hello, world"), "favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{ @@ -5279,7 +5284,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_one", action: "delete", resourceType: testServeResourceTypeOneType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, nil) }, expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil), @@ -5325,7 +5330,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_two", action: "create", resourceType: testServeResourceTypeTwoType, - create: func(ctx context.Context, req CreateResourceRequest, resp *CreateResourceResponse) { + create: func(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "test-instance"), "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ @@ -5562,7 +5567,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_two", action: "update", resourceType: testServeResourceTypeTwoType, - update: func(ctx context.Context, req UpdateResourceRequest, resp *UpdateResourceResponse) { + update: func(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{ "id": tftypes.NewValue(tftypes.String, "test-instance"), "disks": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ @@ -5728,7 +5733,7 @@ func TestServerApplyResourceChange(t *testing.T) { resource: "test_two", action: "delete", resourceType: testServeResourceTypeTwoType, - destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) { + destroy: func(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { resp.State.Raw = tftypes.NewValue(testServeResourceTypeTwoType, nil) }, expectedNewState: tftypes.NewValue(testServeResourceTypeTwoType, nil), @@ -5749,7 +5754,7 @@ func TestServerApplyResourceChange(t *testing.T) { testServer := &Server{ Provider: s, } - var pmSchema Schema + var pmSchema tfsdk.Schema if tc.providerMeta.Type() != nil { sWithMeta := &testServeProviderWithMetaSchema{s} testServer.Provider = sWithMeta @@ -5884,7 +5889,7 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSource string dataSourceType tftypes.Type - impl func(context.Context, ValidateDataSourceConfigRequest, *ValidateDataSourceConfigResponse) + impl func(context.Context, tfsdk.ValidateDataSourceConfigRequest, *tfsdk.ValidateDataSourceConfigResponse) // response expectations expectedDiags []*tfprotov6.Diagnostic @@ -5907,7 +5912,8 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSource: "test_config_validators", dataSourceType: testServeDataSourceTypeConfigValidatorsType, - impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) {}, + impl: func(_ context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { + }, }, "config_validators_one_diag": { config: tftypes.NewValue(testServeDataSourceTypeConfigValidatorsType, map[string]tftypes.Value{ @@ -5916,7 +5922,7 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSource: "test_config_validators", dataSourceType: testServeDataSourceTypeConfigValidatorsType, - impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { if len(resp.Diagnostics) == 0 { resp.Diagnostics.AddError( "This is an error", @@ -5951,7 +5957,7 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSource: "test_config_validators", dataSourceType: testServeDataSourceTypeConfigValidatorsType, - impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { if len(resp.Diagnostics) == 0 { resp.Diagnostics.AddAttributeWarning( tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), @@ -6008,7 +6014,8 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSource: "test_validate_config", dataSourceType: testServeDataSourceTypeValidateConfigType, - impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) {}, + impl: func(_ context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { + }, }, "validate_config_one_diag": { config: tftypes.NewValue(testServeDataSourceTypeValidateConfigType, map[string]tftypes.Value{ @@ -6017,7 +6024,7 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSource: "test_validate_config", dataSourceType: testServeDataSourceTypeValidateConfigType, - impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { resp.Diagnostics.AddError( "This is an error", "Oops.", @@ -6039,7 +6046,7 @@ func TestServerValidateDataResourceConfig(t *testing.T) { dataSource: "test_validate_config", dataSourceType: testServeDataSourceTypeValidateConfigType, - impl: func(_ context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) { + impl: func(_ context.Context, req tfsdk.ValidateDataSourceConfigRequest, resp *tfsdk.ValidateDataSourceConfigResponse) { resp.Diagnostics.AddAttributeWarning( tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(0), "This is a warning", @@ -6115,7 +6122,7 @@ func TestServerReadDataSource(t *testing.T) { dataSource string dataSourceType tftypes.Type - impl func(context.Context, ReadDataSourceRequest, *ReadDataSourceResponse) + impl func(context.Context, tfsdk.ReadDataSourceRequest, *tfsdk.ReadDataSourceResponse) // response expectations expectedNewState tftypes.Value @@ -6132,7 +6139,7 @@ func TestServerReadDataSource(t *testing.T) { dataSource: "test_one", dataSourceType: testServeDataSourceTypeOneType, - impl: func(_ context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { resp.State.Raw = tftypes.NewValue(testServeDataSourceTypeOneType, map[string]tftypes.Value{ "current_date": tftypes.NewValue(tftypes.String, "today"), "current_time": tftypes.NewValue(tftypes.String, "now"), @@ -6159,7 +6166,7 @@ func TestServerReadDataSource(t *testing.T) { "foo": tftypes.NewValue(tftypes.String, "my provider_meta value"), }), - impl: func(_ context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { resp.State.Raw = tftypes.NewValue(testServeDataSourceTypeOneType, map[string]tftypes.Value{ "current_date": tftypes.NewValue(tftypes.String, "today"), "current_time": tftypes.NewValue(tftypes.String, "now"), @@ -6182,7 +6189,7 @@ func TestServerReadDataSource(t *testing.T) { dataSource: "test_one", dataSourceType: testServeDataSourceTypeOneType, - impl: func(_ context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { resp.State.Raw = tftypes.NewValue(testServeDataSourceTypeOneType, nil) }, @@ -6197,7 +6204,7 @@ func TestServerReadDataSource(t *testing.T) { dataSource: "test_two", dataSourceType: testServeDataSourceTypeTwoType, - impl: func(_ context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { resp.State.Raw = tftypes.NewValue(testServeDataSourceTypeTwoType, map[string]tftypes.Value{ "family": tftypes.NewValue(tftypes.String, "123foo"), "name": tftypes.NewValue(tftypes.String, "123foo-askjgsio"), @@ -6220,7 +6227,7 @@ func TestServerReadDataSource(t *testing.T) { dataSource: "test_two", dataSourceType: testServeDataSourceTypeTwoType, - impl: func(_ context.Context, req ReadDataSourceRequest, resp *ReadDataSourceResponse) { + impl: func(_ context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { resp.State.Raw = tftypes.NewValue(testServeDataSourceTypeTwoType, map[string]tftypes.Value{ "family": tftypes.NewValue(tftypes.String, "123foo"), "name": tftypes.NewValue(tftypes.String, "123foo-askjgsio"), @@ -6271,7 +6278,7 @@ func TestServerReadDataSource(t *testing.T) { testServer := &Server{ Provider: s, } - var pmSchema Schema + var pmSchema tfsdk.Schema if tc.providerMeta.Type() != nil { sWithMeta := &testServeProviderWithMetaSchema{s} testServer.Provider = sWithMeta diff --git a/internal/proto6server/state.go b/internal/proto6server/state.go new file mode 100644 index 000000000..3f1459da3 --- /dev/null +++ b/internal/proto6server/state.go @@ -0,0 +1,102 @@ +package proto6server + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// StateGetAttributeValue is a duplicate of tfsdk.State.getAttributeValue, +// except it calls a local duplicate to State.terraformValueAtPath as well. +// It is duplicated to prevent any oddities with trying to use +// tfsdk.State.GetAttribute, which has some potentially undesirable logic. +// Refer to the tfsdk package for the large amount of testing done there. +// +// TODO: Clean up this abstraction back into an internal State type method. +// The extra State parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func StateGetAttributeValue(ctx context.Context, s tfsdk.State, path *tftypes.AttributePath) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + attrType, err := s.Schema.AttributeTypeAtPath(path) + if err != nil { + err = fmt.Errorf("error getting attribute type in schema: %w", err) + diags.AddAttributeError( + path, + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + // if the whole state is nil, the value of a valid attribute is also nil + if s.Raw.IsNull() { + return nil, nil + } + + tfValue, err := StateTerraformValueAtPath(s, path) + + // Ignoring ErrInvalidStep will allow this method to return a null value of the type. + if err != nil && !errors.Is(err, tftypes.ErrInvalidStep) { + diags.AddAttributeError( + path, + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + // TODO: If ErrInvalidStep, check parent paths for unknown value. + // If found, convert this value to an unknown value. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/186 + + if attrTypeWithValidate, ok := attrType.(attr.TypeWithValidate); ok { + logging.FrameworkTrace(ctx, "Type implements TypeWithValidate") + logging.FrameworkDebug(ctx, "Calling provider defined Type Validate") + diags.Append(attrTypeWithValidate.Validate(ctx, tfValue, path)...) + logging.FrameworkDebug(ctx, "Called provider defined Type Validate") + + if diags.HasError() { + return nil, diags + } + } + + attrValue, err := attrType.ValueFromTerraform(ctx, tfValue) + + if err != nil { + diags.AddAttributeError( + path, + "State Read Error", + "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrValue, diags +} + +// StateTerraformValueAtPath is a duplicate of +// tfsdk.State.terraformValueAtPath. +// +// TODO: Clean up this abstraction back into an internal State type method. +// The extra State parameter is a carry-over of creating the proto6server +// package from the tfsdk package and not wanting to export the method. +// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/215 +func StateTerraformValueAtPath(s tfsdk.State, path *tftypes.AttributePath) (tftypes.Value, error) { + rawValue, remaining, err := tftypes.WalkAttributePath(s.Raw, path) + if err != nil { + return tftypes.Value{}, fmt.Errorf("%v still remains in the path: %w", remaining, err) + } + attrValue, ok := rawValue.(tftypes.Value) + if !ok { + return tftypes.Value{}, fmt.Errorf("got non-tftypes.Value result %v", rawValue) + } + return attrValue, err +} diff --git a/internal/toproto6/block.go b/internal/toproto6/block.go new file mode 100644 index 000000000..7a27ea005 --- /dev/null +++ b/internal/toproto6/block.go @@ -0,0 +1,92 @@ +package toproto6 + +import ( + "context" + "sort" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Block returns the *tfprotov6.SchemaNestedBlock equivalent of a Block. +// Errors will be tftypes.AttributePathErrors based on `path`. `name` is the +// name of the attribute. +func Block(ctx context.Context, name string, path *tftypes.AttributePath, b tfsdk.Block) (*tfprotov6.SchemaNestedBlock, error) { + schemaNestedBlock := &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Deprecated: b.DeprecationMessage != "", + }, + MinItems: b.MinItems, + MaxItems: b.MaxItems, + TypeName: name, + } + + if b.Description != "" { + schemaNestedBlock.Block.Description = b.Description + schemaNestedBlock.Block.DescriptionKind = tfprotov6.StringKindPlain + } + + if b.MarkdownDescription != "" { + schemaNestedBlock.Block.Description = b.MarkdownDescription + schemaNestedBlock.Block.DescriptionKind = tfprotov6.StringKindMarkdown + } + + nm := b.NestingMode + switch nm { + case tfsdk.BlockNestingModeList: + schemaNestedBlock.Nesting = tfprotov6.SchemaNestedBlockNestingModeList + case tfsdk.BlockNestingModeSet: + schemaNestedBlock.Nesting = tfprotov6.SchemaNestedBlockNestingModeSet + default: + return nil, path.NewErrorf("unrecognized nesting mode %v", nm) + } + + for attrName, attr := range b.Attributes { + attrPath := path.WithAttributeName(attrName) + attrProto6, err := SchemaAttribute(ctx, attrName, attrPath, attr) + + if err != nil { + return nil, err + } + + schemaNestedBlock.Block.Attributes = append(schemaNestedBlock.Block.Attributes, attrProto6) + } + + for blockName, block := range b.Blocks { + blockPath := path.WithAttributeName(blockName) + blockProto6, err := Block(ctx, blockName, blockPath, block) + + if err != nil { + return nil, err + } + + schemaNestedBlock.Block.BlockTypes = append(schemaNestedBlock.Block.BlockTypes, blockProto6) + } + + sort.Slice(schemaNestedBlock.Block.Attributes, func(i, j int) bool { + if schemaNestedBlock.Block.Attributes[i] == nil { + return true + } + + if schemaNestedBlock.Block.Attributes[j] == nil { + return false + } + + return schemaNestedBlock.Block.Attributes[i].Name < schemaNestedBlock.Block.Attributes[j].Name + }) + + sort.Slice(schemaNestedBlock.Block.BlockTypes, func(i, j int) bool { + if schemaNestedBlock.Block.BlockTypes[i] == nil { + return true + } + + if schemaNestedBlock.Block.BlockTypes[j] == nil { + return false + } + + return schemaNestedBlock.Block.BlockTypes[i].TypeName < schemaNestedBlock.Block.BlockTypes[j].TypeName + }) + + return schemaNestedBlock, nil +} diff --git a/internal/toproto6/block_test.go b/internal/toproto6/block_test.go new file mode 100644 index 000000000..94adebdc6 --- /dev/null +++ b/internal/toproto6/block_test.go @@ -0,0 +1,476 @@ +package toproto6_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestBlock(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + block tfsdk.Block + path *tftypes.AttributePath + expected *tfprotov6.SchemaNestedBlock + expectedErr string + } + + tests := map[string]testCase{ + "nestingmode-invalid": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + }, + path: tftypes.NewAttributePath(), + expectedErr: "unrecognized nesting mode 0", + }, + "nestingmode-list-attributes": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "nestingmode-list-attributes-and-blocks": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_attr": { + Type: types.StringType, + Optional: true, + }, + }, + Blocks: map[string]tfsdk.Block{ + "sub_block": { + Attributes: map[string]tfsdk.Attribute{ + "sub_block_attr": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_attr", + Optional: true, + Type: tftypes.String, + }, + }, + BlockTypes: []*tfprotov6.SchemaNestedBlock{ + { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_block_attr", + Optional: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "sub_block", + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "nestingmode-list-blocks": { + name: "test", + block: tfsdk.Block{ + Blocks: map[string]tfsdk.Block{ + "sub_block": { + Attributes: map[string]tfsdk.Attribute{ + "sub_block_attr": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + BlockTypes: []*tfprotov6.SchemaNestedBlock{ + { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_block_attr", + Optional: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "sub_block", + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "nestingmode-set-attributes": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, + TypeName: "test", + }, + }, + "nestingmode-set-attributes-and-blocks": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_attr": { + Type: types.StringType, + Optional: true, + }, + }, + Blocks: map[string]tfsdk.Block{ + "sub_block": { + Attributes: map[string]tfsdk.Attribute{ + "sub_block_attr": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_attr", + Optional: true, + Type: tftypes.String, + }, + }, + BlockTypes: []*tfprotov6.SchemaNestedBlock{ + { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_block_attr", + Optional: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, + TypeName: "sub_block", + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, + TypeName: "test", + }, + }, + "nestingmode-set-blocks": { + name: "test", + block: tfsdk.Block{ + Blocks: map[string]tfsdk.Block{ + "sub_block": { + Attributes: map[string]tfsdk.Attribute{ + "sub_block_attr": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + BlockTypes: []*tfprotov6.SchemaNestedBlock{ + { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_block_attr", + Optional: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, + TypeName: "sub_block", + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, + TypeName: "test", + }, + }, + "deprecationmessage": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + DeprecationMessage: "deprecated, use something else instead", + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + Deprecated: true, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "description": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + Description: "test description", + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + Description: "test description", + DescriptionKind: tfprotov6.StringKindPlain, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "description-and-markdowndescription": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + Description: "test plain description", + MarkdownDescription: "test markdown description", + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + Description: "test markdown description", + DescriptionKind: tfprotov6.StringKindMarkdown, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "markdowndescription": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + MarkdownDescription: "test description", + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + Description: "test description", + DescriptionKind: tfprotov6.StringKindMarkdown, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "maxitems": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + MaxItems: 10, + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + }, + MaxItems: 10, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + "minitems": { + name: "test", + block: tfsdk.Block{ + Attributes: map[string]tfsdk.Attribute{ + "sub_test": { + Type: types.StringType, + Optional: true, + }, + }, + MinItems: 10, + NestingMode: tfsdk.BlockNestingModeList, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaNestedBlock{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "sub_test", + Optional: true, + Type: tftypes.String, + }, + }, + }, + MinItems: 10, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "test", + }, + }, + } + + for name, tc := range tests { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := toproto6.Block(context.Background(), tc.name, tc.path, tc.block) + if err != nil { + if tc.expectedErr == "" { + t.Errorf("Unexpected error: %s", err) + return + } + if err.Error() != tc.expectedErr { + t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error()) + return + } + // got expected error + return + } + if err == nil && tc.expectedErr != "" { + t.Errorf("Expected error to be %q, got nil", tc.expectedErr) + return + } + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("Unexpected diff (+wanted, -got): %s", diff) + return + } + }) + } +} diff --git a/internal/toproto6/doc.go b/internal/toproto6/doc.go new file mode 100644 index 000000000..512ea5759 --- /dev/null +++ b/internal/toproto6/doc.go @@ -0,0 +1,3 @@ +// Package toproto6 contains functions to convert from framework types to +// protocol version 6 (tfprotov6) types. +package toproto6 diff --git a/internal/toproto6/schema.go b/internal/toproto6/schema.go new file mode 100644 index 000000000..626ec189f --- /dev/null +++ b/internal/toproto6/schema.go @@ -0,0 +1,84 @@ +package toproto6 + +import ( + "context" + "sort" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Schema returns the *tfprotov6.Schema equivalent of a Schema. +func Schema(ctx context.Context, s tfsdk.Schema) (*tfprotov6.Schema, error) { + result := &tfprotov6.Schema{ + Version: s.Version, + } + + var attrs []*tfprotov6.SchemaAttribute + var blocks []*tfprotov6.SchemaNestedBlock + + for name, attr := range s.Attributes { + a, err := SchemaAttribute(ctx, name, tftypes.NewAttributePath().WithAttributeName(name), attr) + + if err != nil { + return nil, err + } + + attrs = append(attrs, a) + } + + for name, block := range s.Blocks { + proto6, err := Block(ctx, name, tftypes.NewAttributePath().WithAttributeName(name), block) + + if err != nil { + return nil, err + } + + blocks = append(blocks, proto6) + } + + sort.Slice(attrs, func(i, j int) bool { + if attrs[i] == nil { + return true + } + + if attrs[j] == nil { + return false + } + + return attrs[i].Name < attrs[j].Name + }) + + sort.Slice(blocks, func(i, j int) bool { + if blocks[i] == nil { + return true + } + + if blocks[j] == nil { + return false + } + + return blocks[i].TypeName < blocks[j].TypeName + }) + + result.Block = &tfprotov6.SchemaBlock{ + // core doesn't do anything with version, as far as I can tell, + // so let's not set it. + Attributes: attrs, + BlockTypes: blocks, + Deprecated: s.DeprecationMessage != "", + } + + if s.Description != "" { + result.Block.Description = s.Description + result.Block.DescriptionKind = tfprotov6.StringKindPlain + } + + if s.MarkdownDescription != "" { + result.Block.Description = s.MarkdownDescription + result.Block.DescriptionKind = tfprotov6.StringKindMarkdown + } + + return result, nil +} diff --git a/internal/toproto6/schema_attribute.go b/internal/toproto6/schema_attribute.go new file mode 100644 index 000000000..4bc891db9 --- /dev/null +++ b/internal/toproto6/schema_attribute.go @@ -0,0 +1,96 @@ +package toproto6 + +import ( + "context" + "sort" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// SchemaAttribute returns the *tfprotov6.SchemaAttribute equivalent of an +// Attribute. Errors will be tftypes.AttributePathErrors based on `path`. +// `name` is the name of the attribute. +func SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePath, a tfsdk.Attribute) (*tfprotov6.SchemaAttribute, error) { + if a.Attributes != nil && len(a.Attributes.GetAttributes()) > 0 && a.Type != nil { + return nil, path.NewErrorf("cannot have both Attributes and Type set") + } + + if (a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0) && a.Type == nil { + return nil, path.NewErrorf("must have Attributes or Type set") + } + + if !a.Required && !a.Optional && !a.Computed { + return nil, path.NewErrorf("must have Required, Optional, or Computed set") + } + + schemaAttribute := &tfprotov6.SchemaAttribute{ + Name: name, + Required: a.Required, + Optional: a.Optional, + Computed: a.Computed, + Sensitive: a.Sensitive, + } + + if a.DeprecationMessage != "" { + schemaAttribute.Deprecated = true + } + + if a.Description != "" { + schemaAttribute.Description = a.Description + schemaAttribute.DescriptionKind = tfprotov6.StringKindPlain + } + + if a.MarkdownDescription != "" { + schemaAttribute.Description = a.MarkdownDescription + schemaAttribute.DescriptionKind = tfprotov6.StringKindMarkdown + } + + if a.Type != nil { + schemaAttribute.Type = a.Type.TerraformType(ctx) + + return schemaAttribute, nil + } + + object := &tfprotov6.SchemaObject{} + nm := a.Attributes.GetNestingMode() + switch nm { + case tfsdk.NestingModeSingle: + object.Nesting = tfprotov6.SchemaObjectNestingModeSingle + case tfsdk.NestingModeList: + object.Nesting = tfprotov6.SchemaObjectNestingModeList + case tfsdk.NestingModeSet: + object.Nesting = tfprotov6.SchemaObjectNestingModeSet + case tfsdk.NestingModeMap: + object.Nesting = tfprotov6.SchemaObjectNestingModeMap + default: + return nil, path.NewErrorf("unrecognized nesting mode %v", nm) + } + + for nestedName, nestedA := range a.Attributes.GetAttributes() { + nestedSchemaAttribute, err := SchemaAttribute(ctx, nestedName, path.WithAttributeName(nestedName), nestedA) + + if err != nil { + return nil, err + } + + object.Attributes = append(object.Attributes, nestedSchemaAttribute) + } + + sort.Slice(object.Attributes, func(i, j int) bool { + if object.Attributes[i] == nil { + return true + } + + if object.Attributes[j] == nil { + return false + } + + return object.Attributes[i].Name < object.Attributes[j].Name + }) + + schemaAttribute.NestedType = object + + return schemaAttribute, nil +} diff --git a/internal/toproto6/schema_attribute_test.go b/internal/toproto6/schema_attribute_test.go new file mode 100644 index 000000000..df4ad8c5b --- /dev/null +++ b/internal/toproto6/schema_attribute_test.go @@ -0,0 +1,445 @@ +package toproto6_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestSchemaAttribute(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + attr tfsdk.Attribute + path *tftypes.AttributePath + expected *tfprotov6.SchemaAttribute + expectedErr string + } + + tests := map[string]testCase{ + "deprecated": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + DeprecationMessage: "deprecated, use new_string instead", + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Optional: true, + Deprecated: true, + }, + }, + "description-plain": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + Description: "A string attribute", + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Optional: true, + Description: "A string attribute", + DescriptionKind: tfprotov6.StringKindPlain, + }, + }, + "description-markdown": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + MarkdownDescription: "A string attribute", + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Optional: true, + Description: "A string attribute", + DescriptionKind: tfprotov6.StringKindMarkdown, + }, + }, + "description-both": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + Description: "A string attribute", + MarkdownDescription: "A string attribute (markdown)", + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Optional: true, + Description: "A string attribute (markdown)", + DescriptionKind: tfprotov6.StringKindMarkdown, + }, + }, + "attr-string": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Optional: true, + }, + }, + "attr-bool": { + name: "bool", + attr: tfsdk.Attribute{ + Type: types.BoolType, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "bool", + Type: tftypes.Bool, + Optional: true, + }, + }, + "attr-number": { + name: "number", + attr: tfsdk.Attribute{ + Type: types.NumberType, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "number", + Type: tftypes.Number, + Optional: true, + }, + }, + "attr-list": { + name: "list", + attr: tfsdk.Attribute{ + Type: types.ListType{ElemType: types.NumberType}, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "list", + Type: tftypes.List{ElementType: tftypes.Number}, + Optional: true, + }, + }, + "attr-map": { + name: "map", + attr: tfsdk.Attribute{ + Type: types.MapType{ElemType: types.StringType}, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "map", + Type: tftypes.Map{ElementType: tftypes.String}, + Optional: true, + }, + }, + "attr-object": { + name: "object", + attr: tfsdk.Attribute{ + Type: types.ObjectType{AttrTypes: map[string]attr.Type{ + "foo": types.StringType, + "bar": types.NumberType, + "baz": types.BoolType, + }}, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "object", + Type: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + "bar": tftypes.Number, + "baz": tftypes.Bool, + }}, + Optional: true, + }, + }, + "attr-set": { + name: "set", + attr: tfsdk.Attribute{ + Type: types.SetType{ElemType: types.NumberType}, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "set", + Type: tftypes.Set{ElementType: tftypes.Number}, + Optional: true, + }, + }, + // TODO: add tuple attribute when we support it + "required": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Required: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + "optional": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Optional: true, + }, + }, + "computed": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Computed: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Computed: true, + }, + }, + "optional-computed": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Computed: true, + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Computed: true, + Optional: true, + }, + }, + "sensitive": { + name: "string", + attr: tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + Sensitive: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "string", + Type: tftypes.String, + Optional: true, + Sensitive: true, + }, + }, + "nested-attr-single": { + name: "single_nested", + attr: tfsdk.Attribute{ + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Optional: true, + }, + "computed": { + Type: types.NumberType, + Computed: true, + Sensitive: true, + }, + }), + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "single_nested", + Optional: true, + NestedType: &tfprotov6.SchemaObject{ + Nesting: tfprotov6.SchemaObjectNestingModeSingle, + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "computed", + Computed: true, + Sensitive: true, + Type: tftypes.Number, + }, + { + Name: "string", + Optional: true, + Type: tftypes.String, + }, + }, + }, + }, + }, + "nested-attr-list": { + name: "list_nested", + attr: tfsdk.Attribute{ + Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Optional: true, + }, + "computed": { + Type: types.NumberType, + Computed: true, + Sensitive: true, + }, + }, tfsdk.ListNestedAttributesOptions{}), + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "list_nested", + Optional: true, + NestedType: &tfprotov6.SchemaObject{ + Nesting: tfprotov6.SchemaObjectNestingModeList, + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "computed", + Computed: true, + Sensitive: true, + Type: tftypes.Number, + }, + { + Name: "string", + Optional: true, + Type: tftypes.String, + }, + }, + }, + }, + }, + "nested-attr-set": { + name: "set_nested", + attr: tfsdk.Attribute{ + Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Optional: true, + }, + "computed": { + Type: types.NumberType, + Computed: true, + Sensitive: true, + }, + }, tfsdk.SetNestedAttributesOptions{}), + Optional: true, + }, + path: tftypes.NewAttributePath(), + expected: &tfprotov6.SchemaAttribute{ + Name: "set_nested", + Optional: true, + NestedType: &tfprotov6.SchemaObject{ + Nesting: tfprotov6.SchemaObjectNestingModeSet, + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "computed", + Computed: true, + Sensitive: true, + Type: tftypes.Number, + }, + { + Name: "string", + Optional: true, + Type: tftypes.String, + }, + }, + }, + }, + }, + "attr-and-nested-attr-set": { + name: "whoops", + attr: tfsdk.Attribute{ + Type: types.StringType, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "testing": { + Type: types.StringType, + Optional: true, + }, + }), + Optional: true, + }, + path: tftypes.NewAttributePath(), + expectedErr: "cannot have both Attributes and Type set", + }, + "attr-and-nested-attr-unset": { + name: "whoops", + attr: tfsdk.Attribute{ + Optional: true, + }, + path: tftypes.NewAttributePath(), + expectedErr: "must have Attributes or Type set", + }, + "attr-and-nested-attr-empty": { + name: "whoops", + attr: tfsdk.Attribute{ + Optional: true, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{}), + }, + path: tftypes.NewAttributePath(), + expectedErr: "must have Attributes or Type set", + }, + "missing-required-optional-and-computed": { + name: "whoops", + attr: tfsdk.Attribute{ + Type: types.StringType, + }, + path: tftypes.NewAttributePath(), + expectedErr: "must have Required, Optional, or Computed set", + }, + } + + for name, tc := range tests { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := toproto6.SchemaAttribute(context.Background(), tc.name, tc.path, tc.attr) + if err != nil { + if tc.expectedErr == "" { + t.Errorf("Unexpected error: %s", err) + return + } + if err.Error() != tc.expectedErr { + t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error()) + return + } + // got expected error + return + } + if err == nil && tc.expectedErr != "" { + t.Errorf("Expected error to be %q, got nil", tc.expectedErr) + return + } + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("Unexpected diff (+wanted, -got): %s", diff) + return + } + }) + } +} diff --git a/internal/toproto6/schema_test.go b/internal/toproto6/schema_test.go new file mode 100644 index 000000000..cd81e539b --- /dev/null +++ b/internal/toproto6/schema_test.go @@ -0,0 +1,580 @@ +package toproto6_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestSchema(t *testing.T) { + t.Parallel() + + type testCase struct { + input tfsdk.Schema + expected *tfprotov6.Schema + expectedErr string + } + + tests := map[string]testCase{ + "empty-val": { + input: tfsdk.Schema{}, + expected: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{}, + Version: 0, + }, + }, + "basic-attrs": { + input: tfsdk.Schema{ + Version: 1, + Attributes: map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "bool": { + Type: types.BoolType, + Computed: true, + }, + }, + }, + expected: &tfprotov6.Schema{ + Version: 1, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Computed: true, + }, + { + Name: "number", + Type: tftypes.Number, + Optional: true, + }, + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + }, + }, + }, + "complex-attrs": { + input: tfsdk.Schema{ + Version: 2, + Attributes: map[string]tfsdk.Attribute{ + "list": { + Type: types.ListType{ElemType: types.StringType}, + Required: true, + }, + "object": { + Type: types.ObjectType{AttrTypes: map[string]attr.Type{ + "string": types.StringType, + "number": types.NumberType, + "bool": types.BoolType, + }}, + Optional: true, + }, + "map": { + Type: types.MapType{ElemType: types.NumberType}, + Computed: true, + }, + "set": { + Type: types.SetType{ElemType: types.StringType}, + Required: true, + }, + // TODO: add tuple support when it lands + }, + }, + expected: &tfprotov6.Schema{ + Version: 2, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "list", + Type: tftypes.List{ElementType: tftypes.String}, + Required: true, + }, + { + Name: "map", + Type: tftypes.Map{ElementType: tftypes.Number}, + Computed: true, + }, + { + Name: "object", + Type: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ + "string": tftypes.String, + "number": tftypes.Number, + "bool": tftypes.Bool, + }}, + Optional: true, + }, + { + Name: "set", + Type: tftypes.Set{ElementType: tftypes.String}, + Required: true, + }, + }, + }, + }, + }, + "nested-attrs": { + input: tfsdk.Schema{ + Version: 3, + Attributes: map[string]tfsdk.Attribute{ + "single": { + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "bool": { + Type: types.BoolType, + Computed: true, + }, + "list": { + Type: types.ListType{ElemType: types.StringType}, + Computed: true, + Optional: true, + }, + }), + Required: true, + }, + "list": { + Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "bool": { + Type: types.BoolType, + Computed: true, + }, + "list": { + Type: types.ListType{ElemType: types.StringType}, + Computed: true, + Optional: true, + }, + }, tfsdk.ListNestedAttributesOptions{}), + Optional: true, + }, + "set": { + Attributes: tfsdk.SetNestedAttributes(map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "bool": { + Type: types.BoolType, + Computed: true, + }, + "list": { + Type: types.ListType{ElemType: types.StringType}, + Computed: true, + Optional: true, + }, + }, tfsdk.SetNestedAttributesOptions{}), + Computed: true, + }, + "map": { + Attributes: tfsdk.MapNestedAttributes(map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "bool": { + Type: types.BoolType, + Computed: true, + }, + "list": { + Type: types.ListType{ElemType: types.StringType}, + Computed: true, + Optional: true, + }, + }, tfsdk.MapNestedAttributesOptions{}), + Optional: true, + Computed: true, + }, + }, + }, + expected: &tfprotov6.Schema{ + Version: 3, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "list", + NestedType: &tfprotov6.SchemaObject{ + Nesting: tfprotov6.SchemaObjectNestingModeList, + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Computed: true, + }, + { + Name: "list", + Type: tftypes.List{ElementType: tftypes.String}, + Optional: true, + Computed: true, + }, + { + Name: "number", + Type: tftypes.Number, + Optional: true, + }, + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + }, + Optional: true, + }, + { + Name: "map", + NestedType: &tfprotov6.SchemaObject{ + Nesting: tfprotov6.SchemaObjectNestingModeMap, + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Computed: true, + }, + { + Name: "list", + Type: tftypes.List{ElementType: tftypes.String}, + Optional: true, + Computed: true, + }, + { + Name: "number", + Type: tftypes.Number, + Optional: true, + }, + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + }, + Optional: true, + Computed: true, + }, + { + Name: "set", + NestedType: &tfprotov6.SchemaObject{ + Nesting: tfprotov6.SchemaObjectNestingModeSet, + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Computed: true, + }, + { + Name: "list", + Type: tftypes.List{ElementType: tftypes.String}, + Optional: true, + Computed: true, + }, + { + Name: "number", + Type: tftypes.Number, + Optional: true, + }, + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + }, + Computed: true, + }, + { + Name: "single", + NestedType: &tfprotov6.SchemaObject{ + Nesting: tfprotov6.SchemaObjectNestingModeSingle, + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Computed: true, + }, + { + Name: "list", + Type: tftypes.List{ElementType: tftypes.String}, + Optional: true, + Computed: true, + }, + { + Name: "number", + Type: tftypes.Number, + Optional: true, + }, + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + }, + Required: true, + }, + }, + }, + }, + }, + "nested-blocks": { + input: tfsdk.Schema{ + Version: 3, + Blocks: map[string]tfsdk.Block{ + "list": { + Attributes: map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "bool": { + Type: types.BoolType, + Computed: true, + }, + "list": { + Type: types.ListType{ElemType: types.StringType}, + Computed: true, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + }, + "set": { + Attributes: map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "bool": { + Type: types.BoolType, + Computed: true, + }, + "list": { + Type: types.ListType{ElemType: types.StringType}, + Computed: true, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeSet, + }, + }, + }, + expected: &tfprotov6.Schema{ + Version: 3, + Block: &tfprotov6.SchemaBlock{ + BlockTypes: []*tfprotov6.SchemaNestedBlock{ + { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Computed: true, + Name: "bool", + Type: tftypes.Bool, + }, + { + Computed: true, + Name: "list", + Optional: true, + Type: tftypes.List{ElementType: tftypes.String}, + }, + { + Name: "number", + Optional: true, + Type: tftypes.Number, + }, + { + Name: "string", + Required: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeList, + TypeName: "list", + }, + { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Computed: true, + Name: "bool", + Type: tftypes.Bool, + }, + { + Computed: true, + Name: "list", + Optional: true, + Type: tftypes.List{ElementType: tftypes.String}, + }, + { + Name: "number", + Optional: true, + Type: tftypes.Number, + }, + { + Name: "string", + Required: true, + Type: tftypes.String, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, + TypeName: "set", + }, + }, + }, + }, + }, + "markdown-description": { + input: tfsdk.Schema{ + Version: 1, + Attributes: map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + }, + MarkdownDescription: "a test resource", + }, + expected: &tfprotov6.Schema{ + Version: 1, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + Description: "a test resource", + DescriptionKind: tfprotov6.StringKindMarkdown, + }, + }, + }, + "plaintext-description": { + input: tfsdk.Schema{ + Version: 1, + Attributes: map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + }, + Description: "a test resource", + }, + expected: &tfprotov6.Schema{ + Version: 1, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + Description: "a test resource", + DescriptionKind: tfprotov6.StringKindPlain, + }, + }, + }, + "deprecated": { + input: tfsdk.Schema{ + Version: 1, + Attributes: map[string]tfsdk.Attribute{ + "string": { + Type: types.StringType, + Required: true, + }, + }, + DeprecationMessage: "deprecated, use other_resource instead", + }, + expected: &tfprotov6.Schema{ + Version: 1, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + Deprecated: true, + }, + }, + }, + } + + for name, tc := range tests { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := toproto6.Schema(context.Background(), tc.input) + if err != nil { + if tc.expectedErr == "" { + t.Errorf("Unexpected error: %s", err) + return + } + if err.Error() != tc.expectedErr { + t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error()) + return + } + // got expected error + return + } + if err == nil && tc.expectedErr != "" { + t.Errorf("Expected error to be %q, got nil", tc.expectedErr) + return + } + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("Unexpected diff (+wanted, -got): %s", diff) + return + } + }) + } +} diff --git a/providerserver/providerserver.go b/providerserver/providerserver.go index 2603a75e3..65789da7d 100644 --- a/providerserver/providerserver.go +++ b/providerserver/providerserver.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/internal/proto6server" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" @@ -15,7 +16,7 @@ import ( // function and various terraform-plugin-mux functions. func NewProtocol6(p tfsdk.Provider) func() tfprotov6.ProviderServer { return func() tfprotov6.ProviderServer { - return &tfsdk.Server{ + return &proto6server.Server{ Provider: p, } } @@ -28,7 +29,7 @@ func NewProtocol6(p tfsdk.Provider) func() tfprotov6.ProviderServer { // The error return is not currently used, but it may be in the future. func NewProtocol6WithError(p tfsdk.Provider) func() (tfprotov6.ProviderServer, error) { return func() (tfprotov6.ProviderServer, error) { - return &tfsdk.Server{ + return &proto6server.Server{ Provider: p, }, nil } @@ -51,7 +52,7 @@ func Serve(ctx context.Context, providerFunc func() tfsdk.Provider, opts ServeOp return tf6server.Serve( opts.Address, func() tfprotov6.ProviderServer { - return &tfsdk.Server{ + return &proto6server.Server{ Provider: providerFunc(), } }, diff --git a/tfsdk/attribute.go b/tfsdk/attribute.go index c5ab1d11b..69bfd6373 100644 --- a/tfsdk/attribute.go +++ b/tfsdk/attribute.go @@ -3,13 +3,8 @@ package tfsdk import ( "context" "errors" - "fmt" - "sort" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/internal/logging" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -166,13 +161,6 @@ func (a Attribute) attributeType() attr.Type { return a.Type } -// definesAttributes returns true if Attribute has a non-empty Attributes definition. -// -// Attribute may also incorrectly have an Attributes and/or Type definition. -func (a Attribute) definesAttributes() bool { - return a.Attributes != nil && len(a.Attributes.GetAttributes()) > 0 -} - // terraformType returns an tftypes.Type corresponding to the attribute. func (a Attribute) terraformType(ctx context.Context) tftypes.Type { if a.Attributes != nil { @@ -181,537 +169,3 @@ func (a Attribute) terraformType(ctx context.Context) tftypes.Type { return a.Type.TerraformType(ctx) } - -// tfprotov6 returns the *tfprotov6.SchemaAttribute equivalent of an -// Attribute. Errors will be tftypes.AttributePathErrors based on -// `path`. `name` is the name of the attribute. -func (a Attribute) tfprotov6SchemaAttribute(ctx context.Context, name string, path *tftypes.AttributePath) (*tfprotov6.SchemaAttribute, error) { - if a.definesAttributes() && a.Type != nil { - return nil, path.NewErrorf("cannot have both Attributes and Type set") - } - - if !a.definesAttributes() && a.Type == nil { - return nil, path.NewErrorf("must have Attributes or Type set") - } - - if !a.Required && !a.Optional && !a.Computed { - return nil, path.NewErrorf("must have Required, Optional, or Computed set") - } - - schemaAttribute := &tfprotov6.SchemaAttribute{ - Name: name, - Required: a.Required, - Optional: a.Optional, - Computed: a.Computed, - Sensitive: a.Sensitive, - } - - if a.DeprecationMessage != "" { - schemaAttribute.Deprecated = true - } - - if a.Description != "" { - schemaAttribute.Description = a.Description - schemaAttribute.DescriptionKind = tfprotov6.StringKindPlain - } - - if a.MarkdownDescription != "" { - schemaAttribute.Description = a.MarkdownDescription - schemaAttribute.DescriptionKind = tfprotov6.StringKindMarkdown - } - - if a.Type != nil { - schemaAttribute.Type = a.Type.TerraformType(ctx) - - return schemaAttribute, nil - } - - object := &tfprotov6.SchemaObject{} - nm := a.Attributes.GetNestingMode() - switch nm { - case NestingModeSingle: - object.Nesting = tfprotov6.SchemaObjectNestingModeSingle - case NestingModeList: - object.Nesting = tfprotov6.SchemaObjectNestingModeList - case NestingModeSet: - object.Nesting = tfprotov6.SchemaObjectNestingModeSet - case NestingModeMap: - object.Nesting = tfprotov6.SchemaObjectNestingModeMap - default: - return nil, path.NewErrorf("unrecognized nesting mode %v", nm) - } - - for nestedName, nestedA := range a.Attributes.GetAttributes() { - nestedSchemaAttribute, err := nestedA.tfprotov6SchemaAttribute(ctx, nestedName, path.WithAttributeName(nestedName)) - - if err != nil { - return nil, err - } - - object.Attributes = append(object.Attributes, nestedSchemaAttribute) - } - - sort.Slice(object.Attributes, func(i, j int) bool { - if object.Attributes[i] == nil { - return true - } - - if object.Attributes[j] == nil { - return false - } - - return object.Attributes[i].Name < object.Attributes[j].Name - }) - - schemaAttribute.NestedType = object - - return schemaAttribute, nil -} - -// validate performs all Attribute validation. -func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { - ctx = logging.FrameworkWithAttributePath(ctx, req.AttributePath.String()) - - if !a.definesAttributes() && a.Type == nil { - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Invalid Attribute Definition", - "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", - ) - - return - } - - if a.definesAttributes() && a.Type != nil { - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Invalid Attribute Definition", - "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", - ) - - return - } - - if !a.Required && !a.Optional && !a.Computed { - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Invalid Attribute Definition", - "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", - ) - - return - } - - attributeConfig, diags := req.Config.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - if diags.HasError() { - return - } - - req.AttributeConfig = attributeConfig - - for _, validator := range a.Validators { - logging.FrameworkDebug( - ctx, - "Calling provider defined AttributeValidator", - map[string]interface{}{ - logging.KeyDescription: validator.Description(ctx), - }, - ) - validator.Validate(ctx, req, resp) - logging.FrameworkDebug( - ctx, - "Called provider defined AttributeValidator", - map[string]interface{}{ - logging.KeyDescription: validator.Description(ctx), - }, - ) - } - - a.validateAttributes(ctx, req, resp) - - if a.DeprecationMessage != "" && attributeConfig != nil { - tfValue, err := attributeConfig.ToTerraformValue(ctx) - if err != nil { - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot convert value. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - if !tfValue.IsNull() { - resp.Diagnostics.AddAttributeWarning( - req.AttributePath, - "Attribute Deprecated", - a.DeprecationMessage, - ) - } - } -} - -// validateAttributes performs all nested Attributes validation. -func (a Attribute) validateAttributes(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { - if !a.definesAttributes() { - return - } - - nm := a.Attributes.GetNestingMode() - switch nm { - case NestingModeList: - l, ok := req.AttributeConfig.(types.List) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for idx := range l.Elems { - for nestedName, nestedAttr := range a.Attributes.GetAttributes() { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(nestedName), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - } - case NestingModeSet: - s, ok := req.AttributeConfig.(types.Set) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for _, value := range s.Elems { - tfValue, err := value.ToTerraformValue(ctx) - if err != nil { - err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for nestedName, nestedAttr := range a.Attributes.GetAttributes() { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(nestedName), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - } - case NestingModeMap: - m, ok := req.AttributeConfig.(types.Map) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for key := range m.Elems { - for nestedName, nestedAttr := range a.Attributes.GetAttributes() { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithElementKeyString(key).WithAttributeName(nestedName), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - } - case NestingModeSingle: - o, ok := req.AttributeConfig.(types.Object) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - if !o.Null && !o.Unknown { - for nestedName, nestedAttr := range a.Attributes.GetAttributes() { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithAttributeName(nestedName), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - } - default: - err := fmt.Errorf("unknown attribute validation nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Validation Error", - "Attribute validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } -} - -// modifyPlan runs all AttributePlanModifiers -func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifySchemaPlanResponse) { - ctx = logging.FrameworkWithAttributePath(ctx, req.AttributePath.String()) - - attrConfig, diags := req.Config.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - // Only on new errors. - if diags.HasError() { - return - } - req.AttributeConfig = attrConfig - - attrState, diags := req.State.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - // Only on new errors. - if diags.HasError() { - return - } - req.AttributeState = attrState - - attrPlan, diags := req.Plan.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - // Only on new errors. - if diags.HasError() { - return - } - req.AttributePlan = attrPlan - - var requiresReplace bool - for _, planModifier := range a.PlanModifiers { - modifyResp := &ModifyAttributePlanResponse{ - AttributePlan: req.AttributePlan, - RequiresReplace: requiresReplace, - } - - logging.FrameworkDebug( - ctx, - "Calling provider defined AttributePlanModifier", - map[string]interface{}{ - logging.KeyDescription: planModifier.Description(ctx), - }, - ) - planModifier.Modify(ctx, req, modifyResp) - logging.FrameworkDebug( - ctx, - "Called provider defined AttributePlanModifier", - map[string]interface{}{ - logging.KeyDescription: planModifier.Description(ctx), - }, - ) - - req.AttributePlan = modifyResp.AttributePlan - resp.Diagnostics.Append(modifyResp.Diagnostics...) - requiresReplace = modifyResp.RequiresReplace - - // Only on new errors. - if modifyResp.Diagnostics.HasError() { - return - } - } - - if requiresReplace { - resp.RequiresReplace = append(resp.RequiresReplace, req.AttributePath) - } - - setAttrDiags := resp.Plan.SetAttribute(ctx, req.AttributePath, req.AttributePlan) - resp.Diagnostics.Append(setAttrDiags...) - - if setAttrDiags.HasError() { - return - } - - if !a.definesAttributes() { - return - } - - nm := a.Attributes.GetNestingMode() - switch nm { - case NestingModeList: - l, ok := req.AttributePlan.(types.List) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Plan Modification Error", - "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for idx := range l.Elems { - for name, attr := range a.Attributes.GetAttributes() { - attrReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - attr.modifyPlan(ctx, attrReq, resp) - } - } - case NestingModeSet: - s, ok := req.AttributePlan.(types.Set) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Plan Modification Error", - "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for _, value := range s.Elems { - tfValue, err := value.ToTerraformValue(ctx) - if err != nil { - err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Plan Modification Error", - "Attribute plan modification cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for name, attr := range a.Attributes.GetAttributes() { - attrReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - attr.modifyPlan(ctx, attrReq, resp) - } - } - case NestingModeMap: - m, ok := req.AttributePlan.(types.Map) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Plan Modification Error", - "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for key := range m.Elems { - for name, attr := range a.Attributes.GetAttributes() { - attrReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithElementKeyString(key).WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - attr.modifyPlan(ctx, attrReq, resp) - } - } - case NestingModeSingle: - o, ok := req.AttributePlan.(types.Object) - - if !ok { - err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Plan Modification Error", - "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - if len(o.Attrs) == 0 { - return - } - - for name, attr := range a.Attributes.GetAttributes() { - attrReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - attr.modifyPlan(ctx, attrReq, resp) - } - default: - err := fmt.Errorf("unknown attribute nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Attribute Plan Modification Error", - "Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } -} diff --git a/tfsdk/attribute_test.go b/tfsdk/attribute_test.go deleted file mode 100644 index 4fc8f11a8..000000000 --- a/tfsdk/attribute_test.go +++ /dev/null @@ -1,2920 +0,0 @@ -package tfsdk - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -func TestAttributeTfprotov6SchemaAttribute(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - attr Attribute - path *tftypes.AttributePath - expected *tfprotov6.SchemaAttribute - expectedErr string - } - - tests := map[string]testCase{ - "deprecated": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Optional: true, - DeprecationMessage: "deprecated, use new_string instead", - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Optional: true, - Deprecated: true, - }, - }, - "description-plain": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Optional: true, - Description: "A string attribute", - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Optional: true, - Description: "A string attribute", - DescriptionKind: tfprotov6.StringKindPlain, - }, - }, - "description-markdown": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Optional: true, - MarkdownDescription: "A string attribute", - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Optional: true, - Description: "A string attribute", - DescriptionKind: tfprotov6.StringKindMarkdown, - }, - }, - "description-both": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Optional: true, - Description: "A string attribute", - MarkdownDescription: "A string attribute (markdown)", - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Optional: true, - Description: "A string attribute (markdown)", - DescriptionKind: tfprotov6.StringKindMarkdown, - }, - }, - "attr-string": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Optional: true, - }, - }, - "attr-bool": { - name: "bool", - attr: Attribute{ - Type: types.BoolType, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "bool", - Type: tftypes.Bool, - Optional: true, - }, - }, - "attr-number": { - name: "number", - attr: Attribute{ - Type: types.NumberType, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "number", - Type: tftypes.Number, - Optional: true, - }, - }, - "attr-list": { - name: "list", - attr: Attribute{ - Type: types.ListType{ElemType: types.NumberType}, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "list", - Type: tftypes.List{ElementType: tftypes.Number}, - Optional: true, - }, - }, - "attr-map": { - name: "map", - attr: Attribute{ - Type: types.MapType{ElemType: types.StringType}, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "map", - Type: tftypes.Map{ElementType: tftypes.String}, - Optional: true, - }, - }, - "attr-object": { - name: "object", - attr: Attribute{ - Type: types.ObjectType{AttrTypes: map[string]attr.Type{ - "foo": types.StringType, - "bar": types.NumberType, - "baz": types.BoolType, - }}, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "object", - Type: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ - "foo": tftypes.String, - "bar": tftypes.Number, - "baz": tftypes.Bool, - }}, - Optional: true, - }, - }, - "attr-set": { - name: "set", - attr: Attribute{ - Type: types.SetType{ElemType: types.NumberType}, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "set", - Type: tftypes.Set{ElementType: tftypes.Number}, - Optional: true, - }, - }, - // TODO: add tuple attribute when we support it - "required": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Required: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - "optional": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Optional: true, - }, - }, - "computed": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Computed: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Computed: true, - }, - }, - "optional-computed": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Computed: true, - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Computed: true, - Optional: true, - }, - }, - "sensitive": { - name: "string", - attr: Attribute{ - Type: types.StringType, - Optional: true, - Sensitive: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "string", - Type: tftypes.String, - Optional: true, - Sensitive: true, - }, - }, - "nested-attr-single": { - name: "single_nested", - attr: Attribute{ - Attributes: SingleNestedAttributes(map[string]Attribute{ - "string": { - Type: types.StringType, - Optional: true, - }, - "computed": { - Type: types.NumberType, - Computed: true, - Sensitive: true, - }, - }), - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "single_nested", - Optional: true, - NestedType: &tfprotov6.SchemaObject{ - Nesting: tfprotov6.SchemaObjectNestingModeSingle, - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "computed", - Computed: true, - Sensitive: true, - Type: tftypes.Number, - }, - { - Name: "string", - Optional: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - "nested-attr-list": { - name: "list_nested", - attr: Attribute{ - Attributes: ListNestedAttributes(map[string]Attribute{ - "string": { - Type: types.StringType, - Optional: true, - }, - "computed": { - Type: types.NumberType, - Computed: true, - Sensitive: true, - }, - }, ListNestedAttributesOptions{}), - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "list_nested", - Optional: true, - NestedType: &tfprotov6.SchemaObject{ - Nesting: tfprotov6.SchemaObjectNestingModeList, - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "computed", - Computed: true, - Sensitive: true, - Type: tftypes.Number, - }, - { - Name: "string", - Optional: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - "nested-attr-set": { - name: "set_nested", - attr: Attribute{ - Attributes: SetNestedAttributes(map[string]Attribute{ - "string": { - Type: types.StringType, - Optional: true, - }, - "computed": { - Type: types.NumberType, - Computed: true, - Sensitive: true, - }, - }, SetNestedAttributesOptions{}), - Optional: true, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaAttribute{ - Name: "set_nested", - Optional: true, - NestedType: &tfprotov6.SchemaObject{ - Nesting: tfprotov6.SchemaObjectNestingModeSet, - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "computed", - Computed: true, - Sensitive: true, - Type: tftypes.Number, - }, - { - Name: "string", - Optional: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - "attr-and-nested-attr-set": { - name: "whoops", - attr: Attribute{ - Type: types.StringType, - Attributes: SingleNestedAttributes(map[string]Attribute{ - "testing": { - Type: types.StringType, - Optional: true, - }, - }), - Optional: true, - }, - path: tftypes.NewAttributePath(), - expectedErr: "cannot have both Attributes and Type set", - }, - "attr-and-nested-attr-unset": { - name: "whoops", - attr: Attribute{ - Optional: true, - }, - path: tftypes.NewAttributePath(), - expectedErr: "must have Attributes or Type set", - }, - "attr-and-nested-attr-empty": { - name: "whoops", - attr: Attribute{ - Optional: true, - Attributes: SingleNestedAttributes(map[string]Attribute{}), - }, - path: tftypes.NewAttributePath(), - expectedErr: "must have Attributes or Type set", - }, - "missing-required-optional-and-computed": { - name: "whoops", - attr: Attribute{ - Type: types.StringType, - }, - path: tftypes.NewAttributePath(), - expectedErr: "must have Required, Optional, or Computed set", - }, - } - - for name, tc := range tests { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - got, err := tc.attr.tfprotov6SchemaAttribute(context.Background(), tc.name, tc.path) - if err != nil { - if tc.expectedErr == "" { - t.Errorf("Unexpected error: %s", err) - return - } - if err.Error() != tc.expectedErr { - t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error()) - return - } - // got expected error - return - } - if err == nil && tc.expectedErr != "" { - t.Errorf("Expected error to be %q, got nil", tc.expectedErr) - return - } - if diff := cmp.Diff(got, tc.expected); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - return - } - }) - } -} - -func TestAttributeModifyPlan(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - req ModifyAttributePlanRequest - resp ModifySchemaPlanResponse // Plan automatically copied from req - expectedResp ModifySchemaPlanResponse - }{ - "config-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - "config-error-previous-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - "plan-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Plan Read Error", - "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - }, - }, - "plan-error-previous-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Plan Read Error", - "An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - }, - }, - "state-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "State Read Error", - "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - "state-error-previous-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "State Read Error", - "An unexpected error was encountered trying to read an attribute from the state. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - "no-plan-modifiers": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - "attribute-plan": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "MODIFIED_TWO"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - }, - }, - "attribute-plan-previous-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "MODIFIED_TWO"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }, - }, - }, - }, - }, - }, - }, - "requires-replacement": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "newtestvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "newtestvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "newtestvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test"), - }, - }, - }, - "requires-replacement-previous-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "newtestvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "newtestvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "newtestvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test"), - }, - }, - }, - "requires-replacement-passthrough": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - RequiresReplace(), - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - testAttrPlanValueModifierOne{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRONE"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - testAttrPlanValueModifierOne{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTATTRTWO"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test"), - }, - }, - }, - "requires-replacement-unset": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - testRequiresReplaceFalseModifier{}, - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - testRequiresReplaceFalseModifier{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - RequiresReplace(), - testRequiresReplaceFalseModifier{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - "warnings": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - // Diagnostics.Append() deduplicates, so the warning will only - // be here once unless the test implementation is changed to - // different modifiers or the modifier itself is changed. - diag.NewWarningDiagnostic( - "Warning diag", - "This is a warning", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - }, - }, - "warnings-previous-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - // Diagnostics.Append() deduplicates, so the warning will only - // be here once unless the test implementation is changed to - // different modifiers or the modifier itself is changed. - diag.NewWarningDiagnostic( - "Warning diag", - "This is a warning", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, - }, - }, - }, - }, - }, - }, - "error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Error diag", - "This is an error", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - }, - }, - "error-previous-error": { - req: ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - State: State{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - }, - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - diag.NewErrorDiagnostic( - "Error diag", - "This is an error", - ), - }, - Plan: Plan{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "TESTDIAG"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - PlanModifiers: []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, - }, - }, - }, - }, - }, - }, - } - - for name, tc := range testCases { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - attribute, err := tc.req.Config.Schema.AttributeAtPath(tc.req.AttributePath) - - if err != nil { - t.Fatalf("Unexpected error getting %s", err) - } - - tc.resp.Plan = tc.req.Plan - - attribute.modifyPlan(context.Background(), tc.req, &tc.resp) - - if diff := cmp.Diff(tc.expectedResp, tc.resp); diff != "" { - t.Errorf("Unexpected response (-wanted, +got): %s", diff) - } - }) - } -} - -func TestAttributeValidate(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - req ValidateAttributeRequest - resp ValidateAttributeResponse - }{ - "no-attributes-or-type": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Invalid Attribute Definition", - "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.", - ), - }, - }, - }, - "both-attributes-and-type": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: SingleNestedAttributes(map[string]Attribute{ - "testing": { - Type: types.StringType, - Optional: true, - }, - }), - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Invalid Attribute Definition", - "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.", - ), - }, - }, - }, - "missing-required-optional-and-computed": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Invalid Attribute Definition", - "Attribute missing Required, Optional, or Computed definition. This is always a problem with the provider and should be reported to the provider developer.", - ), - }, - }, - }, - "config-error": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeErrorDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Configuration Read Error", - "An unexpected error was encountered trying to read an attribute from the configuration. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ - "can't use tftypes.String<\"testvalue\"> as value of List with ElementType types.primitive, can only use tftypes.String values", - ), - }, - }, - }, - "no-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "deprecation-message-known": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Optional: true, - DeprecationMessage: "Use something else instead.", - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeWarningDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Attribute Deprecated", - "Use something else instead.", - ), - }, - }, - }, - "deprecation-message-null": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, nil), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Optional: true, - DeprecationMessage: "Use something else instead.", - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "deprecation-message-unknown": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Optional: true, - DeprecationMessage: "Use something else instead.", - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeWarningDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Attribute Deprecated", - "Use something else instead.", - ), - }, - }, - }, - "warnings": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testWarningAttributeValidator{}, - testWarningAttributeValidator{}, - }, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testWarningDiagnostic1, - testWarningDiagnostic2, - }, - }, - }, - "errors": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - testErrorAttributeValidator{}, - }, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - testErrorDiagnostic2, - }, - }, - }, - "type-with-validate-error": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: testtypes.StringTypeWithValidateError{}, - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test")), - }, - }, - }, - "type-with-validate-warning": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.String, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue(tftypes.String, "testvalue"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Type: testtypes.StringTypeWithValidateWarning{}, - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test")), - }, - }, - }, - "nested-attr-list-no-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: ListNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, ListNestedAttributesOptions{}), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "nested-attr-list-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: ListNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - }, ListNestedAttributesOptions{}), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - }, - }, - }, - "nested-attr-map-no-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Map{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Map{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - map[string]tftypes.Value{ - "testkey": tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: MapNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, MapNestedAttributesOptions{}), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "nested-attr-map-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Map{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Map{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - map[string]tftypes.Value{ - "testkey": tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: MapNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - }, MapNestedAttributesOptions{}), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - }, - }, - }, - "nested-attr-set-no-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: SetNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, SetNestedAttributesOptions{}), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "nested-attr-set-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: SetNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - }, SetNestedAttributesOptions{}), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - }, - }, - }, - "nested-attr-single-no-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: SingleNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "nested-attr-single-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - Schema: Schema{ - Attributes: map[string]Attribute{ - "test": { - Attributes: SingleNestedAttributes(map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - }), - Required: true, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - }, - }, - }, - } - - for name, tc := range testCases { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - var got ValidateAttributeResponse - attribute, err := tc.req.Config.Schema.AttributeAtPath(tc.req.AttributePath) - - if err != nil { - t.Fatalf("Unexpected error getting %s", err) - } - - attribute.validate(context.Background(), tc.req, &got) - - if diff := cmp.Diff(got, tc.resp); diff != "" { - t.Errorf("Unexpected response (+wanted, -got): %s", diff) - } - }) - } -} diff --git a/tfsdk/attribute_validation_test.go b/tfsdk/attribute_validation_test.go deleted file mode 100644 index e72eb502f..000000000 --- a/tfsdk/attribute_validation_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package tfsdk - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" -) - -var ( - testErrorDiagnostic1 = diag.NewErrorDiagnostic( - "Error Diagnostic 1", - "This is an error.", - ) - testErrorDiagnostic2 = diag.NewErrorDiagnostic( - "Error Diagnostic 2", - "This is an error.", - ) - testWarningDiagnostic1 = diag.NewWarningDiagnostic( - "Warning Diagnostic 1", - "This is a warning.", - ) - testWarningDiagnostic2 = diag.NewWarningDiagnostic( - "Warning Diagnostic 2", - "This is a warning.", - ) -) - -type testErrorAttributeValidator struct { - AttributeValidator -} - -func (v testErrorAttributeValidator) Description(ctx context.Context) string { - return "validation that always returns an error" -} - -func (v testErrorAttributeValidator) MarkdownDescription(ctx context.Context) string { - return v.Description(ctx) -} - -func (v testErrorAttributeValidator) Validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { - if len(resp.Diagnostics) == 0 { - resp.Diagnostics.Append(testErrorDiagnostic1) - } else { - resp.Diagnostics.Append(testErrorDiagnostic2) - } -} - -type testWarningAttributeValidator struct { - AttributeValidator -} - -func (v testWarningAttributeValidator) Description(ctx context.Context) string { - return "validation that always returns a warning" -} - -func (v testWarningAttributeValidator) MarkdownDescription(ctx context.Context) string { - return v.Description(ctx) -} - -func (v testWarningAttributeValidator) Validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { - if len(resp.Diagnostics) == 0 { - resp.Diagnostics.Append(testWarningDiagnostic1) - } else { - resp.Diagnostics.Append(testWarningDiagnostic2) - } -} diff --git a/tfsdk/block.go b/tfsdk/block.go index 49982566e..30d5c7309 100644 --- a/tfsdk/block.go +++ b/tfsdk/block.go @@ -3,12 +3,10 @@ package tfsdk import ( "context" "fmt" - "sort" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -154,405 +152,11 @@ func (b Block) attributeType() attr.Type { } } -// modifyPlan performs all Block plan modification. -func (b Block) modifyPlan(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifySchemaPlanResponse) { - attributeConfig, diags := req.Config.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - if diags.HasError() { - return - } - - req.AttributeConfig = attributeConfig - - attributePlan, diags := req.Plan.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - if diags.HasError() { - return - } - - req.AttributePlan = attributePlan - - attributeState, diags := req.State.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - if diags.HasError() { - return - } - - req.AttributeState = attributeState - - var requiresReplace bool - for _, planModifier := range b.PlanModifiers { - modifyResp := &ModifyAttributePlanResponse{ - AttributePlan: req.AttributePlan, - RequiresReplace: requiresReplace, - } - - planModifier.Modify(ctx, req, modifyResp) - - req.AttributePlan = modifyResp.AttributePlan - resp.Diagnostics.Append(modifyResp.Diagnostics...) - requiresReplace = modifyResp.RequiresReplace - - // Only on new errors. - if modifyResp.Diagnostics.HasError() { - return - } - } - - if requiresReplace { - resp.RequiresReplace = append(resp.RequiresReplace, req.AttributePath) - } - - setAttrDiags := resp.Plan.SetAttribute(ctx, req.AttributePath, req.AttributePlan) - resp.Diagnostics.Append(setAttrDiags...) - - if setAttrDiags.HasError() { - return - } - - nm := b.NestingMode - switch nm { - case BlockNestingModeList: - l, ok := req.AttributePlan.(types.List) - - if !ok { - err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Plan Modification Error", - "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for idx := range l.Elems { - for name, attr := range b.Attributes { - attrReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - attr.modifyPlan(ctx, attrReq, resp) - } - - for name, block := range b.Blocks { - blockReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - block.modifyPlan(ctx, blockReq, resp) - } - } - case BlockNestingModeSet: - s, ok := req.AttributePlan.(types.Set) - - if !ok { - err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Plan Modification Error", - "Block plan modification cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for _, value := range s.Elems { - tfValue, err := value.ToTerraformValue(ctx) - if err != nil { - err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Plan Modification Error", - "Block plan modification cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for name, attr := range b.Attributes { - attrReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - attr.modifyPlan(ctx, attrReq, resp) - } - - for name, block := range b.Blocks { - blockReq := ModifyAttributePlanRequest{ - AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), - Config: req.Config, - Plan: resp.Plan, - ProviderMeta: req.ProviderMeta, - State: req.State, - } - - block.modifyPlan(ctx, blockReq, resp) - } - } - default: - err := fmt.Errorf("unknown block plan modification nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Plan Modification Error", - "Block plan modification cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } -} - // terraformType returns an tftypes.Type corresponding to the block. func (b Block) terraformType(ctx context.Context) tftypes.Type { return b.attributeType().TerraformType(ctx) } -// tfprotov6 returns the *tfprotov6.SchemaNestedBlock equivalent of a Block. -// Errors will be tftypes.AttributePathErrors based on `path`. `name` is the -// name of the attribute. -func (b Block) tfprotov6(ctx context.Context, name string, path *tftypes.AttributePath) (*tfprotov6.SchemaNestedBlock, error) { - schemaNestedBlock := &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Deprecated: b.DeprecationMessage != "", - }, - MinItems: b.MinItems, - MaxItems: b.MaxItems, - TypeName: name, - } - - if b.Description != "" { - schemaNestedBlock.Block.Description = b.Description - schemaNestedBlock.Block.DescriptionKind = tfprotov6.StringKindPlain - } - - if b.MarkdownDescription != "" { - schemaNestedBlock.Block.Description = b.MarkdownDescription - schemaNestedBlock.Block.DescriptionKind = tfprotov6.StringKindMarkdown - } - - nm := b.NestingMode - switch nm { - case BlockNestingModeList: - schemaNestedBlock.Nesting = tfprotov6.SchemaNestedBlockNestingModeList - case BlockNestingModeSet: - schemaNestedBlock.Nesting = tfprotov6.SchemaNestedBlockNestingModeSet - default: - return nil, path.NewErrorf("unrecognized nesting mode %v", nm) - } - - for attrName, attr := range b.Attributes { - attrPath := path.WithAttributeName(attrName) - attrProto6, err := attr.tfprotov6SchemaAttribute(ctx, attrName, attrPath) - - if err != nil { - return nil, err - } - - schemaNestedBlock.Block.Attributes = append(schemaNestedBlock.Block.Attributes, attrProto6) - } - - for blockName, block := range b.Blocks { - blockPath := path.WithAttributeName(blockName) - blockProto6, err := block.tfprotov6(ctx, blockName, blockPath) - - if err != nil { - return nil, err - } - - schemaNestedBlock.Block.BlockTypes = append(schemaNestedBlock.Block.BlockTypes, blockProto6) - } - - sort.Slice(schemaNestedBlock.Block.Attributes, func(i, j int) bool { - if schemaNestedBlock.Block.Attributes[i] == nil { - return true - } - - if schemaNestedBlock.Block.Attributes[j] == nil { - return false - } - - return schemaNestedBlock.Block.Attributes[i].Name < schemaNestedBlock.Block.Attributes[j].Name - }) - - sort.Slice(schemaNestedBlock.Block.BlockTypes, func(i, j int) bool { - if schemaNestedBlock.Block.BlockTypes[i] == nil { - return true - } - - if schemaNestedBlock.Block.BlockTypes[j] == nil { - return false - } - - return schemaNestedBlock.Block.BlockTypes[i].TypeName < schemaNestedBlock.Block.BlockTypes[j].TypeName - }) - - return schemaNestedBlock, nil -} - -// validate performs all Block validation. -func (b Block) validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { - attributeConfig, diags := req.Config.getAttributeValue(ctx, req.AttributePath) - resp.Diagnostics.Append(diags...) - - if diags.HasError() { - return - } - - req.AttributeConfig = attributeConfig - - for _, validator := range b.Validators { - validator.Validate(ctx, req, resp) - } - - nm := b.NestingMode - switch nm { - case BlockNestingModeList: - l, ok := req.AttributeConfig.(types.List) - - if !ok { - err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Validation Error", - "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for idx := range l.Elems { - for name, attr := range b.Attributes { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - attr.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - - for name, block := range b.Blocks { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - block.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - } - case BlockNestingModeSet: - s, ok := req.AttributeConfig.(types.Set) - - if !ok { - err := fmt.Errorf("unknown block value type (%s) for nesting mode (%T) at path: %s", req.AttributeConfig.Type(ctx), nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Validation Error", - "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for _, value := range s.Elems { - tfValue, err := value.ToTerraformValue(ctx) - if err != nil { - err := fmt.Errorf("error running ToTerraformValue on element value: %v", value) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Validation Error", - "Block validation cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - for name, attr := range b.Attributes { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - attr.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - - for name, block := range b.Blocks { - nestedAttrReq := ValidateAttributeRequest{ - AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name), - Config: req.Config, - } - nestedAttrResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - block.validate(ctx, nestedAttrReq, nestedAttrResp) - - resp.Diagnostics = nestedAttrResp.Diagnostics - } - } - default: - err := fmt.Errorf("unknown block validation nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath) - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Validation Error", - "Block validation cannot walk schema. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - if b.DeprecationMessage != "" && attributeConfig != nil { - tfValue, err := attributeConfig.ToTerraformValue(ctx) - - if err != nil { - resp.Diagnostics.AddAttributeError( - req.AttributePath, - "Block Validation Error", - "Block validation cannot convert value. Report this to the provider developer:\n\n"+err.Error(), - ) - - return - } - - if !tfValue.IsNull() { - resp.Diagnostics.AddAttributeWarning( - req.AttributePath, - "Block Deprecated", - b.DeprecationMessage, - ) - } - } -} - type nestedBlock struct { Block } diff --git a/tfsdk/block_test.go b/tfsdk/block_test.go deleted file mode 100644 index 8444b2d6f..000000000 --- a/tfsdk/block_test.go +++ /dev/null @@ -1,2128 +0,0 @@ -package tfsdk - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - testtypes "github.com/hashicorp/terraform-plugin-framework/internal/testing/types" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -func TestBlockModifyPlan(t *testing.T) { - t.Parallel() - - schema := func(blockPlanModifiers AttributePlanModifiers, nestedAttrPlanModifiers AttributePlanModifiers) Schema { - return Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - PlanModifiers: nestedAttrPlanModifiers, - }, - }, - NestingMode: BlockNestingModeList, - PlanModifiers: blockPlanModifiers, - }, - }, - } - } - - schemaTfValue := func(nestedAttrValue string) tftypes.Value { - return tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, nestedAttrValue), - }, - ), - }, - ), - }, - ) - } - - var schemaNullTfValue tftypes.Value = tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - nil, - ), - }, - ) - - type modifyAttributePlanValues struct { - config string - plan string - state string - } - - modifyAttributePlanRequest := func(attrPath *tftypes.AttributePath, schema Schema, values modifyAttributePlanValues) ModifyAttributePlanRequest { - return ModifyAttributePlanRequest{ - AttributePath: attrPath, - Config: Config{ - Raw: schemaTfValue(values.config), - Schema: schema, - }, - Plan: Plan{ - Raw: schemaTfValue(values.plan), - Schema: schema, - }, - State: State{ - Raw: schemaTfValue(values.state), - Schema: schema, - }, - } - } - - testCases := map[string]struct { - req ModifyAttributePlanRequest - resp ModifySchemaPlanResponse // Plan automatically copied from req - expectedResp ModifySchemaPlanResponse - }{ - "no-plan-modifiers": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, nil), - modifyAttributePlanValues{ - config: "testvalue", - plan: "testvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaTfValue("testvalue"), - Schema: schema(nil, nil), - }, - }, - }, - "block-modified": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - testBlockPlanModifierNullList{}, - }, nil), - modifyAttributePlanValues{ - config: "TESTATTRONE", - plan: "TESTATTRONE", - state: "TESTATTRONE", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaNullTfValue, - Schema: schema([]AttributePlanModifier{ - testBlockPlanModifierNullList{}, - }, nil), - }, - }, - }, - "block-modified-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - testBlockPlanModifierNullList{}, - }, nil), - modifyAttributePlanValues{ - config: "TESTATTRONE", - plan: "TESTATTRONE", - state: "TESTATTRONE", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - Plan: Plan{ - Raw: schemaNullTfValue, - Schema: schema([]AttributePlanModifier{ - testBlockPlanModifierNullList{}, - }, nil), - }, - }, - }, - "block-requires-replacement": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - RequiresReplace(), - }, nil), - modifyAttributePlanValues{ - config: "newtestvalue", - plan: "newtestvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaTfValue("newtestvalue"), - Schema: schema([]AttributePlanModifier{ - RequiresReplace(), - }, nil), - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test"), - }, - }, - }, - "block-requires-replacement-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - RequiresReplace(), - }, nil), - modifyAttributePlanValues{ - config: "newtestvalue", - plan: "newtestvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("newtestvalue"), - Schema: schema([]AttributePlanModifier{ - RequiresReplace(), - }, nil), - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test"), - }, - }, - }, - "block-requires-replacement-passthrough": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - RequiresReplace(), - testBlockPlanModifierNullList{}, - }, nil), - modifyAttributePlanValues{ - config: "newtestvalue", - plan: "newtestvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaNullTfValue, - Schema: schema([]AttributePlanModifier{ - RequiresReplace(), - testBlockPlanModifierNullList{}, - }, nil), - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test"), - }, - }, - }, - "block-requires-replacement-unset": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - RequiresReplace(), - testRequiresReplaceFalseModifier{}, - }, nil), - modifyAttributePlanValues{ - config: "newtestvalue", - plan: "newtestvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaTfValue("newtestvalue"), - Schema: schema([]AttributePlanModifier{ - RequiresReplace(), - testRequiresReplaceFalseModifier{}, - }, nil), - }, - }, - }, - "block-warnings": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, nil), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - // Diagnostics.Append() deduplicates, so the warning will only - // be here once unless the test implementation is changed to - // different modifiers or the modifier itself is changed. - diag.NewWarningDiagnostic( - "Warning diag", - "This is a warning", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema([]AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, nil), - }, - }, - }, - "block-warnings-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, nil), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - // Diagnostics.Append() deduplicates, so the warning will only - // be here once unless the test implementation is changed to - // different modifiers or the modifier itself is changed. - diag.NewWarningDiagnostic( - "Warning diag", - "This is a warning", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema([]AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }, nil), - }, - }, - }, - "block-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, nil), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Error diag", - "This is an error", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema([]AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, nil), - }, - }, - }, - "block-error-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema([]AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, nil), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - diag.NewErrorDiagnostic( - "Error diag", - "This is an error", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema([]AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }, nil), - }, - }, - }, - "nested-attribute-modified": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }), - modifyAttributePlanValues{ - config: "TESTATTRONE", - plan: "TESTATTRONE", - state: "TESTATTRONE", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaTfValue("MODIFIED_TWO"), - Schema: schema(nil, []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }), - }, - }, - }, - "nested-attribute-modified-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }), - modifyAttributePlanValues{ - config: "TESTATTRONE", - plan: "TESTATTRONE", - state: "TESTATTRONE", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("MODIFIED_TWO"), - Schema: schema(nil, []AttributePlanModifier{ - testAttrPlanValueModifierOne{}, - testAttrPlanValueModifierTwo{}, - }), - }, - }, - }, - "nested-attribute-requires-replacement": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - RequiresReplace(), - }), - modifyAttributePlanValues{ - config: "newtestvalue", - plan: "newtestvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaTfValue("newtestvalue"), - Schema: schema(nil, []AttributePlanModifier{ - RequiresReplace(), - }), - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr"), - }, - }, - }, - "nested-attribute-requires-replacement-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - RequiresReplace(), - }), - modifyAttributePlanValues{ - config: "newtestvalue", - plan: "newtestvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("newtestvalue"), - Schema: schema(nil, []AttributePlanModifier{ - RequiresReplace(), - }), - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr"), - }, - }, - }, - "nested-attribute-requires-replacement-passthrough": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - RequiresReplace(), - testAttrPlanValueModifierOne{}, - }), - modifyAttributePlanValues{ - config: "TESTATTRONE", - plan: "TESTATTRONE", - state: "previousvalue", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaTfValue("TESTATTRTWO"), - Schema: schema(nil, []AttributePlanModifier{ - RequiresReplace(), - testAttrPlanValueModifierOne{}, - }), - }, - RequiresReplace: []*tftypes.AttributePath{ - tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr"), - }, - }, - }, - "nested-attribute-requires-replacement-unset": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - RequiresReplace(), - testRequiresReplaceFalseModifier{}, - }), - modifyAttributePlanValues{ - config: "newtestvalue", - plan: "newtestvalue", - state: "testvalue", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Plan: Plan{ - Raw: schemaTfValue("newtestvalue"), - Schema: schema(nil, []AttributePlanModifier{ - RequiresReplace(), - testRequiresReplaceFalseModifier{}, - }), - }, - }, - }, - "nested-attribute-warnings": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - // Diagnostics.Append() deduplicates, so the warning will only - // be here once unless the test implementation is changed to - // different modifiers or the modifier itself is changed. - diag.NewWarningDiagnostic( - "Warning diag", - "This is a warning", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema(nil, []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }), - }, - }, - }, - "nested-attribute-warnings-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - // Diagnostics.Append() deduplicates, so the warning will only - // be here once unless the test implementation is changed to - // different modifiers or the modifier itself is changed. - diag.NewWarningDiagnostic( - "Warning diag", - "This is a warning", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema(nil, []AttributePlanModifier{ - testWarningDiagModifier{}, - testWarningDiagModifier{}, - }), - }, - }, - }, - "nested-attribute-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{}, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Error diag", - "This is an error", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema(nil, []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }), - }, - }, - }, - "nested-attribute-error-previous-error": { - req: modifyAttributePlanRequest( - tftypes.NewAttributePath().WithAttributeName("test"), - schema(nil, []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }), - modifyAttributePlanValues{ - config: "TESTDIAG", - plan: "TESTDIAG", - state: "TESTDIAG", - }, - ), - resp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - }, - }, - expectedResp: ModifySchemaPlanResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Previous error diag", - "This was a previous error", - ), - diag.NewErrorDiagnostic( - "Error diag", - "This is an error", - ), - }, - Plan: Plan{ - Raw: schemaTfValue("TESTDIAG"), - Schema: schema(nil, []AttributePlanModifier{ - testErrorDiagModifier{}, - testErrorDiagModifier{}, - }), - }, - }, - }, - } - - for name, tc := range testCases { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - block, err := tc.req.Config.Schema.blockAtPath(tc.req.AttributePath) - - if err != nil { - t.Fatalf("Unexpected error getting %s", err) - } - - tc.resp.Plan = tc.req.Plan - - block.modifyPlan(context.Background(), tc.req, &tc.resp) - - if diff := cmp.Diff(tc.expectedResp, tc.resp); diff != "" { - t.Errorf("Unexpected response (+wanted, -got): %s", diff) - } - }) - } -} - -func TestBlockTfprotov6(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - block Block - path *tftypes.AttributePath - expected *tfprotov6.SchemaNestedBlock - expectedErr string - } - - tests := map[string]testCase{ - "nestingmode-invalid": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - }, - path: tftypes.NewAttributePath(), - expectedErr: "unrecognized nesting mode 0", - }, - "nestingmode-list-attributes": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "nestingmode-list-attributes-and-blocks": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_attr": { - Type: types.StringType, - Optional: true, - }, - }, - Blocks: map[string]Block{ - "sub_block": { - Attributes: map[string]Attribute{ - "sub_block_attr": { - Type: types.StringType, - Optional: true, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_attr", - Optional: true, - Type: tftypes.String, - }, - }, - BlockTypes: []*tfprotov6.SchemaNestedBlock{ - { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_block_attr", - Optional: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "sub_block", - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "nestingmode-list-blocks": { - name: "test", - block: Block{ - Blocks: map[string]Block{ - "sub_block": { - Attributes: map[string]Attribute{ - "sub_block_attr": { - Type: types.StringType, - Optional: true, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - BlockTypes: []*tfprotov6.SchemaNestedBlock{ - { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_block_attr", - Optional: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "sub_block", - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "nestingmode-set-attributes": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - NestingMode: BlockNestingModeSet, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, - TypeName: "test", - }, - }, - "nestingmode-set-attributes-and-blocks": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_attr": { - Type: types.StringType, - Optional: true, - }, - }, - Blocks: map[string]Block{ - "sub_block": { - Attributes: map[string]Attribute{ - "sub_block_attr": { - Type: types.StringType, - Optional: true, - }, - }, - NestingMode: BlockNestingModeSet, - }, - }, - NestingMode: BlockNestingModeSet, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_attr", - Optional: true, - Type: tftypes.String, - }, - }, - BlockTypes: []*tfprotov6.SchemaNestedBlock{ - { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_block_attr", - Optional: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, - TypeName: "sub_block", - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, - TypeName: "test", - }, - }, - "nestingmode-set-blocks": { - name: "test", - block: Block{ - Blocks: map[string]Block{ - "sub_block": { - Attributes: map[string]Attribute{ - "sub_block_attr": { - Type: types.StringType, - Optional: true, - }, - }, - NestingMode: BlockNestingModeSet, - }, - }, - NestingMode: BlockNestingModeSet, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - BlockTypes: []*tfprotov6.SchemaNestedBlock{ - { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_block_attr", - Optional: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, - TypeName: "sub_block", - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, - TypeName: "test", - }, - }, - "deprecationmessage": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - DeprecationMessage: "deprecated, use something else instead", - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - Deprecated: true, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "description": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - Description: "test description", - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - Description: "test description", - DescriptionKind: tfprotov6.StringKindPlain, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "description-and-markdowndescription": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - Description: "test plain description", - MarkdownDescription: "test markdown description", - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - Description: "test markdown description", - DescriptionKind: tfprotov6.StringKindMarkdown, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "markdowndescription": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - MarkdownDescription: "test description", - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - Description: "test description", - DescriptionKind: tfprotov6.StringKindMarkdown, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "maxitems": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - MaxItems: 10, - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - }, - MaxItems: 10, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - "minitems": { - name: "test", - block: Block{ - Attributes: map[string]Attribute{ - "sub_test": { - Type: types.StringType, - Optional: true, - }, - }, - MinItems: 10, - NestingMode: BlockNestingModeList, - }, - path: tftypes.NewAttributePath(), - expected: &tfprotov6.SchemaNestedBlock{ - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "sub_test", - Optional: true, - Type: tftypes.String, - }, - }, - }, - MinItems: 10, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "test", - }, - }, - } - - for name, tc := range tests { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - got, err := tc.block.tfprotov6(context.Background(), tc.name, tc.path) - if err != nil { - if tc.expectedErr == "" { - t.Errorf("Unexpected error: %s", err) - return - } - if err.Error() != tc.expectedErr { - t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error()) - return - } - // got expected error - return - } - if err == nil && tc.expectedErr != "" { - t.Errorf("Expected error to be %q, got nil", tc.expectedErr) - return - } - if diff := cmp.Diff(got, tc.expected); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - return - } - }) - } -} - -func TestBlockValidate(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - req ValidateAttributeRequest - resp ValidateAttributeResponse - }{ - "deprecation-message-known": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, - DeprecationMessage: "Use something else instead.", - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeWarningDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Block Deprecated", - "Use something else instead.", - ), - }, - }, - }, - "deprecation-message-null": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - nil, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, - DeprecationMessage: "Use something else instead.", - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "deprecation-message-unknown": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - tftypes.UnknownValue, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, - DeprecationMessage: "Use something else instead.", - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewAttributeWarningDiagnostic( - tftypes.NewAttributePath().WithAttributeName("test"), - "Block Deprecated", - "Use something else instead.", - ), - }, - }, - }, - "warnings": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, - NestingMode: BlockNestingModeList, - Validators: []AttributeValidator{ - testWarningAttributeValidator{}, - testWarningAttributeValidator{}, - }, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testWarningDiagnostic1, - testWarningDiagnostic2, - }, - }, - }, - "errors": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, - NestingMode: BlockNestingModeList, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - testErrorAttributeValidator{}, - }, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - testErrorDiagnostic2, - }, - }, - }, - "nested-attr-warnings": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testWarningAttributeValidator{}, - testWarningAttributeValidator{}, - }, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testWarningDiagnostic1, - testWarningDiagnostic2, - }, - }, - }, - "nested-attr-errors": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - testErrorAttributeValidator{}, - }, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - testErrorDiagnostic2, - }, - }, - }, - "nested-attr-type-with-validate-error": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: testtypes.StringTypeWithValidateError{}, - Required: true, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testtypes.TestErrorDiagnostic(tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr")), - }, - }, - }, - "nested-attr-type-with-validate-warning": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: testtypes.StringTypeWithValidateWarning{}, - Required: true, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testtypes.TestWarningDiagnostic(tftypes.NewAttributePath().WithAttributeName("test").WithElementKeyInt(0).WithAttributeName("nested_attr")), - }, - }, - }, - "list-no-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "list-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.List{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - }, - NestingMode: BlockNestingModeList, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - }, - }, - }, - "set-no-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - }, - }, - NestingMode: BlockNestingModeSet, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{}, - }, - "set-validation": { - req: ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), - Config: Config{ - Raw: tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "test": tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - }, - }, - map[string]tftypes.Value{ - "test": tftypes.NewValue( - tftypes.Set{ - ElementType: tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - }, - []tftypes.Value{ - tftypes.NewValue( - tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "nested_attr": tftypes.String, - }, - }, - map[string]tftypes.Value{ - "nested_attr": tftypes.NewValue(tftypes.String, "testvalue"), - }, - ), - }, - ), - }, - ), - Schema: Schema{ - Blocks: map[string]Block{ - "test": { - Attributes: map[string]Attribute{ - "nested_attr": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - }, - NestingMode: BlockNestingModeSet, - }, - }, - }, - }, - }, - resp: ValidateAttributeResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - }, - }, - }, - } - - for name, tc := range testCases { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - var got ValidateAttributeResponse - block, err := tc.req.Config.Schema.blockAtPath(tc.req.AttributePath) - - if err != nil { - t.Fatalf("Unexpected error getting %s", err) - } - - block.validate(context.Background(), tc.req, &got) - - if diff := cmp.Diff(got, tc.resp); diff != "" { - t.Errorf("Unexpected response (+wanted, -got): %s", diff) - } - }) - } -} - -type testBlockPlanModifierNullList struct{} - -func (t testBlockPlanModifierNullList) Modify(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { - _, ok := req.AttributePlan.(types.List) - if !ok { - return - } - - resp.AttributePlan = types.List{ - ElemType: types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "nested_attr": types.StringType, - }, - }, - Null: true, - } -} - -func (t testBlockPlanModifierNullList) Description(ctx context.Context) string { - return "This plan modifier is for use during testing only" -} - -func (t testBlockPlanModifierNullList) MarkdownDescription(ctx context.Context) string { - return "This plan modifier is for use during testing only" -} diff --git a/tfsdk/schema.go b/tfsdk/schema.go index f3e21351b..5632db554 100644 --- a/tfsdk/schema.go +++ b/tfsdk/schema.go @@ -4,11 +4,9 @@ import ( "context" "errors" "fmt" - "sort" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -169,162 +167,3 @@ func (s Schema) AttributeAtPath(path *tftypes.AttributePath) (Attribute, error) return Attribute{}, fmt.Errorf("got unexpected type %T", res) } } - -// blockAtPath returns the Block at the passed path. If the path points -// to an element or attribute of a complex type, rather than to a Block, -// it will return an ErrPathInsideAtomicAttribute error. -func (s Schema) blockAtPath(path *tftypes.AttributePath) (Block, error) { - res, remaining, err := tftypes.WalkAttributePath(s, path) - if err != nil { - return Block{}, fmt.Errorf("%v still remains in the path: %w", remaining, err) - } - - switch r := res.(type) { - case nestedBlock: - return Block{}, ErrPathInsideAtomicAttribute - case Block: - return r, nil - default: - return Block{}, fmt.Errorf("got unexpected type %T", res) - } -} - -// tfprotov6Schema returns the *tfprotov6.Schema equivalent of a Schema. -func (s Schema) tfprotov6Schema(ctx context.Context) (*tfprotov6.Schema, error) { - result := &tfprotov6.Schema{ - Version: s.Version, - } - - var attrs []*tfprotov6.SchemaAttribute - var blocks []*tfprotov6.SchemaNestedBlock - - for name, attr := range s.Attributes { - a, err := attr.tfprotov6SchemaAttribute(ctx, name, tftypes.NewAttributePath().WithAttributeName(name)) - - if err != nil { - return nil, err - } - - attrs = append(attrs, a) - } - - for name, block := range s.Blocks { - proto6, err := block.tfprotov6(ctx, name, tftypes.NewAttributePath().WithAttributeName(name)) - - if err != nil { - return nil, err - } - - blocks = append(blocks, proto6) - } - - sort.Slice(attrs, func(i, j int) bool { - if attrs[i] == nil { - return true - } - - if attrs[j] == nil { - return false - } - - return attrs[i].Name < attrs[j].Name - }) - - sort.Slice(blocks, func(i, j int) bool { - if blocks[i] == nil { - return true - } - - if blocks[j] == nil { - return false - } - - return blocks[i].TypeName < blocks[j].TypeName - }) - - result.Block = &tfprotov6.SchemaBlock{ - // core doesn't do anything with version, as far as I can tell, - // so let's not set it. - Attributes: attrs, - BlockTypes: blocks, - Deprecated: s.DeprecationMessage != "", - } - - if s.Description != "" { - result.Block.Description = s.Description - result.Block.DescriptionKind = tfprotov6.StringKindPlain - } - - if s.MarkdownDescription != "" { - result.Block.Description = s.MarkdownDescription - result.Block.DescriptionKind = tfprotov6.StringKindMarkdown - } - - return result, nil -} - -// validate performs all Attribute validation. -func (s Schema) validate(ctx context.Context, req ValidateSchemaRequest, resp *ValidateSchemaResponse) { - for name, attribute := range s.Attributes { - - attributeReq := ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName(name), - Config: req.Config, - } - attributeResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - attribute.validate(ctx, attributeReq, attributeResp) - - resp.Diagnostics = attributeResp.Diagnostics - } - - for name, block := range s.Blocks { - attributeReq := ValidateAttributeRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName(name), - Config: req.Config, - } - attributeResp := &ValidateAttributeResponse{ - Diagnostics: resp.Diagnostics, - } - - block.validate(ctx, attributeReq, attributeResp) - - resp.Diagnostics = attributeResp.Diagnostics - } - - if s.DeprecationMessage != "" { - resp.Diagnostics.AddWarning( - "Deprecated", - s.DeprecationMessage, - ) - } -} - -// modifyPlan runs all AttributePlanModifiers in all schema attributes and blocks -func (s Schema) modifyPlan(ctx context.Context, req ModifySchemaPlanRequest, resp *ModifySchemaPlanResponse) { - for name, attr := range s.Attributes { - attrReq := ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName(name), - Config: req.Config, - State: req.State, - Plan: req.Plan, - ProviderMeta: req.ProviderMeta, - } - - attr.modifyPlan(ctx, attrReq, resp) - } - - for name, block := range s.Blocks { - blockReq := ModifyAttributePlanRequest{ - AttributePath: tftypes.NewAttributePath().WithAttributeName(name), - Config: req.Config, - State: req.State, - Plan: req.Plan, - ProviderMeta: req.ProviderMeta, - } - - block.modifyPlan(ctx, blockReq, resp) - } -} diff --git a/tfsdk/schema_plan_modification.go b/tfsdk/schema_plan_modification.go deleted file mode 100644 index 73c59f223..000000000 --- a/tfsdk/schema_plan_modification.go +++ /dev/null @@ -1,39 +0,0 @@ -package tfsdk - -import ( - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -// ModifySchemaPlanRequest represents a request for a schema to run all -// attribute plan modification functions. -type ModifySchemaPlanRequest struct { - // Config is the configuration the user supplied for the resource. - Config Config - - // State is the current state of the resource. - State State - - // Plan is the planned new state for the resource. - Plan Plan - - // ProviderMeta is metadata from the provider_meta block of the module. - ProviderMeta Config -} - -// ModifySchemaPlanResponse represents a response to a ModifySchemaPlanRequest. -type ModifySchemaPlanResponse struct { - // Plan is the planned new state for the resource. - Plan Plan - - // RequiresReplace is a list of tftypes.AttributePaths that require the - // resource to be replaced. They should point to the specific field - // that changed that requires the resource to be destroyed and - // recreated. - RequiresReplace []*tftypes.AttributePath - - // Diagnostics report errors or warnings related to running all attribute - // plan modifiers. Returning an empty slice indicates a successful - // plan modification with no warnings or errors generated. - Diagnostics diag.Diagnostics -} diff --git a/tfsdk/schema_test.go b/tfsdk/schema_test.go index a0f4e81cb..b5a4682ac 100644 --- a/tfsdk/schema_test.go +++ b/tfsdk/schema_test.go @@ -1,14 +1,11 @@ package tfsdk import ( - "context" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -1293,735 +1290,3 @@ func TestSchemaAttributeType(t *testing.T) { t.Fatalf("types not equal (+wanted, -got): %s", cmp.Diff(expectedType, actualType)) } } - -func TestSchemaTfprotov6Schema(t *testing.T) { - t.Parallel() - - type testCase struct { - input Schema - expected *tfprotov6.Schema - expectedErr string - } - - tests := map[string]testCase{ - "empty-val": { - input: Schema{}, - expected: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - Version: 0, - }, - }, - "basic-attrs": { - input: Schema{ - Version: 1, - Attributes: map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - "number": { - Type: types.NumberType, - Optional: true, - }, - "bool": { - Type: types.BoolType, - Computed: true, - }, - }, - }, - expected: &tfprotov6.Schema{ - Version: 1, - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "bool", - Type: tftypes.Bool, - Computed: true, - }, - { - Name: "number", - Type: tftypes.Number, - Optional: true, - }, - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - }, - }, - }, - "complex-attrs": { - input: Schema{ - Version: 2, - Attributes: map[string]Attribute{ - "list": { - Type: types.ListType{ElemType: types.StringType}, - Required: true, - }, - "object": { - Type: types.ObjectType{AttrTypes: map[string]attr.Type{ - "string": types.StringType, - "number": types.NumberType, - "bool": types.BoolType, - }}, - Optional: true, - }, - "map": { - Type: types.MapType{ElemType: types.NumberType}, - Computed: true, - }, - "set": { - Type: types.SetType{ElemType: types.StringType}, - Required: true, - }, - // TODO: add tuple support when it lands - }, - }, - expected: &tfprotov6.Schema{ - Version: 2, - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "list", - Type: tftypes.List{ElementType: tftypes.String}, - Required: true, - }, - { - Name: "map", - Type: tftypes.Map{ElementType: tftypes.Number}, - Computed: true, - }, - { - Name: "object", - Type: tftypes.Object{AttributeTypes: map[string]tftypes.Type{ - "string": tftypes.String, - "number": tftypes.Number, - "bool": tftypes.Bool, - }}, - Optional: true, - }, - { - Name: "set", - Type: tftypes.Set{ElementType: tftypes.String}, - Required: true, - }, - }, - }, - }, - }, - "nested-attrs": { - input: Schema{ - Version: 3, - Attributes: map[string]Attribute{ - "single": { - Attributes: SingleNestedAttributes(map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - "number": { - Type: types.NumberType, - Optional: true, - }, - "bool": { - Type: types.BoolType, - Computed: true, - }, - "list": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Optional: true, - }, - }), - Required: true, - }, - "list": { - Attributes: ListNestedAttributes(map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - "number": { - Type: types.NumberType, - Optional: true, - }, - "bool": { - Type: types.BoolType, - Computed: true, - }, - "list": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Optional: true, - }, - }, ListNestedAttributesOptions{}), - Optional: true, - }, - "set": { - Attributes: SetNestedAttributes(map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - "number": { - Type: types.NumberType, - Optional: true, - }, - "bool": { - Type: types.BoolType, - Computed: true, - }, - "list": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Optional: true, - }, - }, SetNestedAttributesOptions{}), - Computed: true, - }, - "map": { - Attributes: MapNestedAttributes(map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - "number": { - Type: types.NumberType, - Optional: true, - }, - "bool": { - Type: types.BoolType, - Computed: true, - }, - "list": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Optional: true, - }, - }, MapNestedAttributesOptions{}), - Optional: true, - Computed: true, - }, - }, - }, - expected: &tfprotov6.Schema{ - Version: 3, - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "list", - NestedType: &tfprotov6.SchemaObject{ - Nesting: tfprotov6.SchemaObjectNestingModeList, - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "bool", - Type: tftypes.Bool, - Computed: true, - }, - { - Name: "list", - Type: tftypes.List{ElementType: tftypes.String}, - Optional: true, - Computed: true, - }, - { - Name: "number", - Type: tftypes.Number, - Optional: true, - }, - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - }, - Optional: true, - }, - { - Name: "map", - NestedType: &tfprotov6.SchemaObject{ - Nesting: tfprotov6.SchemaObjectNestingModeMap, - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "bool", - Type: tftypes.Bool, - Computed: true, - }, - { - Name: "list", - Type: tftypes.List{ElementType: tftypes.String}, - Optional: true, - Computed: true, - }, - { - Name: "number", - Type: tftypes.Number, - Optional: true, - }, - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - }, - Optional: true, - Computed: true, - }, - { - Name: "set", - NestedType: &tfprotov6.SchemaObject{ - Nesting: tfprotov6.SchemaObjectNestingModeSet, - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "bool", - Type: tftypes.Bool, - Computed: true, - }, - { - Name: "list", - Type: tftypes.List{ElementType: tftypes.String}, - Optional: true, - Computed: true, - }, - { - Name: "number", - Type: tftypes.Number, - Optional: true, - }, - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - }, - Computed: true, - }, - { - Name: "single", - NestedType: &tfprotov6.SchemaObject{ - Nesting: tfprotov6.SchemaObjectNestingModeSingle, - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "bool", - Type: tftypes.Bool, - Computed: true, - }, - { - Name: "list", - Type: tftypes.List{ElementType: tftypes.String}, - Optional: true, - Computed: true, - }, - { - Name: "number", - Type: tftypes.Number, - Optional: true, - }, - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - }, - Required: true, - }, - }, - }, - }, - }, - "nested-blocks": { - input: Schema{ - Version: 3, - Blocks: map[string]Block{ - "list": { - Attributes: map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - "number": { - Type: types.NumberType, - Optional: true, - }, - "bool": { - Type: types.BoolType, - Computed: true, - }, - "list": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Optional: true, - }, - }, - NestingMode: BlockNestingModeList, - }, - "set": { - Attributes: map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - "number": { - Type: types.NumberType, - Optional: true, - }, - "bool": { - Type: types.BoolType, - Computed: true, - }, - "list": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Optional: true, - }, - }, - NestingMode: BlockNestingModeSet, - }, - }, - }, - expected: &tfprotov6.Schema{ - Version: 3, - Block: &tfprotov6.SchemaBlock{ - BlockTypes: []*tfprotov6.SchemaNestedBlock{ - { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Computed: true, - Name: "bool", - Type: tftypes.Bool, - }, - { - Computed: true, - Name: "list", - Optional: true, - Type: tftypes.List{ElementType: tftypes.String}, - }, - { - Name: "number", - Optional: true, - Type: tftypes.Number, - }, - { - Name: "string", - Required: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeList, - TypeName: "list", - }, - { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Computed: true, - Name: "bool", - Type: tftypes.Bool, - }, - { - Computed: true, - Name: "list", - Optional: true, - Type: tftypes.List{ElementType: tftypes.String}, - }, - { - Name: "number", - Optional: true, - Type: tftypes.Number, - }, - { - Name: "string", - Required: true, - Type: tftypes.String, - }, - }, - }, - Nesting: tfprotov6.SchemaNestedBlockNestingModeSet, - TypeName: "set", - }, - }, - }, - }, - }, - "markdown-description": { - input: Schema{ - Version: 1, - Attributes: map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - }, - MarkdownDescription: "a test resource", - }, - expected: &tfprotov6.Schema{ - Version: 1, - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - Description: "a test resource", - DescriptionKind: tfprotov6.StringKindMarkdown, - }, - }, - }, - "plaintext-description": { - input: Schema{ - Version: 1, - Attributes: map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - }, - Description: "a test resource", - }, - expected: &tfprotov6.Schema{ - Version: 1, - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - Description: "a test resource", - DescriptionKind: tfprotov6.StringKindPlain, - }, - }, - }, - "deprecated": { - input: Schema{ - Version: 1, - Attributes: map[string]Attribute{ - "string": { - Type: types.StringType, - Required: true, - }, - }, - DeprecationMessage: "deprecated, use other_resource instead", - }, - expected: &tfprotov6.Schema{ - Version: 1, - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "string", - Type: tftypes.String, - Required: true, - }, - }, - Deprecated: true, - }, - }, - }, - } - - for name, tc := range tests { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - got, err := tc.input.tfprotov6Schema(context.Background()) - if err != nil { - if tc.expectedErr == "" { - t.Errorf("Unexpected error: %s", err) - return - } - if err.Error() != tc.expectedErr { - t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error()) - return - } - // got expected error - return - } - if err == nil && tc.expectedErr != "" { - t.Errorf("Expected error to be %q, got nil", tc.expectedErr) - return - } - if diff := cmp.Diff(got, tc.expected); diff != "" { - t.Errorf("Unexpected diff (+wanted, -got): %s", diff) - return - } - }) - } -} - -func TestSchemaValidate(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - req ValidateSchemaRequest - resp ValidateSchemaResponse - }{ - "no-validation": { - req: ValidateSchemaRequest{ - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "attr1": tftypes.String, - "attr2": tftypes.String, - }, - }, map[string]tftypes.Value{ - "attr1": tftypes.NewValue(tftypes.String, "attr1value"), - "attr2": tftypes.NewValue(tftypes.String, "attr2value"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "attr1": { - Type: types.StringType, - Required: true, - }, - "attr2": { - Type: types.StringType, - Required: true, - }, - }, - }, - }, - }, - resp: ValidateSchemaResponse{}, - }, - "deprecation-message": { - req: ValidateSchemaRequest{ - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "attr1": tftypes.String, - "attr2": tftypes.String, - }, - }, map[string]tftypes.Value{ - "attr1": tftypes.NewValue(tftypes.String, "attr1value"), - "attr2": tftypes.NewValue(tftypes.String, "attr2value"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "attr1": { - Type: types.StringType, - Required: true, - }, - "attr2": { - Type: types.StringType, - Required: true, - }, - }, - DeprecationMessage: "Use something else instead.", - }, - }, - }, - resp: ValidateSchemaResponse{ - Diagnostics: diag.Diagnostics{ - diag.NewWarningDiagnostic( - "Deprecated", - "Use something else instead.", - ), - }, - }, - }, - "warnings": { - req: ValidateSchemaRequest{ - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "attr1": tftypes.String, - "attr2": tftypes.String, - }, - }, map[string]tftypes.Value{ - "attr1": tftypes.NewValue(tftypes.String, "attr1value"), - "attr2": tftypes.NewValue(tftypes.String, "attr2value"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "attr1": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testWarningAttributeValidator{}, - }, - }, - "attr2": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testWarningAttributeValidator{}, - }, - }, - }, - }, - }, - }, - resp: ValidateSchemaResponse{ - Diagnostics: diag.Diagnostics{ - testWarningDiagnostic1, - testWarningDiagnostic2, - }, - }, - }, - "errors": { - req: ValidateSchemaRequest{ - Config: Config{ - Raw: tftypes.NewValue(tftypes.Object{ - AttributeTypes: map[string]tftypes.Type{ - "attr1": tftypes.String, - "attr2": tftypes.String, - }, - }, map[string]tftypes.Value{ - "attr1": tftypes.NewValue(tftypes.String, "attr1value"), - "attr2": tftypes.NewValue(tftypes.String, "attr2value"), - }), - Schema: Schema{ - Attributes: map[string]Attribute{ - "attr1": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - "attr2": { - Type: types.StringType, - Required: true, - Validators: []AttributeValidator{ - testErrorAttributeValidator{}, - }, - }, - }, - }, - }, - }, - resp: ValidateSchemaResponse{ - Diagnostics: diag.Diagnostics{ - testErrorDiagnostic1, - testErrorDiagnostic2, - }, - }, - }, - } - - for name, tc := range testCases { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - t.Parallel() - - var got ValidateSchemaResponse - tc.req.Config.Schema.validate(context.Background(), tc.req, &got) - - if diff := cmp.Diff(got, tc.resp); diff != "" { - t.Errorf("Unexpected response (+wanted, -got): %s", diff) - } - }) - } -} diff --git a/tfsdk/schema_validation.go b/tfsdk/schema_validation.go deleted file mode 100644 index 261c17873..000000000 --- a/tfsdk/schema_validation.go +++ /dev/null @@ -1,23 +0,0 @@ -package tfsdk - -import ( - "github.com/hashicorp/terraform-plugin-framework/diag" -) - -// ValidateSchemaRequest repesents a request for validating a Schema. -type ValidateSchemaRequest struct { - // Config contains the entire configuration of the data source, provider, or resource. - // - // This configuration may contain unknown values if a user uses - // interpolation or other functionality that would prevent Terraform - // from knowing the value at request time. - Config Config -} - -// ValidateSchemaResponse represents a response to a -// ValidateSchemaRequest. -type ValidateSchemaResponse struct { - // Diagnostics report errors or warnings related to validating the schema. - // An empty slice indicates success, with no warnings or errors generated. - Diagnostics diag.Diagnostics -} diff --git a/tfsdk/serve_opts.go b/tfsdk/serve_opts.go deleted file mode 100644 index cfdf30aca..000000000 --- a/tfsdk/serve_opts.go +++ /dev/null @@ -1,92 +0,0 @@ -package tfsdk - -import ( - "context" - "fmt" - "strings" -) - -// ServeOpts are options for serving the provider. -// -// Deprecated: Use providerserver.ServeOpts instead. This will be removed in -// the next minor version. -type ServeOpts struct { - // Address is the full address of the provider. Full address form has three - // parts separated by forward slashes (/): Hostname, namespace, and - // provider type ("name"). - // - // For example: registry.terraform.io/hashicorp/random. - Address string - - // Name is the name of the provider, in full address form. For example: - // registry.terraform.io/hashicorp/random. - // - // Deprecated: Use Address field instead. - Name string - - // Debug runs the provider in a mode acceptable for debugging and testing - // processes, such as delve, by managing the process lifecycle. Information - // needed for Terraform CLI to connect to the provider is output to stdout. - // os.Interrupt (Ctrl-c) can be used to stop the provider. - Debug bool -} - -// Get provider address, based on whether Address or Name is specified. -// -// Deprecated: Will be removed in preference of just using the Address field. -func (opts ServeOpts) address(_ context.Context) string { - if opts.Address != "" { - return opts.Address - } - - return opts.Name -} - -// Validate a given provider address. This is only used for the Address field -// to preserve backwards compatibility for the Name field. -// -// This logic is manually implemented over importing -// github.com/hashicorp/terraform-registry-address as its functionality such as -// ParseAndInferProviderSourceString and ParseRawProviderSourceString allow -// shorter address formats, which would then require post-validation anyways. -func (opts ServeOpts) validateAddress(_ context.Context) error { - addressParts := strings.Split(opts.Address, "/") - formatErr := fmt.Errorf("expected hostname/namespace/type format, got: %s", opts.Address) - - if len(addressParts) != 3 { - return formatErr - } - - if addressParts[0] == "" || addressParts[1] == "" || addressParts[2] == "" { - return formatErr - } - - return nil -} - -// Validation checks for provider defined ServeOpts. -// -// Current checks which return errors: -// -// - If both Address and Name are set -// - If neither Address nor Name is set -// - If Address is set, it is a valid full provider address -func (opts ServeOpts) validate(ctx context.Context) error { - if opts.Address == "" && opts.Name == "" { - return fmt.Errorf("either Address or Name must be provided") - } - - if opts.Address != "" && opts.Name != "" { - return fmt.Errorf("only one of Address or Name should be provided") - } - - if opts.Address != "" { - err := opts.validateAddress(ctx) - - if err != nil { - return fmt.Errorf("unable to validate Address: %w", err) - } - } - - return nil -} diff --git a/tfsdk/serve_opts_test.go b/tfsdk/serve_opts_test.go deleted file mode 100644 index b0f613f12..000000000 --- a/tfsdk/serve_opts_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package tfsdk - -import ( - "context" - "fmt" - "strings" - "testing" -) - -func TestServeOptsAddress(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - serveOpts ServeOpts - expected string - }{ - "Address": { - serveOpts: ServeOpts{ - Address: "registry.terraform.io/hashicorp/testing", - }, - expected: "registry.terraform.io/hashicorp/testing", - }, - "Address-and-Name-both": { - serveOpts: ServeOpts{ - Address: "registry.terraform.io/hashicorp/testing", - Name: "testing", - }, - expected: "registry.terraform.io/hashicorp/testing", - }, - "Name": { - serveOpts: ServeOpts{ - Name: "testing", - }, - expected: "testing", - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - got := testCase.serveOpts.address(context.Background()) - - if got != testCase.expected { - t.Fatalf("expected %q, got: %s", testCase.expected, got) - } - }) - } -} - -func TestServeOptsValidate(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - serveOpts ServeOpts - expectedError error - }{ - "Address": { - serveOpts: ServeOpts{ - Address: "registry.terraform.io/hashicorp/testing", - }, - }, - "Address-and-Name-both": { - serveOpts: ServeOpts{ - Address: "registry.terraform.io/hashicorp/testing", - Name: "testing", - }, - expectedError: fmt.Errorf("only one of Address or Name should be provided"), - }, - "Address-and-Name-missing": { - serveOpts: ServeOpts{}, - expectedError: fmt.Errorf("either Address or Name must be provided"), - }, - "Address-invalid-type-only": { - serveOpts: ServeOpts{ - Address: "testing", - }, - expectedError: fmt.Errorf("unable to validate Address: expected hostname/namespace/type format, got: testing"), - }, - "Address-invalid-missing-hostname": { - serveOpts: ServeOpts{ - Address: "hashicorp/testing", - }, - expectedError: fmt.Errorf("unable to validate Address: expected hostname/namespace/type format, got: hashicorp/testing"), - }, - "Name": { - serveOpts: ServeOpts{ - Name: "testing", - }, - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - err := testCase.serveOpts.validate(context.Background()) - - if err != nil { - if testCase.expectedError == nil { - t.Fatalf("expected no error, got: %s", err) - } - - if !strings.Contains(err.Error(), testCase.expectedError.Error()) { - t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) - } - } - - if err == nil && testCase.expectedError != nil { - t.Fatalf("got no error, expected: %s", testCase.expectedError) - } - }) - } -} - -func TestServeOptsValidateAddress(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - serveOpts ServeOpts - expectedError error - }{ - "valid": { - serveOpts: ServeOpts{ - Address: "registry.terraform.io/hashicorp/testing", - }, - }, - "invalid-type-only": { - serveOpts: ServeOpts{ - Address: "testing", - }, - expectedError: fmt.Errorf("expected hostname/namespace/type format, got: testing"), - }, - "invalid-missing-hostname": { - serveOpts: ServeOpts{ - Address: "hashicorp/testing", - }, - expectedError: fmt.Errorf("expected hostname/namespace/type format, got: hashicorp/testing"), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - err := testCase.serveOpts.validateAddress(context.Background()) - - if err != nil { - if testCase.expectedError == nil { - t.Fatalf("expected no error, got: %s", err) - } - - if !strings.Contains(err.Error(), testCase.expectedError.Error()) { - t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) - } - } - - if err == nil && testCase.expectedError != nil { - t.Fatalf("got no error, expected: %s", testCase.expectedError) - } - }) - } -} From 3cc81d4e518d1f81b2bba4bb9bd3215275de1a38 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Fri, 29 Apr 2022 16:20:03 -0400 Subject: [PATCH 2/3] Update CHANGELOG for #310 --- .changelog/{pending.txt => 310.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/{pending.txt => 310.txt} (100%) diff --git a/.changelog/pending.txt b/.changelog/310.txt similarity index 100% rename from .changelog/pending.txt rename to .changelog/310.txt From 0ae9a76dc7c21846818902634b4f38035413b221 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Fri, 29 Apr 2022 16:32:29 -0400 Subject: [PATCH 3/3] internal: Disable staticcheck deprecation warnings on migrated code Since the code is now external to the package that declares these as deprecated, staticcheck will raise an issue. This silences those reports, either by denoting that the support is required in the case of Block support, or denoting that the functionality will be removed in the case of ResourceImportStateNotImplemented(). --- internal/proto6server/schema_plan_modification.go | 1 + internal/proto6server/schema_validation.go | 1 + internal/proto6server/serve_import_test.go | 1 + .../serve_resource_upgrade_state_not_implemented_test.go | 1 + internal/toproto6/schema.go | 1 + 5 files changed, 5 insertions(+) diff --git a/internal/proto6server/schema_plan_modification.go b/internal/proto6server/schema_plan_modification.go index 9fab6148b..6b57ef5a8 100644 --- a/internal/proto6server/schema_plan_modification.go +++ b/internal/proto6server/schema_plan_modification.go @@ -61,6 +61,7 @@ func SchemaModifyPlan(ctx context.Context, s tfsdk.Schema, req ModifySchemaPlanR AttributeModifyPlan(ctx, attr, attrReq, resp) } + //nolint:staticcheck // Block support is required within the framework. for name, block := range s.Blocks { blockReq := tfsdk.ModifyAttributePlanRequest{ AttributePath: tftypes.NewAttributePath().WithAttributeName(name), diff --git a/internal/proto6server/schema_validation.go b/internal/proto6server/schema_validation.go index d6e72617e..371285355 100644 --- a/internal/proto6server/schema_validation.go +++ b/internal/proto6server/schema_validation.go @@ -48,6 +48,7 @@ func SchemaValidate(ctx context.Context, s tfsdk.Schema, req ValidateSchemaReque resp.Diagnostics = attributeResp.Diagnostics } + //nolint:staticcheck // Block support is required within the framework. for name, block := range s.Blocks { attributeReq := tfsdk.ValidateAttributeRequest{ AttributePath: tftypes.NewAttributePath().WithAttributeName(name), diff --git a/internal/proto6server/serve_import_test.go b/internal/proto6server/serve_import_test.go index 046ccf7e1..60d2bf6fe 100644 --- a/internal/proto6server/serve_import_test.go +++ b/internal/proto6server/serve_import_test.go @@ -103,6 +103,7 @@ func TestServerImportResourceState(t *testing.T) { }, impl: func(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + //nolint:staticcheck // This will be removed before the next minor release. tfsdk.ResourceImportStateNotImplemented(ctx, "", resp) }, diff --git a/internal/proto6server/serve_resource_upgrade_state_not_implemented_test.go b/internal/proto6server/serve_resource_upgrade_state_not_implemented_test.go index 6331ea648..f845ebe6d 100644 --- a/internal/proto6server/serve_resource_upgrade_state_not_implemented_test.go +++ b/internal/proto6server/serve_resource_upgrade_state_not_implemented_test.go @@ -87,5 +87,6 @@ func (r testServeResourceUpgradeStateNotImplemented) Delete(ctx context.Context, // Intentionally blank. Not expected to be called during testing. } func (r testServeResourceUpgradeStateNotImplemented) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + //nolint:staticcheck // This will be removed before the next minor release. tfsdk.ResourceImportStateNotImplemented(ctx, "intentionally not implemented", resp) } diff --git a/internal/toproto6/schema.go b/internal/toproto6/schema.go index 626ec189f..b9cfa2557 100644 --- a/internal/toproto6/schema.go +++ b/internal/toproto6/schema.go @@ -28,6 +28,7 @@ func Schema(ctx context.Context, s tfsdk.Schema) (*tfprotov6.Schema, error) { attrs = append(attrs, a) } + //nolint:staticcheck // Block support is required within the framework. for name, block := range s.Blocks { proto6, err := Block(ctx, name, tftypes.NewAttributePath().WithAttributeName(name), block)