diff --git a/.changelog/40076.txt b/.changelog/40076.txt new file mode 100644 index 00000000000..20a35a25098 --- /dev/null +++ b/.changelog/40076.txt @@ -0,0 +1,18 @@ +```release-note:bug +resource/aws_iam_group_policy_attachments_exclusive: Add validation to prevent null values in `policy_arns` +``` +```release-note:bug +resource/aws_iam_role_policy_attachments_exclusive: Add validation to prevent null values in `policy_arns` +``` +```release-note:bug +resource/aws_iam_user_policy_attachments_exclusive: Add validation to prevent null values in `policy_arns` +``` +```release-note:bug +resource/aws_iam_group_policies_exclusive: Add validation to prevent null values in `policy_names` +``` +```release-note:bug +resource/aws_iam_role_policies_exclusive: Add validation to prevent null values in `policy_names` +``` +```release-note:bug +resource/aws_iam_user_policies_exclusive: Add validation to prevent null values in `policy_names` +``` diff --git a/internal/framework/validators/non_null_values.go b/internal/framework/validators/non_null_values.go new file mode 100644 index 00000000000..679794719ee --- /dev/null +++ b/internal/framework/validators/non_null_values.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Set = nonNullValuesValidator{} +var _ function.SetParameterValidator = nonNullValuesValidator{} + +type nonNullValuesValidator struct{} + +func (v nonNullValuesValidator) Description(_ context.Context) string { + return "null values are not permitted" +} + +func (v nonNullValuesValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v nonNullValuesValidator) ValidateSet(_ context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + elements := req.ConfigValue.Elements() + + for _, e := range elements { + // Only evaluate known values for null + if e.IsUnknown() { + continue + } + + if e.IsNull() { + resp.Diagnostics.AddAttributeError( + req.Path, + "Null Set Value", + "This attribute contains a null value.", + ) + } + } +} + +func (v nonNullValuesValidator) ValidateParameterSet(ctx context.Context, req function.SetParameterValidatorRequest, resp *function.SetParameterValidatorResponse) { + if req.Value.IsNull() || req.Value.IsUnknown() { + return + } + + elements := req.Value.Elements() + + for _, e := range elements { + // Only evaluate known values for null + if e.IsUnknown() { + continue + } + + if e.IsNull() { + resp.Error = function.ConcatFuncErrors( + resp.Error, + function.NewArgumentFuncError( + req.ArgumentPosition, + "Null Set Value: This attribute contains a null value.", + ), + ) + } + } +} + +// NonNullValues returns a validator which ensures that any configured set +// only contains non-null values. +func NonNullValues() nonNullValuesValidator { + return nonNullValuesValidator{} +} diff --git a/internal/framework/validators/non_null_values_test.go b/internal/framework/validators/non_null_values_test.go new file mode 100644 index 00000000000..36d2f8df48c --- /dev/null +++ b/internal/framework/validators/non_null_values_test.go @@ -0,0 +1,98 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validators + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestNonNullValuesValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val types.Set + expectError bool + } + tests := map[string]testCase{ + "Set unknown": { + val: types.SetUnknown( + types.StringType, + ), + expectError: false, + }, + "Set null": { + val: types.SetNull( + types.StringType, + ), + expectError: false, + }, + "Null values": { + val: types.SetValueMust( + types.StringType, + []attr.Value{ + types.StringValue("first"), + types.StringNull(), + }, + ), + expectError: true, + }, + "Non null values": { + val: types.SetValueMust( + types.StringType, + []attr.Value{ + types.StringValue("first"), + types.StringValue("second"), + }, + ), + expectError: false, + }, + } + + for name, test := range tests { + t.Run(fmt.Sprintf("ValidateSet - %s", name), func(t *testing.T) { + t.Parallel() + request := validator.SetRequest{ + Path: path.Root("test"), + PathExpression: path.MatchRoot("test"), + ConfigValue: test.val, + } + response := validator.SetResponse{} + NonNullValues().ValidateSet(context.TODO(), 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) + } + }) + + t.Run(fmt.Sprintf("ValidateParameterSet - %s", name), func(t *testing.T) { + t.Parallel() + request := function.SetParameterValidatorRequest{ + ArgumentPosition: 0, + Value: test.val, + } + response := function.SetParameterValidatorResponse{} + NonNullValues().ValidateParameterSet(context.TODO(), request, &response) + + if response.Error == nil && test.expectError { + t.Fatal("expected error, got no error") + } + + if response.Error != nil && !test.expectError { + t.Fatalf("got unexpected error: %s", response.Error) + } + }) + } +} diff --git a/internal/service/iam/group_policies_exclusive.go b/internal/service/iam/group_policies_exclusive.go index 0db5cd25250..d737124c2f6 100644 --- a/internal/service/iam/group_policies_exclusive.go +++ b/internal/service/iam/group_policies_exclusive.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -21,6 +22,7 @@ import ( intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -55,6 +57,9 @@ func (r *resourceGroupPoliciesExclusive) Schema(ctx context.Context, req resourc "policy_names": schema.SetAttribute{ ElementType: types.StringType, Required: true, + Validators: []validator.Set{ + validators.NonNullValues(), + }, }, }, } diff --git a/internal/service/iam/group_policy_attachments_exclusive.go b/internal/service/iam/group_policy_attachments_exclusive.go index 61e72969c00..876c700f996 100644 --- a/internal/service/iam/group_policy_attachments_exclusive.go +++ b/internal/service/iam/group_policy_attachments_exclusive.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -21,6 +22,7 @@ import ( intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -55,6 +57,9 @@ func (r *resourceGroupPolicyAttachmentsExclusive) Schema(ctx context.Context, re "policy_arns": schema.SetAttribute{ ElementType: types.StringType, Required: true, + Validators: []validator.Set{ + validators.NonNullValues(), + }, }, }, } diff --git a/internal/service/iam/role_policies_exclusive.go b/internal/service/iam/role_policies_exclusive.go index 120b07d0848..e214e925cc0 100644 --- a/internal/service/iam/role_policies_exclusive.go +++ b/internal/service/iam/role_policies_exclusive.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -21,6 +22,7 @@ import ( intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -55,6 +57,9 @@ func (r *resourceRolePoliciesExclusive) Schema(ctx context.Context, req resource "policy_names": schema.SetAttribute{ ElementType: types.StringType, Required: true, + Validators: []validator.Set{ + validators.NonNullValues(), + }, }, }, } diff --git a/internal/service/iam/role_policy_attachments_exclusive.go b/internal/service/iam/role_policy_attachments_exclusive.go index 9af4ee0979c..c7c28108b92 100644 --- a/internal/service/iam/role_policy_attachments_exclusive.go +++ b/internal/service/iam/role_policy_attachments_exclusive.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -21,6 +22,7 @@ import ( intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -55,6 +57,9 @@ func (r *resourceRolePolicyAttachmentsExclusive) Schema(ctx context.Context, req "policy_arns": schema.SetAttribute{ ElementType: types.StringType, Required: true, + Validators: []validator.Set{ + validators.NonNullValues(), + }, }, }, } diff --git a/internal/service/iam/user_policies_exclusive.go b/internal/service/iam/user_policies_exclusive.go index b82147eb21e..901bb29474f 100644 --- a/internal/service/iam/user_policies_exclusive.go +++ b/internal/service/iam/user_policies_exclusive.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -21,6 +22,7 @@ import ( intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -55,6 +57,9 @@ func (r *resourceUserPoliciesExclusive) Schema(ctx context.Context, req resource "policy_names": schema.SetAttribute{ ElementType: types.StringType, Required: true, + Validators: []validator.Set{ + validators.NonNullValues(), + }, }, }, } diff --git a/internal/service/iam/user_policy_attachments_exclusive.go b/internal/service/iam/user_policy_attachments_exclusive.go index 3c2f3dd9393..db960ec61ef 100644 --- a/internal/service/iam/user_policy_attachments_exclusive.go +++ b/internal/service/iam/user_policy_attachments_exclusive.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -21,6 +22,7 @@ import ( intflex "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -55,6 +57,9 @@ func (r *resourceUserPolicyAttachmentsExclusive) Schema(ctx context.Context, req "policy_arns": schema.SetAttribute{ ElementType: types.StringType, Required: true, + Validators: []validator.Set{ + validators.NonNullValues(), + }, }, }, }