diff --git a/.changelog/310.txt b/.changelog/310.txt new file mode 100644 index 000000000..61ac7c348 --- /dev/null +++ b/.changelog/310.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..6b57ef5a8 --- /dev/null +++ b/internal/proto6server/schema_plan_modification.go @@ -0,0 +1,76 @@ +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) + } + + //nolint:staticcheck // Block support is required within the framework. + 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..371285355 --- /dev/null +++ b/internal/proto6server/schema_validation.go @@ -0,0 +1,72 @@ +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 + } + + //nolint:staticcheck // Block support is required within the framework. + 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 84% rename from tfsdk/serve_import_test.go rename to internal/proto6server/serve_import_test.go index 78e91f40b..60d2bf6fe 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,9 @@ 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) { + //nolint:staticcheck // This will be removed before the next minor release. + tfsdk.ResourceImportStateNotImplemented(ctx, "", resp) }, resp: &tfprotov6.ImportResourceStateResponse{ @@ -121,7 +123,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 +144,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 70% 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..f845ebe6d 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,19 @@ 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) { + //nolint:staticcheck // This will be removed before the next minor release. + 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..b9cfa2557 --- /dev/null +++ b/internal/toproto6/schema.go @@ -0,0 +1,85 @@ +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) + } + + //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) + + 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) - } - }) - } -}