diff --git a/.changelog/44739.txt b/.changelog/44739.txt new file mode 100644 index 000000000000..6bfff4d34e12 --- /dev/null +++ b/.changelog/44739.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_iam_role_policy_attachment: Adds List support +``` diff --git a/internal/acctest/querycheck/expect_identity_func.go b/internal/acctest/querycheck/expect_identity_func.go new file mode 100644 index 000000000000..4857966d6d82 --- /dev/null +++ b/internal/acctest/querycheck/expect_identity_func.go @@ -0,0 +1,94 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "errors" + "fmt" + "slices" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +var _ querycheck.QueryResultCheck = expectIdentityFunc{} + +type expectIdentityFunc struct { + listResourceAddress string + identityFunc func() map[string]knownvalue.Check +} + +// Adapted (with updates) from github.com/hashicorp/terraform-plugin-testing/statecheck.ExpectIdentity +func (e expectIdentityFunc) CheckQuery(_ context.Context, req querycheck.CheckQueryRequest, resp *querycheck.CheckQueryResponse) { + checks := e.identityFunc() + + for _, res := range req.Query { + var errCollection []error + + if e.listResourceAddress != strings.TrimPrefix(res.Address, "list.") { + continue + } + + if len(res.Identity) != len(checks) { + var deltaMsg string + if len(res.Identity) > len(checks) { + deltaMsg = statecheck.CreateDeltaString(res.Identity, checks, "actual identity has extra attribute(s): ") + } else { + deltaMsg = statecheck.CreateDeltaString(checks, res.Identity, "actual identity is missing attribute(s): ") + } + + resp.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.listResourceAddress, len(checks), len(res.Identity), deltaMsg) + return + } + + var keys []string + + for k := range checks { + keys = append(keys, k) + } + + slices.Sort(keys) + + for _, k := range keys { + actualIdentityVal, ok := res.Identity[k] + + if !ok { + resp.Error = fmt.Errorf("%s - missing attribute %q in actual identity object", e.listResourceAddress, k) + return + } + + if err := checks[k].CheckValue(actualIdentityVal); err != nil { + errCollection = append(errCollection, fmt.Errorf("%s - %q identity attribute: %w", e.listResourceAddress, k, err)) + } + } + + if errCollection == nil { + return + } + } + + var errCollection []error + errCollection = append(errCollection, fmt.Errorf("an identity with the following attributes was not found")) + + // wrap errors for each check + for attr, check := range checks { + errCollection = append(errCollection, fmt.Errorf("attribute %q: %s", attr, check)) + } + errCollection = append(errCollection, fmt.Errorf("address: %s\n", e.listResourceAddress)) + resp.Error = errors.Join(errCollection...) +} + +// ExpectIdentityFunc returns a query check that asserts that the given list resource contains a resource with an identity matching +// the identity checks returned by the identityFunc. +// +// This query check can only be used with managed resources that support resource identity and query. Query is only supported in Terraform v1.14+ +func ExpectIdentityFunc(resourceAddress string, identityFunc func() map[string]knownvalue.Check) querycheck.QueryResultCheck { + return expectIdentityFunc{ + listResourceAddress: resourceAddress, + identityFunc: identityFunc, + } +} diff --git a/internal/acctest/statecheck/identity.go b/internal/acctest/statecheck/identity.go new file mode 100644 index 000000000000..ec946d2a5909 --- /dev/null +++ b/internal/acctest/statecheck/identity.go @@ -0,0 +1,71 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + "maps" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +type identity struct { + resourceAddress string + values map[string]any +} + +func Identity() identity { + return identity{} +} + +// GetIdentity sets the resource address to check and stores the identity values. +// Calls to GetIdentity occur before any TestStep is run. +func (v *identity) GetIdentity(resourceAddress string) statecheck.StateCheck { + v.resourceAddress = resourceAddress + + return newIdentityStateChecker(v) +} + +type identityStateChecker struct { + base Base + identity *identity +} + +func newIdentityStateChecker(identity *identity) identityStateChecker { + return identityStateChecker{ + base: NewBase(identity.resourceAddress), + identity: identity, + } +} + +func (vc identityStateChecker) CheckState(ctx context.Context, request statecheck.CheckStateRequest, response *statecheck.CheckStateResponse) { + resource, ok := vc.base.ResourceFromState(request, response) + if !ok { + return + } + + if resource.IdentitySchemaVersion == nil || len(resource.IdentityValues) == 0 { + response.Error = fmt.Errorf("%s - Identity not found in state. Either the resource does not support identity or the Terraform version running the test does not support identity. (must be v1.12+)", vc.base.resourceAddress) + + return + } + + vc.identity.values = maps.Collect(maps.All(resource.IdentityValues)) +} + +// Checks returns a function that provides the identity values as knownvalue.Checks. +// Calls to Checks occur before any TestStep is run. +func (v *identity) Checks() func() map[string]knownvalue.Check { + return func() map[string]knownvalue.Check { + checks := make(map[string]knownvalue.Check, len(v.values)) + + for k, val := range v.values { + checks[k] = knownvalue.StringExact(val.(string)) + } + + return checks + } +} diff --git a/internal/service/iam/role.go b/internal/service/iam/role.go index 4f625fd3f235..9a7e8d44919f 100644 --- a/internal/service/iam/role.go +++ b/internal/service/iam/role.go @@ -38,6 +38,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/logging" "github.com/hashicorp/terraform-provider-aws/internal/provider/sdkv2/importer" "github.com/hashicorp/terraform-provider-aws/internal/retry" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" @@ -731,6 +732,32 @@ func listRoles(ctx context.Context, conn *iam.Client, input *iam.ListRolesInput) } } +func listNonServiceLinkedRoles(ctx context.Context, conn *iam.Client, input *iam.ListRolesInput) iter.Seq2[awstypes.Role, error] { + return func(yield func(awstypes.Role, error) bool) { + roles := listRoles(ctx, conn, input) + for role, err := range roles { + if err != nil { + yield(awstypes.Role{}, err) + return + } + + // Exclude Service-Linked Roles + if strings.HasPrefix(aws.ToString(role.Path), "/aws-service-role/") { + tflog.Debug(ctx, "Skipping resource", map[string]any{ + "skip_reason": "Service-Linked Role", + logging.ResourceAttributeKey("role_name"): aws.ToString(role.RoleName), + logging.ResourceAttributeKey(names.AttrPath): aws.ToString(role.Path), + }) + continue + } + + if !yield(role, nil) { + return + } + } + } +} + func resourceRoleFlatten(ctx context.Context, role *awstypes.Role, d *schema.ResourceData) diag.Diagnostics { var diags diag.Diagnostics @@ -1132,29 +1159,25 @@ func (l *roleListResource) List(ctx context.Context, request list.ListRequest, s return } - stream.Results = func(yield func(list.ListResult) bool) { - result := request.NewListResult(ctx) + tflog.Info(ctx, "Listing resources") - for output, err := range listRoles(ctx, conn, &input) { + stream.Results = func(yield func(list.ListResult) bool) { + for role, err := range listNonServiceLinkedRoles(ctx, conn, &input) { if err != nil { - result = fwdiag.NewListResultErrorDiagnostic(err) + result := fwdiag.NewListResultErrorDiagnostic(err) yield(result) return } - // Exclude Service-Linked Roles - if strings.HasPrefix(aws.ToString(output.Path), "/aws-service-role/") { - tflog.Debug(ctx, "Skipping resource", map[string]any{ - "skip_reason": "Service-Linked Role", - "role_name": aws.ToString(output.RoleName), - names.AttrPath: aws.ToString(output.Path), - }) - continue - } + ctx := resourceRoleListItemLoggingContext(ctx, role) + + result := request.NewListResult(ctx) rd := l.ResourceData() - rd.SetId(aws.ToString(output.RoleName)) - result.Diagnostics.Append(translateDiags(resourceRoleFlatten(ctx, &output, rd))...) + rd.SetId(aws.ToString(role.RoleName)) + + tflog.Info(ctx, "Reading resource") + result.Diagnostics.Append(translateDiags(resourceRoleFlatten(ctx, &role, rd))...) if result.Diagnostics.HasError() { yield(result) return @@ -1168,7 +1191,7 @@ func (l *roleListResource) List(ctx context.Context, request list.ListRequest, s return } - result.DisplayName = aws.ToString(output.RoleName) + result.DisplayName = resourceRoleDisplayName(role) l.SetResult(ctx, awsClient, request.IncludeResource, &result, rd) if result.Diagnostics.HasError() { @@ -1183,6 +1206,17 @@ func (l *roleListResource) List(ctx context.Context, request list.ListRequest, s } } +func resourceRoleDisplayName(role awstypes.Role) string { + var buf strings.Builder + + path := aws.ToString(role.Path) + buf.WriteString(strings.TrimPrefix(path, "/")) + + buf.WriteString(aws.ToString(role.RoleName)) + + return buf.String() +} + func translateDiags(in diag.Diagnostics) frameworkdiag.Diagnostics { out := make(frameworkdiag.Diagnostics, len(in)) for i, diagIn := range in { @@ -1237,3 +1271,7 @@ func translatePath(in cty.Path) path.Path { return out } + +func resourceRoleListItemLoggingContext(ctx context.Context, role awstypes.Role) context.Context { + return tflog.SetField(ctx, logging.ResourceAttributeKey(names.AttrName), aws.ToString(role.RoleName)) +} diff --git a/internal/service/iam/role_policy_attachment.go b/internal/service/iam/role_policy_attachment.go index 550c90889194..1e95b27f4d53 100644 --- a/internal/service/iam/role_policy_attachment.go +++ b/internal/service/iam/role_policy_attachment.go @@ -10,16 +10,25 @@ import ( "strings" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/iam" awstypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/hashicorp/terraform-plugin-framework/list" + listschema "github.com/hashicorp/terraform-plugin-framework/list/schema" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/logging" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -52,6 +61,14 @@ func resourceRolePolicyAttachment() *schema.Resource { } } +// @SDKListResource("aws_iam_role_policy_attachment") +func rolePolicyAttachmentResourceAsListResource() inttypes.ListResourceForSDK { + l := rolePolicyAttachmentListResource{} + l.SetResourceSchema(resourceRolePolicyAttachment()) + + return &l +} + func resourceRolePolicyAttachmentCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).IAMClient(ctx) @@ -195,8 +212,12 @@ func createRolePolicyAttachmentImportID(d *schema.ResourceData) string { type rolePolicyAttachmentImportID struct{} -func (rolePolicyAttachmentImportID) Create(d *schema.ResourceData) string { - return fmt.Sprintf("%s/%s", d.Get(names.AttrRole).(string), d.Get("policy_arn").(string)) +func (v rolePolicyAttachmentImportID) Create(d *schema.ResourceData) string { + return v.create(d.Get(names.AttrRole).(string), d.Get("policy_arn").(string)) +} + +func (rolePolicyAttachmentImportID) create(roleName, policyARN string) string { + return fmt.Sprintf("%s/%s", roleName, policyARN) } func (rolePolicyAttachmentImportID) Parse(id string) (string, map[string]string, error) { @@ -211,3 +232,118 @@ func (rolePolicyAttachmentImportID) Parse(id string) (string, map[string]string, } return id, result, nil } + +var _ list.ListResourceWithRawV5Schemas = &rolePolicyAttachmentListResource{} + +type rolePolicyAttachmentListResource struct { + framework.ResourceWithConfigure + framework.ListResourceWithSDKv2Resource +} + +type rolePolicyAttachmentListResourceModel struct { +} + +func (l *rolePolicyAttachmentListResource) ListResourceConfigSchema(ctx context.Context, request list.ListResourceSchemaRequest, response *list.ListResourceSchemaResponse) { + response.Schema = listschema.Schema{ + Attributes: map[string]listschema.Attribute{}, + } +} + +func (l *rolePolicyAttachmentListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream) { + awsClient := l.Meta() + conn := awsClient.IAMClient(ctx) + + var query rolePolicyAttachmentListResourceModel + if request.Config.Raw.IsKnown() && !request.Config.Raw.IsNull() { + if diags := request.Config.Get(ctx, &query); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + } + + var input iam.ListRolesInput + if diags := fwflex.Expand(ctx, query, &input); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + tflog.Info(ctx, "Listing resources") + + stream.Results = func(yield func(list.ListResult) bool) { + for role, err := range listNonServiceLinkedRoles(ctx, conn, &input) { + if err != nil { + result := fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + tflog.Info(ctx, "Listing attached policies for role", map[string]any{ + logging.ResourceAttributeKey(names.AttrRole): aws.ToString(role.RoleName), + }) + + input := iam.ListAttachedRolePoliciesInput{ + RoleName: role.RoleName, + } + pages := iam.NewListAttachedRolePoliciesPaginator(conn, &input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if errs.IsA[*awstypes.NoSuchEntityException](err) { + tflog.Warn(ctx, "Resource disappeared during listing, skipping", map[string]any{ + logging.ResourceAttributeKey(names.AttrRole): aws.ToString(role.RoleName), + }) + continue + } + if err != nil { + result := fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + for _, attachedPolicy := range page.AttachedPolicies { + ctx := resourceRolePolicyAttachmentListItemLoggingContext(ctx, role, attachedPolicy) + + result := request.NewListResult(ctx) + + rd := l.ResourceData() + rd.SetId(resourceRolePolicyAttachmentImportIDFromData(role, attachedPolicy)) + + tflog.Info(ctx, "Reading resource") + rd.Set(names.AttrRole, aws.ToString(role.RoleName)) + rd.Set("policy_arn", aws.ToString(attachedPolicy.PolicyArn)) + + result.DisplayName = resourceRolePolicyAttachmentDisplayName(role, attachedPolicy) + + l.SetResult(ctx, awsClient, request.IncludeResource, &result, rd) + if result.Diagnostics.HasError() { + yield(result) + return + } + + if !yield(result) { + return + } + } + } + } + } +} + +func resourceRolePolicyAttachmentListItemLoggingContext(ctx context.Context, role awstypes.Role, attachedPolicy awstypes.AttachedPolicy) context.Context { + ctx = tflog.SetField(ctx, logging.ResourceAttributeKey(names.AttrRole), aws.ToString(role.RoleName)) + ctx = tflog.SetField(ctx, logging.ResourceAttributeKey("policy_arn"), aws.ToString(attachedPolicy.PolicyArn)) + return ctx +} + +func resourceRolePolicyAttachmentImportIDFromData(role awstypes.Role, attachedPolicy awstypes.AttachedPolicy) string { + return (rolePolicyAttachmentImportID{}).create(aws.ToString(role.RoleName), aws.ToString(attachedPolicy.PolicyArn)) +} + +func resourceRolePolicyAttachmentDisplayName(role awstypes.Role, attachedPolicy awstypes.AttachedPolicy) string { + foo := aws.ToString(attachedPolicy.PolicyArn) + policyARN, err := arn.Parse(foo) + if err == nil { + foo = strings.TrimPrefix(policyARN.Resource, "policy/") + } + + return fmt.Sprintf("Role: %s - Policy: %s", resourceRoleDisplayName(role), foo) +} diff --git a/internal/service/iam/role_policy_attachment_list_test.go b/internal/service/iam/role_policy_attachment_list_test.go new file mode 100644 index 000000000000..032262fe9838 --- /dev/null +++ b/internal/service/iam/role_policy_attachment_list_test.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iam_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/config" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfquerycheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/querycheck" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccIAMRolePolicyAttachment_List_Basic(t *testing.T) { + ctx := acctest.Context(t) + + customerManagedName1 := "aws_iam_role_policy_attachment.customer_managed[0]" + customerManagedName2 := "aws_iam_role_policy_attachment.customer_managed[1]" + awsManagedName1 := "aws_iam_role_policy_attachment.aws_managed[0]" + awsManagedName2 := "aws_iam_role_policy_attachment.aws_managed[1]" + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + identity1 := tfstatecheck.Identity() + identity2 := tfstatecheck.Identity() + identity3 := tfstatecheck.Identity() + identity4 := tfstatecheck.Identity() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + CheckDestroy: testAccCheckRoleDestroy(ctx), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/RolePolicyAttachment/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(customerManagedName1), + statecheck.ExpectKnownValue(customerManagedName1, tfjsonpath.New(names.AttrRole), knownvalue.StringExact(rName+"-0")), + statecheck.CompareValuePairs(customerManagedName1, tfjsonpath.New("policy_arn"), "aws_iam_policy.test", tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + + identity2.GetIdentity(customerManagedName2), + statecheck.ExpectKnownValue(customerManagedName2, tfjsonpath.New(names.AttrRole), knownvalue.StringExact(rName+"-1")), + statecheck.CompareValuePairs(customerManagedName2, tfjsonpath.New("policy_arn"), "aws_iam_policy.test", tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + + identity3.GetIdentity(awsManagedName1), + statecheck.ExpectKnownValue(awsManagedName1, tfjsonpath.New(names.AttrRole), knownvalue.StringExact(rName+"-0")), + statecheck.CompareValuePairs(awsManagedName1, tfjsonpath.New("policy_arn"), "data.aws_iam_policy.AmazonDynamoDBReadOnlyAccess", tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + + identity4.GetIdentity(awsManagedName2), + statecheck.ExpectKnownValue(awsManagedName2, tfjsonpath.New(names.AttrRole), knownvalue.StringExact(rName+"-1")), + statecheck.CompareValuePairs(awsManagedName2, tfjsonpath.New("policy_arn"), "data.aws_iam_policy.AmazonDynamoDBReadOnlyAccess", tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + }, + }, + + // Step 2: Query + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/RolePolicyAttachment/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_iam_role_policy_attachment.test", identity1.Checks()), + + tfquerycheck.ExpectIdentityFunc("aws_iam_role_policy_attachment.test", identity2.Checks()), + + tfquerycheck.ExpectIdentityFunc("aws_iam_role_policy_attachment.test", identity3.Checks()), + + tfquerycheck.ExpectIdentityFunc("aws_iam_role_policy_attachment.test", identity4.Checks()), + }, + }, + }, + }) +} diff --git a/internal/service/iam/service_package_gen.go b/internal/service/iam/service_package_gen.go index a00416fe386f..9d251ec2b392 100644 --- a/internal/service/iam/service_package_gen.go +++ b/internal/service/iam/service_package_gen.go @@ -455,6 +455,16 @@ func (p *servicePackage) SDKListResources(ctx context.Context) iter.Seq[*inttype }), Identity: inttypes.GlobalSingleParameterIdentity(names.AttrName), }, + { + Factory: rolePolicyAttachmentResourceAsListResource, + TypeName: "aws_iam_role_policy_attachment", + Name: "Role Policy Attachment", + Region: unique.Make(inttypes.ResourceRegionDisabled()), + Identity: inttypes.GlobalParameterizedIdentity([]inttypes.IdentityAttribute{ + inttypes.StringIdentityAttribute(names.AttrRole, true), + inttypes.StringIdentityAttribute("policy_arn", true), + }), + }, }) } diff --git a/internal/service/iam/testdata/RolePolicyAttachment/list_basic/main.tf b/internal/service/iam/testdata/RolePolicyAttachment/list_basic/main.tf new file mode 100644 index 000000000000..595d2adb50b9 --- /dev/null +++ b/internal/service/iam/testdata/RolePolicyAttachment/list_basic/main.tf @@ -0,0 +1,65 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_iam_role_policy_attachment" "customer_managed" { + count = 2 + + role = aws_iam_role.test[count.index].name + policy_arn = aws_iam_policy.test.arn +} + +resource "aws_iam_role_policy_attachment" "aws_managed" { + count = 2 + + role = aws_iam_role.test[count.index].name + policy_arn = data.aws_iam_policy.AmazonDynamoDBReadOnlyAccess.arn +} + +resource "aws_iam_role" "test" { + count = 2 + + name = "${var.rName}-${count.index}" + + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +data "aws_iam_policy_document" "assume_role" { + statement { + actions = ["sts:AssumeRole"] + effect = "Allow" + + principals { + identifiers = ["ec2.amazonaws.com"] + type = "Service" + } + } +} + +resource "aws_iam_policy" "test" { + name = var.rName + description = "A test policy" + + policy = data.aws_iam_policy_document.test.json +} + +data "aws_iam_policy_document" "test" { + statement { + effect = "Allow" + actions = [ + "iam:ChangePassword" + ] + resources = [ + "*" + ] + } +} + +data "aws_iam_policy" "AmazonDynamoDBReadOnlyAccess" { + name = "AmazonDynamoDBReadOnlyAccess" +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} diff --git a/internal/service/iam/testdata/RolePolicyAttachment/list_basic/main.tfquery.hcl b/internal/service/iam/testdata/RolePolicyAttachment/list_basic/main.tfquery.hcl new file mode 100644 index 000000000000..04e9f50a2703 --- /dev/null +++ b/internal/service/iam/testdata/RolePolicyAttachment/list_basic/main.tfquery.hcl @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +list "aws_iam_role_policy_attachment" "test" { + provider = aws +} diff --git a/internal/service/iam/validate_framework_test.go b/internal/service/iam/validate_framework_test.go index c7d84266c649..c02992558a13 100644 --- a/internal/service/iam/validate_framework_test.go +++ b/internal/service/iam/validate_framework_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/names" ) func TestValidPolicyPathFramework(t *testing.T) { diff --git a/website/docs/list-resources/iam_policy.html.markdown b/website/docs/list-resources/iam_policy.html.markdown index 65834ef65586..33eafa93ad05 100644 --- a/website/docs/list-resources/iam_policy.html.markdown +++ b/website/docs/list-resources/iam_policy.html.markdown @@ -12,7 +12,7 @@ description: |- Lists IAM Policy resources. -Excludes AWS Managed Policiesmanaged policies" in [Policies and permissions in AWS Identity and Access Management documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#access_policy-types). +Excludes AWS Managed Policies (see "AWS managed policies" in [Policies and permissions in AWS Identity and Access Management documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#access_policy-types). ## Example Usage diff --git a/website/docs/list-resources/iam_role_policy_attachment.html.markdown b/website/docs/list-resources/iam_role_policy_attachment.html.markdown new file mode 100644 index 000000000000..628ca7e85e01 --- /dev/null +++ b/website/docs/list-resources/iam_role_policy_attachment.html.markdown @@ -0,0 +1,27 @@ +--- +subcategory: "IAM (Identity & Access Management)" +layout: "aws" +page_title: "AWS: aws_iam_role_policy_attachment" +description: |- + Lists IAM Role-Policy Attachment resources. +--- + +# List Resource: aws_iam_role_policy_attachment + +~> **Note:** The `aws_iam_role_policy_attachment` List Resource is in beta. Its interface and behavior may change as the feature evolves, and breaking changes are possible. It is offered as a technical preview without compatibility guarantees until Terraform 1.14 is generally available. + +Lists IAM Role-Policy Attachment resources. + +Excludes Service-Linked Roles (see "AWS service-linked role" in [IAM Roles Terms and Concepts documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html#id_roles_terms-and-concepts)). + +## Example Usage + +```terraform +list "aws_iam_role_policy_attachment" "example" { + provider = aws +} +``` + +## Argument Reference + +This list resource does not support any arguments.