Skip to content

Commit 70fd597

Browse files
committed
r/aws_iam_user: switch tag identifier to id
IAM user tag operations rely on the user name as the identifier. When the `name` argument is used as the identifier attribute, an update which changes both `name` and `tag` arguments causes transparent tagging to fail. Transparent tagging operations run _before_ the update operation, meaning the updated `name` value is passed to the [`TagUser`](https://docs.aws.amazon.com/IAM/latest/APIReference/API_TagUser.html) API before the [`UpdateUser`](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateUser.html) API is called to update it. By switching the tag identifier to `id`, we allow the `TagUser` API to reference the "old" user name, via `d.GetId()`, before the update operation executes and changes the name along with resource identifier. As `id` tracks the user name, this change does not break any existing tagging workflows, just enables the "name and tag" update case to behave as expected. Before: ```console % make t K=iam T=TestAccIAMUser_nameAndTags make: Verifying source code with gofmt... ==> Checking that code complies with gofmt requirements... make: Running acceptance tests on branch: 🌿 b-iam_user-tag-and-name-update 🌿... TF_ACC=1 go1.24.11 test ./internal/service/iam/... -v -count 1 -parallel 20 -run='TestAccIAMUser_nameAndTags' -timeout 360m -vet=off 2025/12/15 16:39:45 Creating Terraform AWS Provider (SDKv2-style)... 2025/12/15 16:39:45 Initializing Terraform AWS Provider (SDKv2-style)... === CONT TestAccIAMUser_nameAndTags user_test.go:538: Step 2/2 error: Error running apply: exit status 1 Error: updating tags for IAM (Identity & Access Management) User (tf-acc-test-1187392722609479887-updated): tagging resource (tf-acc-test-1187392722609479887-updated): operation error IAM: TagUser, https response error StatusCode: 404, RequestID: 5651011a-12db-4b23-9199-24d6672641bf, NoSuchEntity: The user with name tf-acc-test-1187392722609479887-updated cannot be found. with aws_iam_user.user, on terraform_plugin_test.tf line 12, in resource "aws_iam_user" "user": 12: resource "aws_iam_user" "user" { --- FAIL: TestAccIAMUser_nameAndTags (15.18s) FAIL FAIL github.com/hashicorp/terraform-provider-aws/internal/service/iam 21.693s ``` After: ```console % make t K=iam T=TestAccIAMUser_nameAndTags make: Verifying source code with gofmt... ==> Checking that code complies with gofmt requirements... make: Running acceptance tests on branch: 🌿 b-iam_user-tag-and-name-update 🌿... TF_ACC=1 go1.24.11 test ./internal/service/iam/... -v -count 1 -parallel 20 -run='TestAccIAMUser_nameAndTags' -timeout 360m -vet=off 2025/12/16 10:20:19 Creating Terraform AWS Provider (SDKv2-style)... 2025/12/16 10:20:19 Initializing Terraform AWS Provider (SDKv2-style)... --- PASS: TestAccIAMUser_nameAndTags (21.19s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/iam 27.774s ``` ```console % make t K=iam T=TestAccIAMUser_ make: Verifying source code with gofmt... ==> Checking that code complies with gofmt requirements... make: Running acceptance tests on branch: 🌿 b-iam_user-tag-and-name-update 🌿... TF_ACC=1 go1.24.11 test ./internal/service/iam/... -v -count 1 -parallel 20 -run='TestAccIAMUser_' -timeout 360m -vet=off 2025/12/16 10:25:18 Creating Terraform AWS Provider (SDKv2-style)... 2025/12/16 10:25:18 Initializing Terraform AWS Provider (SDKv2-style)... --- PASS: TestAccIAMUser_ForceDestroy_policyInline (34.73s) === CONT TestAccIAMUser_tags_DefaultTags_updateToResourceOnly --- PASS: TestAccIAMUser_ForceDestroy_policyAttached (36.71s) === CONT TestAccIAMUser_tags_DefaultTags_updateToProviderOnly --- PASS: TestAccIAMUser_ForceDestroy_policyInlineAttached (36.82s) === CONT TestAccIAMUser_basic --- PASS: TestAccIAMUser_ForceDestroy_accessKey (43.86s) === CONT TestAccIAMUser_disappears --- PASS: TestAccIAMUser_ForceDestroy_signingCertificate (45.67s) === CONT TestAccIAMUser_tags_IgnoreTags_Overlap_ResourceTag --- PASS: TestAccIAMUser_ForceDestroy_serviceSpecificCred (45.80s) === CONT TestAccIAMUser_ForceDestroy_sshKey --- PASS: TestAccIAMUser_ForceDestroy_mfaDevice (45.97s) === CONT TestAccIAMUser_tags_IgnoreTags_Overlap_DefaultTag --- PASS: TestAccIAMUser_tags_DefaultTags_nullNonOverlappingResourceTag (49.78s) === CONT TestAccIAMUser_tags_EmptyTag_OnUpdate_Add --- PASS: TestAccIAMUser_tags_DefaultTags_nullOverlappingResourceTag (51.79s) === CONT TestAccIAMUser_tags_DefaultTags_nonOverlapping --- PASS: TestAccIAMUser_tags_DefaultTags_emptyProviderOnlyTag (52.16s) === CONT TestAccIAMUser_tags_DefaultTags_providerOnly --- PASS: TestAccIAMUser_tags_DefaultTags_emptyResourceTag (54.09s) === CONT TestAccIAMUser_tags_EmptyTag_OnUpdate_Replace --- PASS: TestAccIAMUser_tags_ComputedTag_OnCreate (56.57s) === CONT TestAccIAMUser_tags_AddOnUpdate --- PASS: TestAccIAMUser_pathChange (69.16s) === CONT TestAccIAMUser_tags_EmptyTag_OnCreate --- PASS: TestAccIAMUser_nameChange (73.24s) === CONT TestAccIAMUser_ForceDestroy_loginProfile --- PASS: TestAccIAMUser_nameAndTags (75.08s) === CONT TestAccIAMUser_tags_EmptyMap --- PASS: TestAccIAMUser_disappears (37.71s) === CONT TestAccIAMUser_tags_null --- PASS: TestAccIAMUser_tags_ComputedTag_OnUpdate_Replace (88.28s) --- PASS: TestAccIAMUser_tags_ComputedTag_OnUpdate_Add (91.48s) --- PASS: TestAccIAMUser_ForceDestroy_sshKey (46.05s) --- PASS: TestAccIAMUser_basic (71.99s) --- PASS: TestAccIAMUser_ForceDestroy_loginProfile (40.51s) --- PASS: TestAccIAMUser_tags_DefaultTags_updateToResourceOnly (79.26s) --- PASS: TestAccIAMUser_tags_DefaultTags_updateToProviderOnly (78.40s) --- PASS: TestAccIAMUser_tags_EmptyTag_OnUpdate_Replace (72.19s) --- PASS: TestAccIAMUser_tags_AddOnUpdate (70.56s) --- PASS: TestAccIAMUser_tags_DefaultTags_overlapping (131.10s) --- PASS: TestAccIAMUser_tags_EmptyMap (57.29s) --- PASS: TestAccIAMUser_tags_IgnoreTags_Overlap_DefaultTag (88.51s) --- PASS: TestAccIAMUser_tags_null (53.51s) --- PASS: TestAccIAMUser_tags_EmptyTag_OnCreate (69.51s) --- PASS: TestAccIAMUser_tags_IgnoreTags_Overlap_ResourceTag (94.78s) --- PASS: TestAccIAMUser_tags_EmptyTag_OnUpdate_Add (93.17s) --- PASS: TestAccIAMUser_tags (142.97s) --- PASS: TestAccIAMUser_tags_DefaultTags_nonOverlapping (93.90s) --- PASS: TestAccIAMUser_permissionsBoundary (147.57s) --- PASS: TestAccIAMUser_tags_DefaultTags_providerOnly (105.94s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/iam 164.605s ```
1 parent 2ac2917 commit 70fd597

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

internal/service/iam/service_package_gen.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/service/iam/user.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
)
2929

3030
// @SDKResource("aws_iam_user", name="User")
31-
// @Tags(identifierAttribute="name", resourceType="User")
31+
// @Tags(identifierAttribute="id", resourceType="User")
3232
// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/iam/types;types.User", importIgnore="force_destroy")
3333
func resourceUser() *schema.Resource {
3434
return &schema.Resource{

internal/service/iam/user_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import (
1414
awstypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
1515
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
1616
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
17+
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
18+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
19+
"github.com/hashicorp/terraform-plugin-testing/statecheck"
1720
"github.com/hashicorp/terraform-plugin-testing/terraform"
21+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
1822
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
1923
"github.com/hashicorp/terraform-provider-aws/internal/conns"
2024
"github.com/hashicorp/terraform-provider-aws/internal/retry"
@@ -523,6 +527,67 @@ func TestAccIAMUser_permissionsBoundary(t *testing.T) {
523527
})
524528
}
525529

530+
// Verify behavior when name and tags are updated simultaneously
531+
func TestAccIAMUser_nameAndTags(t *testing.T) {
532+
ctx := acctest.Context(t)
533+
var u awstypes.User
534+
535+
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
536+
rNameUpdated := rName + "-updated"
537+
resourceName := "aws_iam_user.user"
538+
539+
resource.ParallelTest(t, resource.TestCase{
540+
PreCheck: func() { acctest.PreCheck(ctx, t) },
541+
ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID),
542+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
543+
CheckDestroy: testAccCheckUserDestroy(ctx),
544+
Steps: []resource.TestStep{
545+
{
546+
Config: testAccUserConfig_nameAndTags(rName, acctest.CtKey1, acctest.CtValue1),
547+
Check: resource.ComposeTestCheckFunc(
548+
testAccCheckUserExists(ctx, "aws_iam_user.user", &u),
549+
),
550+
ConfigPlanChecks: resource.ConfigPlanChecks{
551+
PreApply: []plancheck.PlanCheck{
552+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
553+
plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrName), knownvalue.StringExact(rName)),
554+
plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{
555+
acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1),
556+
})),
557+
},
558+
},
559+
ConfigStateChecks: []statecheck.StateCheck{
560+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrName), knownvalue.StringExact(rName)),
561+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{
562+
acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1),
563+
})),
564+
},
565+
},
566+
{
567+
Config: testAccUserConfig_nameAndTags(rNameUpdated, acctest.CtKey1, acctest.CtValue1Updated),
568+
Check: resource.ComposeTestCheckFunc(
569+
testAccCheckUserExists(ctx, "aws_iam_user.user", &u),
570+
),
571+
ConfigPlanChecks: resource.ConfigPlanChecks{
572+
PreApply: []plancheck.PlanCheck{
573+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
574+
plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrName), knownvalue.StringExact(rNameUpdated)),
575+
plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{
576+
acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1Updated),
577+
})),
578+
},
579+
},
580+
ConfigStateChecks: []statecheck.StateCheck{
581+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrName), knownvalue.StringExact(rNameUpdated)),
582+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{
583+
acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1Updated),
584+
})),
585+
},
586+
},
587+
},
588+
})
589+
}
590+
526591
func testAccCheckUserDestroy(ctx context.Context) resource.TestCheckFunc {
527592
return func(s *terraform.State) error {
528593
conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx)
@@ -827,3 +892,15 @@ resource "aws_iam_user" "test" {
827892
}
828893
`, rName)
829894
}
895+
896+
func testAccUserConfig_nameAndTags(rName, key1, value1 string) string {
897+
return fmt.Sprintf(`
898+
resource "aws_iam_user" "user" {
899+
name = %[1]q
900+
901+
tags = {
902+
%[2]s = %[3]q
903+
}
904+
}
905+
`, rName, key1, value1)
906+
}

0 commit comments

Comments
 (0)