From 7db9b0a758726de1f343e2da13f179932b5a758a Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 16 Dec 2022 11:02:32 -0500 Subject: [PATCH] refactor(fw): terraform-plugin-framework v1.0.0 updates --- internal/acctest/framework.go | 10 +- internal/framework/planmodifiers/README.md | 3 - .../framework/planmodifiers/default_value.go | 43 -- .../planmodifiers/default_value_test.go | 281 -------------- .../framework/stringplanmodifier/README.md | 3 + .../stringplanmodifier/default_value.go | 43 ++ .../stringplanmodifier/default_value_test.go | 83 ++++ internal/framework/types/arn.go | 43 +- internal/framework/types/duration.go | 28 ++ internal/provider/fwprovider/provider.go | 366 ++++++++---------- internal/service/ec2/filters.go | 23 +- internal/tags/framework.go | 18 +- 12 files changed, 367 insertions(+), 577 deletions(-) delete mode 100644 internal/framework/planmodifiers/README.md delete mode 100644 internal/framework/planmodifiers/default_value.go delete mode 100644 internal/framework/planmodifiers/default_value_test.go create mode 100644 internal/framework/stringplanmodifier/README.md create mode 100644 internal/framework/stringplanmodifier/default_value.go create mode 100644 internal/framework/stringplanmodifier/default_value_test.go diff --git a/internal/acctest/framework.go b/internal/acctest/framework.go index d7e2ebf9e8ec..f11d92b2d873 100644 --- a/internal/acctest/framework.go +++ b/internal/acctest/framework.go @@ -2,7 +2,6 @@ package acctest import ( "context" - "errors" "fmt" "log" "strings" @@ -31,14 +30,7 @@ func DeleteFrameworkResource(factory func(context.Context) (fwresource.ResourceW resource.Configure(ctx, fwresource.ConfigureRequest{ProviderData: meta}, &fwresource.ConfigureResponse{}) schemaResp := fwresource.SchemaResponse{} - if v, ok := resource.(fwresource.ResourceWithSchema); ok { - v.Schema(ctx, fwresource.SchemaRequest{}, &schemaResp) - if schemaResp.Diagnostics.HasError() { - return fwdiag.DiagnosticsError(schemaResp.Diagnostics) - } - } else { - return errors.New("resource does not implement Schema method") - } + resource.Schema(ctx, fwresource.SchemaRequest{}, &schemaResp) // Construct a simple Framework State that contains just top-level attributes. state := tfsdk.State{ diff --git a/internal/framework/planmodifiers/README.md b/internal/framework/planmodifiers/README.md deleted file mode 100644 index a520c6ccd109..000000000000 --- a/internal/framework/planmodifiers/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Terraform Plugin Framework Plan Modifiers - -This package contains Terraform Plugin Framework [plan modifiers](https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification). diff --git a/internal/framework/planmodifiers/default_value.go b/internal/framework/planmodifiers/default_value.go deleted file mode 100644 index 265de1102439..000000000000 --- a/internal/framework/planmodifiers/default_value.go +++ /dev/null @@ -1,43 +0,0 @@ -package planmodifiers - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -type defaultValue struct { - value attr.Value -} - -// DefaultValue return an AttributePlanModifier that sets the specified value if the planned value is Null. -func DefaultValue(value attr.Value) tfsdk.AttributePlanModifier { - return defaultValue{ - value: value, - } -} - -func DefaultStringValue(value string) tfsdk.AttributePlanModifier { - return DefaultValue(types.StringValue(value)) -} - -func (m defaultValue) Description(context.Context) string { - return fmt.Sprintf("If value is not configured, defaults to %s", m.value) -} - -func (m defaultValue) MarkdownDescription(ctx context.Context) string { - return m.Description(ctx) -} - -func (m defaultValue) Modify(ctx context.Context, request tfsdk.ModifyAttributePlanRequest, response *tfsdk.ModifyAttributePlanResponse) { - if v, err := request.AttributePlan.ToTerraformValue(ctx); err != nil { - response.Diagnostics.AddAttributeError(request.AttributePath, "getting attribute value", err.Error()) - - return - } else if v.IsNull() { - response.AttributePlan = m.value - } -} diff --git a/internal/framework/planmodifiers/default_value_test.go b/internal/framework/planmodifiers/default_value_test.go deleted file mode 100644 index 56030d302e1c..000000000000 --- a/internal/framework/planmodifiers/default_value_test.go +++ /dev/null @@ -1,281 +0,0 @@ -package planmodifiers - -import ( - "context" - "math/big" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -func TestDefaultValue(t *testing.T) { - t.Parallel() - - type testCase struct { - plannedValue attr.Value - currentValue attr.Value - defaultValue attr.Value - expectedValue attr.Value - expectError bool - } - tests := map[string]testCase{ - "non-default non-Null string": { - plannedValue: types.StringValue("gamma"), - currentValue: types.StringValue("beta"), - defaultValue: types.StringValue("alpha"), - expectedValue: types.StringValue("gamma"), - }, - "non-default non-Null string, current Null": { - plannedValue: types.StringValue("gamma"), - currentValue: types.StringNull(), - defaultValue: types.StringValue("alpha"), - expectedValue: types.StringValue("gamma"), - }, - "non-default Null string, current Null": { - plannedValue: types.StringNull(), - currentValue: types.StringValue("beta"), - defaultValue: types.StringValue("alpha"), - expectedValue: types.StringValue("alpha"), - }, - "default string": { - plannedValue: types.StringNull(), - currentValue: types.StringValue("alpha"), - defaultValue: types.StringValue("alpha"), - expectedValue: types.StringValue("alpha"), - }, - "default string on create": { - plannedValue: types.StringNull(), - currentValue: types.StringNull(), - defaultValue: types.StringValue("alpha"), - expectedValue: types.StringValue("alpha"), - }, - "non-default non-Null number": { - plannedValue: types.NumberValue(big.NewFloat(30)), - currentValue: types.NumberValue(big.NewFloat(10)), - defaultValue: types.NumberValue(big.NewFloat(-10)), - expectedValue: types.NumberValue(big.NewFloat(30)), - }, - "non-default non-Null number, current Null": { - plannedValue: types.NumberValue(big.NewFloat(30)), - currentValue: types.NumberNull(), - defaultValue: types.NumberValue(big.NewFloat(-10)), - expectedValue: types.NumberValue(big.NewFloat(30)), - }, - "non-default Null number, current Null": { - plannedValue: types.NumberNull(), - currentValue: types.NumberValue(big.NewFloat(10)), - defaultValue: types.NumberValue(big.NewFloat(-10)), - expectedValue: types.NumberValue(big.NewFloat(-10)), - }, - "default number": { - plannedValue: types.NumberNull(), - currentValue: types.NumberValue(big.NewFloat(-10)), - defaultValue: types.NumberValue(big.NewFloat(-10)), - expectedValue: types.NumberValue(big.NewFloat(-10)), - }, - "non-default string list": { - plannedValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("POST"), - }), - currentValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("PUT"), - }), - defaultValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - expectedValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("POST"), - }), - }, - "non-default string list, current out of order": { - plannedValue: types.ListNull(types.StringType), - currentValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("HEAD"), - types.StringValue("GET"), - }), - defaultValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - expectedValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - }, - "default string list": { - plannedValue: types.ListNull(types.StringType), - currentValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - defaultValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - expectedValue: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - }, - "non-default string set": { - plannedValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("POST"), - }), - currentValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("PUT"), - }), - defaultValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - expectedValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("POST"), - }), - }, - "default string set, current out of order": { - plannedValue: types.SetNull(types.StringType), - currentValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("HEAD"), - types.StringValue("GET"), - }), - defaultValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - expectedValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("HEAD"), - types.StringValue("GET"), - }), - }, - "default string set": { - plannedValue: types.SetNull(types.StringType), - currentValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - defaultValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - expectedValue: types.SetValueMust(types.StringType, []attr.Value{ - types.StringValue("GET"), - types.StringValue("HEAD"), - }), - }, - "non-default object": { - plannedValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("gamma"), - }, - ), - currentValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("beta"), - }, - ), - defaultValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("alpha"), - }, - ), - expectedValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("gamma"), - }, - ), - }, - "non-default object, different value": { - plannedValue: types.ObjectNull(map[string]attr.Type{ - "value": types.StringType, - }), - currentValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("beta"), - }, - ), - defaultValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("alpha"), - }, - ), - expectedValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("alpha"), - }, - ), - }, - "default object": { - plannedValue: types.ObjectNull(map[string]attr.Type{ - "value": types.StringType, - }), - currentValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("alpha"), - }, - ), - defaultValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("alpha"), - }, - ), - expectedValue: types.ObjectValueMust(map[string]attr.Type{ - "value": types.StringType, - }, - map[string]attr.Value{ - "value": types.StringValue("alpha"), - }, - ), - }, - } - - for name, test := range tests { - name, test := name, test - t.Run(name, func(t *testing.T) { - ctx := context.Background() - request := tfsdk.ModifyAttributePlanRequest{ - AttributePath: path.Root("test"), - AttributePlan: test.plannedValue, - AttributeState: test.currentValue, - } - response := tfsdk.ModifyAttributePlanResponse{ - AttributePlan: request.AttributePlan, - } - DefaultValue(test.defaultValue).Modify(ctx, request, &response) - - if !response.Diagnostics.HasError() && test.expectError { - t.Fatal("expected error, got no error") - } - - if response.Diagnostics.HasError() && !test.expectError { - t.Fatalf("got unexpected error: %s", response.Diagnostics) - } - - if diff := cmp.Diff(response.AttributePlan, test.expectedValue); diff != "" { - t.Errorf("unexpected diff (+wanted, -got): %s", diff) - } - }) - } -} diff --git a/internal/framework/stringplanmodifier/README.md b/internal/framework/stringplanmodifier/README.md new file mode 100644 index 000000000000..7f6bf3c02094 --- /dev/null +++ b/internal/framework/stringplanmodifier/README.md @@ -0,0 +1,3 @@ +# Terraform Plugin Framework String Plan Modifiers + +This package contains Terraform Plugin Framework [string plan modifiers](https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification). diff --git a/internal/framework/stringplanmodifier/default_value.go b/internal/framework/stringplanmodifier/default_value.go new file mode 100644 index 000000000000..90ff6d5f426e --- /dev/null +++ b/internal/framework/stringplanmodifier/default_value.go @@ -0,0 +1,43 @@ +package stringplanmodifier + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type stringDefaultValue struct { + DefaultValue types.String +} + +// StringDefaultValue return a string plan modifier that sets the specified value if the planned value is Null. +func StringDefaultValue(s types.String) planmodifier.String { + return stringDefaultValue{ + DefaultValue: s, + } +} + +func (m stringDefaultValue) Description(context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to %s", m.DefaultValue) +} + +func (m stringDefaultValue) MarkdownDescription(ctx context.Context) string { + return m.Description(ctx) +} + +func (m stringDefaultValue) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // If the attribute configuration is not null, we are done here + if !req.ConfigValue.IsNull() { + return + } + + // If the attribute plan is "known" and "not null", then a previous plan modifier in the sequence + // has already been applied, and we don't want to interfere. + if !req.PlanValue.IsUnknown() && !req.PlanValue.IsNull() { + return + } + + resp.PlanValue = m.DefaultValue +} diff --git a/internal/framework/stringplanmodifier/default_value_test.go b/internal/framework/stringplanmodifier/default_value_test.go new file mode 100644 index 000000000000..a3e3f71fb022 --- /dev/null +++ b/internal/framework/stringplanmodifier/default_value_test.go @@ -0,0 +1,83 @@ +package stringplanmodifier + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestDefaultValue(t *testing.T) { + t.Parallel() + + type testCase struct { + plannedValue types.String + currentValue types.String + defaultValue types.String + expectedValue types.String + expectError bool + } + tests := map[string]testCase{ + "non-default non-Null string": { + plannedValue: types.StringValue("gamma"), + currentValue: types.StringValue("beta"), + defaultValue: types.StringValue("alpha"), + expectedValue: types.StringValue("gamma"), + }, + "non-default non-Null string, current Null": { + plannedValue: types.StringValue("gamma"), + currentValue: types.StringNull(), + defaultValue: types.StringValue("alpha"), + expectedValue: types.StringValue("gamma"), + }, + "non-default Null string, current Null": { + plannedValue: types.StringNull(), + currentValue: types.StringValue("beta"), + defaultValue: types.StringValue("alpha"), + expectedValue: types.StringValue("alpha"), + }, + "default string": { + plannedValue: types.StringNull(), + currentValue: types.StringValue("alpha"), + defaultValue: types.StringValue("alpha"), + expectedValue: types.StringValue("alpha"), + }, + "default string on create": { + plannedValue: types.StringNull(), + currentValue: types.StringNull(), + defaultValue: types.StringValue("alpha"), + expectedValue: types.StringValue("alpha"), + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.Background() + request := planmodifier.StringRequest{ + Path: path.Root("test"), + PlanValue: test.plannedValue, + StateValue: test.currentValue, + } + response := planmodifier.StringResponse{ + PlanValue: request.PlanValue, + } + StringDefaultValue(test.defaultValue).PlanModifyString(ctx, request, &response) + + if !response.Diagnostics.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Diagnostics.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Diagnostics) + } + + if diff := cmp.Diff(response.PlanValue, test.expectedValue); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } + }) + } +} diff --git a/internal/framework/types/arn.go b/internal/framework/types/arn.go index a5eefe0f3baf..e6faef73af01 100644 --- a/internal/framework/types/arn.go +++ b/internal/framework/types/arn.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -27,6 +28,27 @@ func (t arnType) TerraformType(_ context.Context) tftypes.Type { return tftypes.String } +func (t arnType) ValueFromString(_ context.Context, st types.String) (basetypes.StringValuable, diag.Diagnostics) { + if st.IsNull() { + return ARNNull(), nil + } + if st.IsUnknown() { + return ARNUnknown(), nil + } + + var diags diag.Diagnostics + v, err := arn.Parse(st.ValueString()) + if err != nil { + diags.AddError( + "ARN ValueFromString Error", + fmt.Sprintf("String %s cannot be parsed as an ARN.", st), + ) + return nil, diags + } + + return ARNValue(v), diags +} + func (t arnType) ValueFromTerraform(_ context.Context, in tftypes.Value) (attr.Value, error) { if !in.IsKnown() { return ARNUnknown(), nil @@ -119,27 +141,6 @@ func (t arnType) Description() string { return `An Amazon Resource Name.` } -func (t arnType) ValueFromString(ctx context.Context, st types.String) (types.StringValuable, diag.Diagnostics) { - if st.IsNull() { - return ARNNull(), nil - } - if st.IsUnknown() { - return ARNUnknown(), nil - } - - var diags diag.Diagnostics - v, err := arn.Parse(st.String()) - if err != nil { - diags.AddError( - "ARN ValueFromString Error", - fmt.Sprintf("String %s cannot be parsed as an ARN.", st), - ) - return nil, diags - } - - return ARNValue(v), diags -} - func ARNNull() ARN { return ARN{ state: attr.ValueStateNull, diff --git a/internal/framework/types/duration.go b/internal/framework/types/duration.go index a38c7d7f8c89..59dd43dd9409 100644 --- a/internal/framework/types/duration.go +++ b/internal/framework/types/duration.go @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr/xattr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -26,6 +28,28 @@ func (d durationType) TerraformType(_ context.Context) tftypes.Type { return tftypes.String } +func (d durationType) ValueFromString(_ context.Context, in types.String) (basetypes.StringValuable, diag.Diagnostics) { + if in.IsUnknown() { + return DurationUnknown(), nil + } + + if in.IsNull() { + return DurationNull(), nil + } + + var diags diag.Diagnostics + v, err := time.ParseDuration(in.ValueString()) + if err != nil { + diags.AddError( + "Duration Type Validation Error", + fmt.Sprintf("Value %q cannot be parsed as a Duration.", in.ValueString()), + ) + return nil, diags + } + + return DurationValue(v), nil +} + func (d durationType) ValueFromTerraform(_ context.Context, in tftypes.Value) (attr.Value, error) { if !in.IsKnown() { return DurationUnknown(), nil @@ -152,6 +176,10 @@ func (d Duration) Type(_ context.Context) attr.Type { return DurationType } +func (d Duration) ToStringValue(ctx context.Context) (types.String, diag.Diagnostics) { + return types.StringValue(d.value.String()), nil +} + // ToTerraformValue returns the data contained in the *String as a string. If // Unknown is true, it returns a tftypes.UnknownValue. If Null is true, it // returns nil. diff --git a/internal/provider/fwprovider/provider.go b/internal/provider/fwprovider/provider.go index 63ebf6c76252..641185469930 100644 --- a/internal/provider/fwprovider/provider.go +++ b/internal/provider/fwprovider/provider.go @@ -6,11 +6,12 @@ import ( "reflect" "strings" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -30,276 +31,255 @@ type fwprovider struct { Primary interface{ Meta() interface{} } } -// GetSchema returns the schema for this provider's configuration. -func (p *fwprovider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { - var diags diag.Diagnostics +func (p *fwprovider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "aws" +} +// Schema returns the schema for this provider's configuration. +func (p *fwprovider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { // This schema must match exactly the Terraform Protocol v5 (Terraform Plugin SDK v2) provider's schema. - schema := tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "access_key": { - Type: types.StringType, + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "access_key": schema.StringAttribute{ Optional: true, Description: "The access key for API operations. You can retrieve this\nfrom the 'Security & Credentials' section of the AWS console.", }, - "allowed_account_ids": { - Type: types.SetType{ElemType: types.StringType}, - Optional: true, + "allowed_account_ids": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, }, - "custom_ca_bundle": { - Type: types.StringType, + "custom_ca_bundle": schema.StringAttribute{ Optional: true, Description: "File containing custom root and intermediate certificates. Can also be configured using the `AWS_CA_BUNDLE` environment variable. (Setting `ca_bundle` in the shared config file is not supported.)", }, - "ec2_metadata_service_endpoint": { - Type: types.StringType, + "ec2_metadata_service_endpoint": schema.StringAttribute{ Optional: true, Description: "Address of the EC2 metadata service endpoint to use. Can also be configured using the `AWS_EC2_METADATA_SERVICE_ENDPOINT` environment variable.", }, - "ec2_metadata_service_endpoint_mode": { - Type: types.StringType, + "ec2_metadata_service_endpoint_mode": schema.StringAttribute{ Optional: true, Description: "Protocol to use with EC2 metadata service endpoint.Valid values are `IPv4` and `IPv6`. Can also be configured using the `AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE` environment variable.", }, - "forbidden_account_ids": { - Type: types.SetType{ElemType: types.StringType}, - Optional: true, + "forbidden_account_ids": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, }, - "http_proxy": { - Type: types.StringType, + "http_proxy": schema.StringAttribute{ Optional: true, Description: "The address of an HTTP proxy to use when accessing the AWS API. Can also be configured using the `HTTP_PROXY` or `HTTPS_PROXY` environment variables.", }, - "insecure": { - Type: types.BoolType, + "insecure": schema.BoolAttribute{ Optional: true, Description: "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted, default value is `false`", }, - "max_retries": { - Type: types.Int64Type, + "max_retries": schema.Int64Attribute{ Optional: true, Description: "The maximum number of times an AWS API request is\nbeing executed. If the API request still fails, an error is\nthrown.", }, - "profile": { - Type: types.StringType, + "profile": schema.StringAttribute{ Optional: true, Description: "The profile for API operations. If not set, the default profile\ncreated with `aws configure` will be used.", }, - "region": { - Type: types.StringType, + "region": schema.StringAttribute{ Optional: true, Description: "The region where AWS operations will take place. Examples\nare us-east-1, us-west-2, etc.", // lintignore:AWSAT003 }, - "s3_force_path_style": { - Type: types.BoolType, + "s3_force_path_style": schema.BoolAttribute{ Optional: true, Description: "Set this to true to enable the request to use path-style addressing,\ni.e., https://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\nuse virtual hosted bucket addressing when possible\n(https://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", DeprecationMessage: "Use s3_use_path_style instead.", }, - "s3_use_path_style": { - Type: types.BoolType, + "s3_use_path_style": schema.BoolAttribute{ Optional: true, Description: "Set this to true to enable the request to use path-style addressing,\ni.e., https://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\nuse virtual hosted bucket addressing when possible\n(https://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", }, - "secret_key": { - Type: types.StringType, + "secret_key": schema.StringAttribute{ Optional: true, Description: "The secret key for API operations. You can retrieve this\nfrom the 'Security & Credentials' section of the AWS console.", }, - "shared_config_files": { - Type: types.ListType{ElemType: types.StringType}, + "shared_config_files": schema.ListAttribute{ + ElementType: types.StringType, Optional: true, Description: "List of paths to shared config files. If not set, defaults to [~/.aws/config].", }, - "shared_credentials_file": { - Type: types.StringType, + "shared_credentials_file": schema.StringAttribute{ Optional: true, Description: "The path to the shared credentials file. If not set, defaults to ~/.aws/credentials.", DeprecationMessage: "Use shared_credentials_files instead.", }, - "shared_credentials_files": { - Type: types.ListType{ElemType: types.StringType}, + "shared_credentials_files": schema.ListAttribute{ + ElementType: types.StringType, Optional: true, Description: "List of paths to shared credentials files. If not set, defaults to [~/.aws/credentials].", }, - "skip_credentials_validation": { - Type: types.BoolType, + "skip_credentials_validation": schema.BoolAttribute{ Optional: true, Description: "Skip the credentials validation via STS API. Used for AWS API implementations that do not have STS available/implemented.", }, - "skip_get_ec2_platforms": { - Type: types.BoolType, + "skip_get_ec2_platforms": schema.BoolAttribute{ Optional: true, Description: "Skip getting the supported EC2 platforms. Used by users that don't have ec2:DescribeAccountAttributes permissions.", DeprecationMessage: `With the retirement of EC2-Classic the skip_get_ec2_platforms attribute has been deprecated and will be removed in a future version.`, }, - "skip_metadata_api_check": { - Type: types.StringType, + "skip_metadata_api_check": schema.StringAttribute{ Optional: true, Description: "Skip the AWS Metadata API check. Used for AWS API implementations that do not have a metadata api endpoint.", }, - "skip_region_validation": { - Type: types.BoolType, + "skip_region_validation": schema.BoolAttribute{ Optional: true, Description: "Skip static validation of region name. Used by users of alternative AWS-like APIs or users w/ access to regions that are not public (yet).", }, - "skip_requesting_account_id": { - Type: types.BoolType, + "skip_requesting_account_id": schema.BoolAttribute{ Optional: true, Description: "Skip requesting the account ID. Used for AWS API implementations that do not have IAM/STS API and/or metadata API.", }, - "sts_region": { - Type: types.StringType, + "sts_region": schema.StringAttribute{ Optional: true, Description: "The region where AWS STS operations will take place. Examples\nare us-east-1 and us-west-2.", // lintignore:AWSAT003 }, - "token": { - Type: types.StringType, + "token": schema.StringAttribute{ Optional: true, Description: "session token. A session token is only required if you are\nusing temporary security credentials.", }, - "use_dualstack_endpoint": { - Type: types.BoolType, + "use_dualstack_endpoint": schema.BoolAttribute{ Optional: true, Description: "Resolve an endpoint with DualStack capability", }, - "use_fips_endpoint": { - Type: types.BoolType, + "use_fips_endpoint": schema.BoolAttribute{ Optional: true, Description: "Resolve an endpoint with FIPS capability", }, }, - Blocks: map[string]tfsdk.Block{ - "assume_role": { - Attributes: map[string]tfsdk.Attribute{ - "duration": { - Type: fwtypes.DurationType, - Optional: true, - Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", - }, - "duration_seconds": { - Type: types.Int64Type, - Optional: true, - Description: "The duration, in seconds, of the role session.", - DeprecationMessage: "Use assume_role.duration instead", - }, - "external_id": { - Type: types.StringType, - Optional: true, - Description: "A unique identifier that might be required when you assume a role in another account.", - }, - "policy": { - Type: types.StringType, - Optional: true, - Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", - }, - "policy_arns": { - Type: types.SetType{ElemType: types.StringType}, - Optional: true, - Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", - }, - "role_arn": { - Type: types.StringType, - Optional: true, - Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", - }, - "session_name": { - Type: types.StringType, - Optional: true, - Description: "An identifier for the assumed role session.", - }, - "source_identity": { - Type: types.StringType, - Optional: true, - Description: "Source identity specified by the principal assuming the role.", - }, - "tags": { - Type: types.MapType{ElemType: types.StringType}, - Optional: true, - Description: "Assume role session tags.", - }, - "transitive_tag_keys": { - Type: types.SetType{ElemType: types.StringType}, - Optional: true, - Description: "Assume role session tag keys to pass to any subsequent sessions.", + Blocks: map[string]schema.Block{ + "assume_role": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "duration": schema.StringAttribute{ + CustomType: fwtypes.DurationType, + Optional: true, + Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", + }, + "duration_seconds": schema.Int64Attribute{ + Optional: true, + Description: "The duration, in seconds, of the role session.", + DeprecationMessage: "Use assume_role.duration instead", + }, + "external_id": schema.StringAttribute{ + Optional: true, + Description: "A unique identifier that might be required when you assume a role in another account.", + }, + "policy": schema.StringAttribute{ + Optional: true, + Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", + }, + "policy_arns": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", + }, + "role_arn": schema.StringAttribute{ + Optional: true, + Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", + }, + "session_name": schema.StringAttribute{ + Optional: true, + Description: "An identifier for the assumed role session.", + }, + "source_identity": schema.StringAttribute{ + Optional: true, + Description: "Source identity specified by the principal assuming the role.", + }, + "tags": schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Assume role session tags.", + }, + "transitive_tag_keys": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Assume role session tag keys to pass to any subsequent sessions.", + }, }, }, - NestingMode: tfsdk.BlockNestingModeList, - MaxItems: 1, }, - "assume_role_with_web_identity": { - Attributes: map[string]tfsdk.Attribute{ - "duration": { - Type: fwtypes.DurationType, - Optional: true, - Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", - }, - "policy": { - Type: types.StringType, - Optional: true, - Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", - }, - "policy_arns": { - Type: types.SetType{ElemType: types.StringType}, - Optional: true, - Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", - }, - "role_arn": { - Type: types.StringType, - Optional: true, - Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", - }, - "session_name": { - Type: types.StringType, - Optional: true, - Description: "An identifier for the assumed role session.", - }, - "web_identity_token": { - Type: types.StringType, - Optional: true, - }, - "web_identity_token_file": { - Type: types.StringType, - Optional: true, + "assume_role_with_web_identity": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "duration": schema.StringAttribute{ + CustomType: fwtypes.DurationType, + Optional: true, + Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", + }, + "policy": schema.StringAttribute{ + Optional: true, + Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", + }, + "policy_arns": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", + }, + "role_arn": schema.StringAttribute{ + Optional: true, + Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", + }, + "session_name": schema.StringAttribute{ + Optional: true, + Description: "An identifier for the assumed role session.", + }, + "web_identity_token": schema.StringAttribute{ + Optional: true, + }, + "web_identity_token_file": schema.StringAttribute{ + Optional: true, + }, }, }, - NestingMode: tfsdk.BlockNestingModeList, - MaxItems: 1, }, - "default_tags": { - Attributes: map[string]tfsdk.Attribute{ - "tags": { - Type: types.MapType{ElemType: types.StringType}, - Optional: true, - Description: "Resource tags to default across all resources", - }, + "default_tags": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(1), }, - NestingMode: tfsdk.BlockNestingModeList, - MaxItems: 1, Description: "Configuration block with settings to default resource tags across all resources.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "tags": schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Resource tags to default across all resources", + }, + }, + }, }, "endpoints": endpointsBlock(), - "ignore_tags": { - Attributes: map[string]tfsdk.Attribute{ - "key_prefixes": { - Type: types.SetType{ElemType: types.StringType}, - Optional: true, - Description: "Resource tag key prefixes to ignore across all resources.", - }, - "keys": { - Type: types.SetType{ElemType: types.StringType}, - Optional: true, - Description: "Resource tag keys to ignore across all resources.", - }, + "ignore_tags": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(1), }, - NestingMode: tfsdk.BlockNestingModeList, - MaxItems: 1, Description: "Configuration block with settings to ignore resource tags across all resources.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "key_prefixes": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Resource tag key prefixes to ignore across all resources.", + }, + "keys": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Resource tag keys to ignore across all resources.", + }, + }, + }, }, }, } - - return schema, diags } // Configure is called at the beginning of the provider lifecycle, when @@ -372,20 +352,20 @@ func (p *fwprovider) Resources(ctx context.Context) []func() resource.Resource { return resources } -func endpointsBlock() tfsdk.Block { - endpointsAttributes := make(map[string]tfsdk.Attribute) +func endpointsBlock() schema.SetNestedBlock { + endpointsAttributes := make(map[string]schema.Attribute) for _, serviceKey := range names.Aliases() { - endpointsAttributes[serviceKey] = tfsdk.Attribute{ - Type: types.StringType, + endpointsAttributes[serviceKey] = schema.StringAttribute{ Optional: true, Description: "Use this to override the default service endpoint URL", } } - return tfsdk.Block{ - Attributes: endpointsAttributes, - NestingMode: tfsdk.BlockNestingModeSet, + return schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: endpointsAttributes, + }, } } @@ -404,14 +384,7 @@ func (w *wrappedDataSource) Metadata(ctx context.Context, request datasource.Met } func (w *wrappedDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { - if v, ok := w.inner.(datasource.DataSourceWithSchema); ok { - v.Schema(ctx, request, response) - return - } - response.Diagnostics.AddError( - "DataSource Schema Not Implemented", - "This data source does not support get schema. Please contact the provider developer for additional information.", - ) + w.inner.Schema(ctx, request, response) } func (w *wrappedDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { @@ -442,14 +415,7 @@ func (w *wrappedResource) Metadata(ctx context.Context, request resource.Metadat } func (w *wrappedResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { - if v, ok := w.inner.(resource.ResourceWithSchema); ok { - v.Schema(ctx, request, response) - return - } - response.Diagnostics.AddError( - "Resource Schema Not Implemented", - "This resource does not support get schema. Please contact the provider developer for additional information.", - ) + w.inner.Schema(ctx, request, response) } func (w *wrappedResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { diff --git a/internal/service/ec2/filters.go b/internal/service/ec2/filters.go index b7882e7a9205..e6b2b8c5c56c 100644 --- a/internal/service/ec2/filters.go +++ b/internal/service/ec2/filters.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -107,19 +108,19 @@ func CustomFiltersSchema() *schema.Schema { } // CustomFiltersBlock is the Plugin Framework variant of CustomFiltersSchema. -func CustomFiltersBlock() tfsdk.Block { - return tfsdk.Block{ - Attributes: map[string]tfsdk.Attribute{ - "name": { - Type: types.StringType, - Required: true, - }, - "values": { - Type: types.SetType{ElemType: types.StringType}, - Required: true, +func CustomFiltersBlock() datasourceschema.Block { + return datasourceschema.SetNestedBlock{ + NestedObject: datasourceschema.NestedBlockObject{ + Attributes: map[string]datasourceschema.Attribute{ + "name": datasourceschema.StringAttribute{ + Required: true, + }, + "values": datasourceschema.SetAttribute{ + ElementType: types.StringType, + Required: true, + }, }, }, - NestingMode: tfsdk.BlockNestingModeSet, } } diff --git a/internal/tags/framework.go b/internal/tags/framework.go index 1e525beb8063..297b69347ea4 100644 --- a/internal/tags/framework.go +++ b/internal/tags/framework.go @@ -1,23 +1,23 @@ package tags import ( - "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" ) // Terraform Plugin Framework variants of tags schemas. -func TagsAttribute() tfsdk.Attribute { - return tfsdk.Attribute{ - Type: types.MapType{ElemType: types.StringType}, - Optional: true, +func TagsAttribute() schema.Attribute { + return schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, } } -func TagsAttributeComputedOnly() tfsdk.Attribute { - return tfsdk.Attribute{ - Type: types.MapType{ElemType: types.StringType}, - Computed: true, +func TagsAttributeComputedOnly() schema.Attribute { + return schema.MapAttribute{ + ElementType: types.StringType, + Computed: true, } }