diff --git a/.changelog/27563.txt b/.changelog/27563.txt new file mode 100644 index 00000000000..cf0ee617c35 --- /dev/null +++ b/.changelog/27563.txt @@ -0,0 +1,7 @@ +```release-note:bug +resource/aws_transfer_user: Fix bug preventing removal of all `home_directory_mappings` due to empty list validation error +``` + +```release-note:enhancement +resource/aws_transfer_user: Add configurable timeout for Delete +``` \ No newline at end of file diff --git a/internal/service/transfer/find.go b/internal/service/transfer/find.go index f4f3407cfe9..85c6836da75 100644 --- a/internal/service/transfer/find.go +++ b/internal/service/transfer/find.go @@ -61,32 +61,6 @@ func FindServerByID(ctx context.Context, conn *transfer.Transfer, id string) (*t return output.Server, nil } -func FindUserByServerIDAndUserName(ctx context.Context, conn *transfer.Transfer, serverID, userName string) (*transfer.DescribedUser, error) { - input := &transfer.DescribeUserInput{ - ServerId: aws.String(serverID), - UserName: aws.String(userName), - } - - output, err := conn.DescribeUserWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, transfer.ErrCodeResourceNotFoundException) { - return nil, &resource.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || output.User == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.User, nil -} - func FindWorkflowByID(ctx context.Context, conn *transfer.Transfer, id string) (*transfer.DescribedWorkflow, error) { input := &transfer.DescribeWorkflowInput{ WorkflowId: aws.String(id), diff --git a/internal/service/transfer/server.go b/internal/service/transfer/server.go index 731b9c2cf88..e4735d1c11d 100644 --- a/internal/service/transfer/server.go +++ b/internal/service/transfer/server.go @@ -32,6 +32,7 @@ func ResourceServer() *schema.Resource { ReadWithoutTimeout: resourceServerRead, UpdateWithoutTimeout: resourceServerUpdate, DeleteWithoutTimeout: resourceServerDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -689,7 +690,7 @@ func resourceServerDelete(ctx context.Context, d *schema.ResourceData, meta inte } for _, user := range page.Users { - err := userDelete(ctx, conn, d.Id(), aws.StringValue(user.UserName)) + err := userDelete(ctx, conn, d.Id(), aws.StringValue(user.UserName), d.Timeout(schema.TimeoutDelete)) if err != nil { deletionErrs = multierror.Append(deletionErrs, err) diff --git a/internal/service/transfer/server_data_source_test.go b/internal/service/transfer/server_data_source_test.go index 2ed278b4b14..28da6da928e 100644 --- a/internal/service/transfer/server_data_source_test.go +++ b/internal/service/transfer/server_data_source_test.go @@ -10,12 +10,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" ) -func TestAccTransferServerDataSource_basic(t *testing.T) { +func testAccServerDataSource_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_transfer_server.test" datasourceName := "data.aws_transfer_server.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -34,13 +34,13 @@ func TestAccTransferServerDataSource_basic(t *testing.T) { }) } -func TestAccTransferServerDataSource_Service_managed(t *testing.T) { +func testAccServerDataSource_Service_managed(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandString(5) resourceName := "aws_transfer_server.test" datasourceName := "data.aws_transfer_server.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -64,13 +64,13 @@ func TestAccTransferServerDataSource_Service_managed(t *testing.T) { }) } -func TestAccTransferServerDataSource_apigateway(t *testing.T) { +func testAccServerDataSource_apigateway(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandString(5) resourceName := "aws_transfer_server.test" datasourceName := "data.aws_transfer_server.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t); acctest.PreCheckAPIGatewayTypeEDGE(t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/transfer/status.go b/internal/service/transfer/status.go index 12c52b22f66..8c459a64b77 100644 --- a/internal/service/transfer/status.go +++ b/internal/service/transfer/status.go @@ -9,10 +9,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -const ( - userStateExists = "exists" -) - func statusServerState(ctx context.Context, conn *transfer.Transfer, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { output, err := FindServerByID(ctx, conn, id) @@ -28,19 +24,3 @@ func statusServerState(ctx context.Context, conn *transfer.Transfer, id string) return output, aws.StringValue(output.State), nil } } - -func statusUserState(ctx context.Context, conn *transfer.Transfer, serverID, userName string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := FindUserByServerIDAndUserName(ctx, conn, serverID, userName) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return output, userStateExists, nil - } -} diff --git a/internal/service/transfer/tag_test.go b/internal/service/transfer/tag_test.go index 605dfbe9aa6..a16dd96e156 100644 --- a/internal/service/transfer/tag_test.go +++ b/internal/service/transfer/tag_test.go @@ -11,12 +11,12 @@ import ( tftransfer "github.com/hashicorp/terraform-provider-aws/internal/service/transfer" ) -func TestAccTransferTag_basic(t *testing.T) { +func testAccTag_basic(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_transfer_tag.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -39,12 +39,12 @@ func TestAccTransferTag_basic(t *testing.T) { }) } -func TestAccTransferTag_disappears(t *testing.T) { +func testAccTag_disappears(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_transfer_tag.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -62,12 +62,12 @@ func TestAccTransferTag_disappears(t *testing.T) { }) } -func TestAccTransferTag_value(t *testing.T) { +func testAccTag_value(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_transfer_tag.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -98,12 +98,12 @@ func TestAccTransferTag_value(t *testing.T) { }) } -func TestAccTransferTag_system(t *testing.T) { +func testAccTag_system(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_transfer_tag.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/transfer/transfer_test.go b/internal/service/transfer/transfer_test.go index d1959fb75b5..5dcfbcd4b65 100644 --- a/internal/service/transfer/transfer_test.go +++ b/internal/service/transfer/transfer_test.go @@ -22,6 +22,9 @@ func TestAccTransfer_serial(t *testing.T) { "APIGateway": testAccServer_apiGateway, "APIGatewayForceDestroy": testAccServer_apiGateway_forceDestroy, "AuthenticationLoginBanners": testAccServer_authenticationLoginBanners, + "DataSourceBasic": testAccServerDataSource_basic, + "DataSourceServiceManaged": testAccServerDataSource_Service_managed, + "DataSourceAPIGateway": testAccServerDataSource_apigateway, "DirectoryService": testAccServer_directoryService, "Domain": testAccServer_domain, "ForceDestroy": testAccServer_forceDestroy, @@ -45,9 +48,16 @@ func TestAccTransfer_serial(t *testing.T) { "SSHKey": { "basic": testAccSSHKey_basic, }, + "Tag": { + "basic": testAccTag_basic, + "disappears": testAccTag_disappears, + "Value": testAccTag_value, + "System": testAccTag_system, + }, "User": { "basic": testAccUser_basic, "disappears": testAccUser_disappears, + "tags": testAccUser_tags, "HomeDirectoryMappings": testAccUser_homeDirectoryMappings, "ModifyWithOptions": testAccUser_modifyWithOptions, "Posix": testAccUser_posix, diff --git a/internal/service/transfer/user.go b/internal/service/transfer/user.go index e7f01d18ff6..7206b557018 100644 --- a/internal/service/transfer/user.go +++ b/internal/service/transfer/user.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "log" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/transfer" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -32,18 +34,20 @@ func ResourceUser() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, Computed: true, }, - "home_directory": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(0, 1024), }, - "home_directory_mappings": { Type: schema.TypeList, Optional: true, @@ -62,14 +66,12 @@ func ResourceUser() *schema.Resource { }, }, }, - "home_directory_type": { Type: schema.TypeString, Optional: true, Default: transfer.HomeDirectoryTypePath, - ValidateFunc: validation.StringInSlice([]string{transfer.HomeDirectoryTypePath, transfer.HomeDirectoryTypeLogical}, false), + ValidateFunc: validation.StringInSlice(transfer.HomeDirectoryType_Values(), false), }, - "policy": { Type: schema.TypeString, Optional: true, @@ -81,7 +83,6 @@ func ResourceUser() *schema.Resource { return json }, }, - "posix_profile": { Type: schema.TypeList, MaxItems: 1, @@ -92,35 +93,31 @@ func ResourceUser() *schema.Resource { Type: schema.TypeInt, Required: true, }, - "uid": { - Type: schema.TypeInt, - Required: true, - }, "secondary_gids": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeInt}, Optional: true, }, + "uid": { + Type: schema.TypeInt, + Required: true, + }, }, }, }, - "role": { Type: schema.TypeString, Required: true, ValidateFunc: verify.ValidARN, }, - "server_id": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validServerID, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), - "user_name": { Type: schema.TypeString, Required: true, @@ -177,7 +174,6 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Creating Transfer User: %s", input) _, err := conn.CreateUserWithContext(ctx, input) if err != nil { @@ -201,7 +197,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac return sdkdiag.AppendErrorf(diags, "parsing Transfer User ID: %s", err) } - user, err := FindUserByServerIDAndUserName(ctx, conn, serverID, userName) + user, err := FindUserByTwoPartKey(ctx, conn, serverID, userName) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Transfer User (%s) not found, removing from state", d.Id()) @@ -224,7 +220,6 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac if err != nil { return sdkdiag.AppendErrorf(diags, "reading Transfer User (%s): %s", d.Id(), err) } - d.Set("policy", policyToSet) if err := d.Set("posix_profile", flattenUserPOSIXUser(user.PosixProfile)); err != nil { @@ -292,7 +287,6 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf input.Role = aws.String(d.Get("role").(string)) } - log.Printf("[DEBUG] Updating Transfer User: %s", input) _, err = conn.UpdateUserWithContext(ctx, input) if err != nil { @@ -302,6 +296,7 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") + if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { return sdkdiag.AppendErrorf(diags, "updating tags: %s", err) } @@ -320,14 +315,40 @@ func resourceUserDelete(ctx context.Context, d *schema.ResourceData, meta interf return sdkdiag.AppendErrorf(diags, "parsing Transfer User ID: %s", err) } - if err := userDelete(ctx, conn, serverID, userName); err != nil { + if err := userDelete(ctx, conn, serverID, userName, d.Timeout(schema.TimeoutDelete)); err != nil { return sdkdiag.AppendFromErr(diags, err) } + return diags } -// userDelete attempts to delete a transfer user. -func userDelete(ctx context.Context, conn *transfer.Transfer, serverID, userName string) error { +func FindUserByTwoPartKey(ctx context.Context, conn *transfer.Transfer, serverID, userName string) (*transfer.DescribedUser, error) { + input := &transfer.DescribeUserInput{ + ServerId: aws.String(serverID), + UserName: aws.String(userName), + } + + output, err := conn.DescribeUserWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, transfer.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.User == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.User, nil +} + +func userDelete(ctx context.Context, conn *transfer.Transfer, serverID, userName string, timeout time.Duration) error { id := UserCreateResourceID(serverID, userName) input := &transfer.DeleteUserInput{ ServerId: aws.String(serverID), @@ -345,16 +366,22 @@ func userDelete(ctx context.Context, conn *transfer.Transfer, serverID, userName return fmt.Errorf("deleting Transfer User (%s): %w", id, err) } - _, err = waitUserDeleted(ctx, conn, serverID, userName) + _, err = tfresource.RetryUntilNotFound(ctx, timeout, func() (interface{}, error) { + return FindUserByTwoPartKey(ctx, conn, serverID, userName) + }) if err != nil { - return fmt.Errorf("deleting Transfer User (%s): waiting for completion: %w", id, err) + return fmt.Errorf("waiting for Transfer User (%s) delete: %w", id, err) } return nil } func expandHomeDirectoryMappings(in []interface{}) []*transfer.HomeDirectoryMapEntry { + if len(in) == 0 { + return nil + } + mappings := make([]*transfer.HomeDirectoryMapEntry, 0) for _, tConfig := range in { diff --git a/internal/service/transfer/user_test.go b/internal/service/transfer/user_test.go index 099b64686bf..38f513a5b79 100644 --- a/internal/service/transfer/user_test.go +++ b/internal/service/transfer/user_test.go @@ -20,7 +20,7 @@ func testAccUser_basic(t *testing.T) { ctx := acctest.Context(t) var conf transfer.DescribedUser resourceName := "aws_transfer_user.test" - rName := sdkacctest.RandString(10) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, @@ -33,9 +33,10 @@ func testAccUser_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckUserExists(ctx, resourceName, &conf), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "transfer", regexp.MustCompile(`user/.+`)), - resource.TestCheckResourceAttrPair(resourceName, "server_id", "aws_transfer_server.test", "id"), - resource.TestCheckResourceAttrPair(resourceName, "role", "aws_iam_role.test", "arn"), resource.TestCheckResourceAttr(resourceName, "posix_profile.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "role", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "server_id", "aws_transfer_server.test", "id"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -47,11 +48,81 @@ func testAccUser_basic(t *testing.T) { }) } +func testAccUser_disappears(t *testing.T) { + ctx := acctest.Context(t) + var userConf transfer.DescribedUser + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_transfer_user.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(ctx, resourceName, &userConf), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tftransfer.ResourceUser(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccUser_tags(t *testing.T) { + ctx := acctest.Context(t) + var conf transfer.DescribedUser + resourceName := "aws_transfer_user.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccUserConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccUserConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccUserConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + func testAccUser_posix(t *testing.T) { ctx := acctest.Context(t) var conf transfer.DescribedUser resourceName := "aws_transfer_user.test" - rName := sdkacctest.RandString(10) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, @@ -91,8 +162,8 @@ func testAccUser_modifyWithOptions(t *testing.T) { ctx := acctest.Context(t) var conf transfer.DescribedUser resourceName := "aws_transfer_user.test" - rName := sdkacctest.RandString(10) - rName2 := sdkacctest.RandString(10) + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, @@ -101,68 +172,37 @@ func testAccUser_modifyWithOptions(t *testing.T) { CheckDestroy: testAccCheckUserDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccUserConfig_options(rName), + Config: testAccUserConfig_options(rName1), Check: resource.ComposeTestCheckFunc( testAccCheckUserExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "home_directory", "/home/tftestuser"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), - resource.TestCheckResourceAttr(resourceName, "tags.NAME", "tftestuser"), - resource.TestCheckResourceAttr(resourceName, "tags.ENV", "test"), - resource.TestCheckResourceAttr(resourceName, "tags.ADMIN", "test"), ), }, { Config: testAccUserConfig_modify(rName2), Check: resource.ComposeTestCheckFunc( testAccCheckUserExists(ctx, resourceName, &conf), - resource.TestCheckResourceAttrPair(resourceName, "role", "aws_iam_role.test", "arn"), resource.TestCheckResourceAttr(resourceName, "home_directory", "/test"), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.NAME", "tf-test-user"), - resource.TestCheckResourceAttr(resourceName, "tags.TEST", "test2"), + resource.TestCheckResourceAttrPair(resourceName, "role", "aws_iam_role.test", "arn"), ), }, { Config: testAccUserConfig_forceNew(rName2), Check: resource.ComposeTestCheckFunc( testAccCheckUserExists(ctx, resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "user_name", "tftestuser2"), - resource.TestCheckResourceAttrPair(resourceName, "role", "aws_iam_role.test", "arn"), resource.TestCheckResourceAttr(resourceName, "home_directory", "/home/tftestuser2"), + resource.TestCheckResourceAttrPair(resourceName, "role", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "user_name", "tftestuser2"), ), }, }, }) } -func testAccUser_disappears(t *testing.T) { - ctx := acctest.Context(t) - var serverConf transfer.DescribedServer - var userConf transfer.DescribedUser - rName := sdkacctest.RandString(10) - resourceName := "aws_transfer_user.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckUserDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccUserConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckServerExists(ctx, "aws_transfer_server.test", &serverConf), - testAccCheckUserExists(ctx, "aws_transfer_user.test", &userConf), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tftransfer.ResourceUser(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - func testAccUser_UserName_Validation(t *testing.T) { ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), @@ -170,28 +210,28 @@ func testAccUser_UserName_Validation(t *testing.T) { CheckDestroy: testAccCheckUserDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccUserConfig_nameValidation("!@#$%^"), + Config: testAccUserConfig_nameValidation(rName, "!@#$%^"), ExpectError: regexp.MustCompile(`Invalid "user_name": `), }, { - Config: testAccUserConfig_nameValidation(sdkacctest.RandString(2)), + Config: testAccUserConfig_nameValidation(rName, sdkacctest.RandString(2)), ExpectError: regexp.MustCompile(`Invalid "user_name": `), }, { - Config: testAccUserConfig_nameValidation(sdkacctest.RandString(33)), + Config: testAccUserConfig_nameValidation(rName, sdkacctest.RandString(33)), ExpectNonEmptyPlan: true, PlanOnly: true, }, { - Config: testAccUserConfig_nameValidation(sdkacctest.RandString(101)), + Config: testAccUserConfig_nameValidation(rName, sdkacctest.RandString(101)), ExpectError: regexp.MustCompile(`Invalid "user_name": `), }, { - Config: testAccUserConfig_nameValidation("-abcdef"), + Config: testAccUserConfig_nameValidation(rName, "-abcdef"), ExpectError: regexp.MustCompile(`Invalid "user_name": `), }, { - Config: testAccUserConfig_nameValidation("valid_username"), + Config: testAccUserConfig_nameValidation(rName, "valid_username"), ExpectNonEmptyPlan: true, PlanOnly: true, }, @@ -202,8 +242,12 @@ func testAccUser_UserName_Validation(t *testing.T) { func testAccUser_homeDirectoryMappings(t *testing.T) { ctx := acctest.Context(t) var conf transfer.DescribedUser - rName := sdkacctest.RandString(10) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_transfer_user.test" + entry1 := "/your-personal-report.pdf" + target1 := "/bucket3/customized-reports/tftestuser.pdf" + entry2 := "/your-personal-report2.pdf" + target2 := "/bucket3/customized-reports2/tftestuser.pdf" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(ctx, t) }, @@ -212,17 +256,25 @@ func testAccUser_homeDirectoryMappings(t *testing.T) { CheckDestroy: testAccCheckUserDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccUserConfig_homeDirectoryMappings(rName), + Config: testAccUserConfig_homeDirectoryMappings(rName, entry1, target1), Check: resource.ComposeTestCheckFunc( testAccCheckUserExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.0.entry", entry1), + resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.0.target", target1), + resource.TestCheckResourceAttr(resourceName, "home_directory_type", "LOGICAL"), ), }, { - Config: testAccUserConfig_homeDirectoryMappingsUpdate(rName), + Config: testAccUserConfig_homeDirectoryMappingsUpdate(rName, entry1, target1, entry2, target2), Check: resource.ComposeTestCheckFunc( testAccCheckUserExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.#", "2"), + resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.0.entry", entry1), + resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.0.target", target1), + resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.1.entry", entry2), + resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.1.target", target2), + resource.TestCheckResourceAttr(resourceName, "home_directory_type", "LOGICAL"), ), }, { @@ -230,11 +282,19 @@ func testAccUser_homeDirectoryMappings(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccUserConfig_homeDirectoryMappingsRemove(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "home_directory_mappings.#", "0"), + resource.TestCheckResourceAttr(resourceName, "home_directory_type", "PATH"), + ), + }, }, }) } -func testAccCheckUserExists(ctx context.Context, n string, res *transfer.DescribedUser) resource.TestCheckFunc { +func testAccCheckUserExists(ctx context.Context, n string, v *transfer.DescribedUser) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -247,16 +307,19 @@ func testAccCheckUserExists(ctx context.Context, n string, res *transfer.Describ conn := acctest.Provider.Meta().(*conns.AWSClient).TransferConn() - userName := rs.Primary.Attributes["user_name"] - serverID := rs.Primary.Attributes["server_id"] + serverID, userName, err := tftransfer.UserParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } - output, err := tftransfer.FindUserByServerIDAndUserName(ctx, conn, serverID, userName) + output, err := tftransfer.FindUserByTwoPartKey(ctx, conn, serverID, userName) if err != nil { return err } - *res = *output + *v = *output return nil } @@ -271,10 +334,13 @@ func testAccCheckUserDestroy(ctx context.Context) resource.TestCheckFunc { continue } - userName := rs.Primary.Attributes["user_name"] - serverID := rs.Primary.Attributes["server_id"] + serverID, userName, err := tftransfer.UserParseResourceID(rs.Primary.ID) - _, err := tftransfer.FindUserByServerIDAndUserName(ctx, conn, serverID, userName) + if err != nil { + return err + } + + _, err = tftransfer.FindUserByTwoPartKey(ctx, conn, serverID, userName) if tfresource.NotFound(err) { continue @@ -289,22 +355,10 @@ func testAccCheckUserDestroy(ctx context.Context) resource.TestCheckFunc { } } -const testAccUserConfig_base = ` -resource "aws_transfer_server" "test" { - identity_provider_type = "SERVICE_MANAGED" - - tags = { - NAME = "tf-acc-test-transfer-server" - } -} - -data "aws_partition" "current" {} -` - -func testAccUserConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccUserConfig_base, fmt.Sprintf(` +func testAccUserConfig_baseRole(rName string) string { + return fmt.Sprintf(` resource "aws_iam_role" "test" { - name = "tf-test-transfer-user-iam-role-%[1]s" + name = %[1]q assume_role_policy = <