diff --git a/aws/provider.go b/aws/provider.go index 34e5c2925841..6d5d20e8ab3c 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -488,6 +488,7 @@ func Provider() *schema.Provider { "aws_datapipeline_pipeline": resourceAwsDataPipelinePipeline(), "aws_datasync_agent": resourceAwsDataSyncAgent(), "aws_datasync_location_efs": resourceAwsDataSyncLocationEfs(), + "aws_datasync_location_fsx_windows_file_system": resourceAwsDataSyncLocationFsxWindowsFileSystem(), "aws_datasync_location_nfs": resourceAwsDataSyncLocationNfs(), "aws_datasync_location_s3": resourceAwsDataSyncLocationS3(), "aws_datasync_location_smb": resourceAwsDataSyncLocationSmb(), diff --git a/aws/resource_aws_datasync_location_fsx_windows_file_system.go b/aws/resource_aws_datasync_location_fsx_windows_file_system.go new file mode 100644 index 000000000000..c3a85982ad24 --- /dev/null +++ b/aws/resource_aws_datasync_location_fsx_windows_file_system.go @@ -0,0 +1,218 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsDataSyncLocationFsxWindowsFileSystem() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDataSyncLocationFsxWindowsFileSystemCreate, + Read: resourceAwsDataSyncLocationFsxWindowsFileSystemRead, + Update: resourceAwsDataSyncLocationFsxWindowsFileSystemUpdate, + Delete: resourceAwsDataSyncLocationFsxWindowsFileSystemDelete, + Importer: &schema.ResourceImporter{ + State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), "#") + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("Unexpected format of ID (%q), expected DataSyncLocationArn#FsxArn", d.Id()) + } + + DSArn := idParts[0] + FSxArn := idParts[1] + + d.Set("fsx_filesystem_arn", FSxArn) + d.SetId(DSArn) + + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "fsx_filesystem_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + "password": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 104), + }, + "user": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 104), + }, + "domain": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 253), + }, + "security_group_arns": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + MinItems: 1, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + }, + "subdirectory": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 4096), + }, + "tags": tagsSchema(), + "uri": { + Type: schema.TypeString, + Computed: true, + }, + "creation_time": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsDataSyncLocationFsxWindowsFileSystemCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + fsxArn := d.Get("fsx_filesystem_arn").(string) + + input := &datasync.CreateLocationFsxWindowsInput{ + FsxFilesystemArn: aws.String(fsxArn), + User: aws.String(d.Get("user").(string)), + Password: aws.String(d.Get("password").(string)), + SecurityGroupArns: expandStringSet(d.Get("security_group_arns").(*schema.Set)), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().DatasyncTags(), + } + + if v, ok := d.GetOk("subdirectory"); ok { + input.Subdirectory = aws.String(v.(string)) + } + + if v, ok := d.GetOk("domain"); ok { + input.Domain = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating DataSync Location Fsx Windows File System: %#v", input) + output, err := conn.CreateLocationFsxWindows(input) + if err != nil { + return fmt.Errorf("error creating DataSync Location Fsx Windows File System: %w", err) + } + + d.SetId(aws.StringValue(output.LocationArn)) + + return resourceAwsDataSyncLocationFsxWindowsFileSystemRead(d, meta) +} + +func resourceAwsDataSyncLocationFsxWindowsFileSystemRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + input := &datasync.DescribeLocationFsxWindowsInput{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading DataSync Location Fsx Windows: %#v", input) + output, err := conn.DescribeLocationFsxWindows(input) + + if isAWSErr(err, datasync.ErrCodeInvalidRequestException, "not found") { + log.Printf("[WARN] DataSync Location Fsx Windows %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading DataSync Location Fsx Windows (%s): %w", d.Id(), err) + } + + subdirectory, err := dataSyncParseLocationURI(aws.StringValue(output.LocationUri)) + + if err != nil { + return fmt.Errorf("error parsing Location Fsx Windows File System (%s) URI (%s): %w", d.Id(), aws.StringValue(output.LocationUri), err) + } + + d.Set("arn", output.LocationArn) + d.Set("subdirectory", subdirectory) + d.Set("uri", output.LocationUri) + d.Set("user", output.User) + d.Set("domain", output.Domain) + + if err := d.Set("security_group_arns", flattenStringSet(output.SecurityGroupArns)); err != nil { + return fmt.Errorf("error setting security_group_arns: %w", err) + } + + if err := d.Set("creation_time", output.CreationTime.Format(time.RFC3339)); err != nil { + return fmt.Errorf("error setting creation_time: %w", err) + } + + tags, err := keyvaluetags.DatasyncListTags(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error listing tags for DataSync Location Fsx Windows (%s): %w", d.Id(), err) + } + + if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + return nil +} + +func resourceAwsDataSyncLocationFsxWindowsFileSystemUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + if d.HasChange("tags") { + o, n := d.GetChange("tags") + + if err := keyvaluetags.DatasyncUpdateTags(conn, d.Id(), o, n); err != nil { + return fmt.Errorf("error updating DataSync Location Fsx Windows File System (%s) tags: %w", d.Id(), err) + } + } + + return resourceAwsDataSyncLocationFsxWindowsFileSystemRead(d, meta) +} + +func resourceAwsDataSyncLocationFsxWindowsFileSystemDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).datasyncconn + + input := &datasync.DeleteLocationInput{ + LocationArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting DataSync Location Fsx Windows File System: %#v", input) + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, datasync.ErrCodeInvalidRequestException, "not found") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting DataSync Location Fsx Windows (%s): %w", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_datasync_location_fsx_windows_file_system_test.go b/aws/resource_aws_datasync_location_fsx_windows_file_system_test.go new file mode 100644 index 000000000000..3c5be0c7d922 --- /dev/null +++ b/aws/resource_aws_datasync_location_fsx_windows_file_system_test.go @@ -0,0 +1,323 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func init() { + resource.AddTestSweepers("aws_datasync_location_fsx_windows_file_system", &resource.Sweeper{ + Name: "aws_datasync_location_fsx_windows_file_system", + F: testSweepDataSyncLocationFsxWindows, + }) +} + +func testSweepDataSyncLocationFsxWindows(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*AWSClient).datasyncconn + + input := &datasync.ListLocationsInput{} + for { + output, err := conn.ListLocations(input) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping DataSync Location FSX Windows sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error retrieving DataSync Location FSX Windows: %w", err) + } + + if len(output.Locations) == 0 { + log.Print("[DEBUG] No DataSync Location FSX Windows File System to sweep") + return nil + } + + for _, location := range output.Locations { + uri := aws.StringValue(location.LocationUri) + if !strings.HasPrefix(uri, "fsxw://") { + log.Printf("[INFO] Skipping DataSync Location FSX Windows File System: %s", uri) + continue + } + log.Printf("[INFO] Deleting DataSync Location FSX Windows File System: %s", uri) + input := &datasync.DeleteLocationInput{ + LocationArn: location.LocationArn, + } + + _, err := conn.DeleteLocation(input) + + if isAWSErr(err, datasync.ErrCodeInvalidRequestException, "not found") { + continue + } + + if err != nil { + log.Printf("[ERROR] Failed to delete DataSync Location FSX Windows (%s): %s", uri, err) + } + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + return nil +} + +func TestAccAWSDataSyncLocationFsxWindows_basic(t *testing.T) { + var locationFsxWindows1 datasync.DescribeLocationFsxWindowsOutput + resourceName := "aws_datasync_location_fsx_windows_file_system.test" + fsResourceName := "aws_fsx_windows_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDataSync(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationFsxWindowsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationFsxWindowsConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationFsxWindowsExists(resourceName, &locationFsxWindows1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "datasync", regexp.MustCompile(`location/loc-.+`)), + resource.TestCheckResourceAttrPair(resourceName, "fsx_filesystem_arn", fsResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "subdirectory", "/"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestMatchResourceAttr(resourceName, "uri", regexp.MustCompile(`^fsxw://.+/`)), + resource.TestCheckResourceAttrSet(resourceName, "creation_time"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWSDataSyncLocationFsxWindowsImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationFsxWindows_disappears(t *testing.T) { + var locationFsxWindows1 datasync.DescribeLocationFsxWindowsOutput + resourceName := "aws_datasync_location_fsx_windows_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDataSync(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationFsxWindowsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationFsxWindowsConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationFsxWindowsExists(resourceName, &locationFsxWindows1), + testAccCheckResourceDisappears(testAccProvider, resourceAwsDataSyncLocationFsxWindowsFileSystem(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationFsxWindows_subdirectory(t *testing.T) { + var locationFsxWindows1 datasync.DescribeLocationFsxWindowsOutput + resourceName := "aws_datasync_location_fsx_windows_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDataSync(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationFsxWindowsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationFsxWindowsConfigSubdirectory("/subdirectory1/"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationFsxWindowsExists(resourceName, &locationFsxWindows1), + resource.TestCheckResourceAttr(resourceName, "subdirectory", "/subdirectory1/"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWSDataSyncLocationFsxWindowsImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} + +func TestAccAWSDataSyncLocationFsxWindows_tags(t *testing.T) { + var locationFsxWindows1 datasync.DescribeLocationFsxWindowsOutput + resourceName := "aws_datasync_location_fsx_windows_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSDataSync(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDataSyncLocationFsxWindowsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSyncLocationFsxWindowsConfigTags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationFsxWindowsExists(resourceName, &locationFsxWindows1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWSDataSyncLocationFsxWindowsImportStateIdFunc(resourceName), + ImportStateVerifyIgnore: []string{"password"}, + }, + { + Config: testAccAWSDataSyncLocationFsxWindowsConfigTags2("key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationFsxWindowsExists(resourceName, &locationFsxWindows1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSDataSyncLocationFsxWindowsConfigTags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDataSyncLocationFsxWindowsExists(resourceName, &locationFsxWindows1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + +func testAccCheckAWSDataSyncLocationFsxWindowsDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_datasync_location_fsx_windows_file_system" { + continue + } + + input := &datasync.DescribeLocationFsxWindowsInput{ + LocationArn: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeLocationFsxWindows(input) + + if isAWSErr(err, datasync.ErrCodeInvalidRequestException, "not found") { + return nil + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccCheckAWSDataSyncLocationFsxWindowsExists(resourceName string, locationFsxWindows *datasync.DescribeLocationFsxWindowsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).datasyncconn + input := &datasync.DescribeLocationFsxWindowsInput{ + LocationArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeLocationFsxWindows(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Location %q does not exist", rs.Primary.ID) + } + + *locationFsxWindows = *output + + return nil + } +} + +func testAccWSDataSyncLocationFsxWindowsImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return fmt.Sprintf("%s#%s", rs.Primary.ID, rs.Primary.Attributes["fsx_filesystem_arn"]), nil + } +} + +func testAccAWSDataSyncLocationFsxWindowsConfig() string { + return testAccAwsFsxWindowsFileSystemConfigSecurityGroupIds1() + fmt.Sprintf(` +resource "aws_datasync_location_fsx_windows_file_system" "test" { + fsx_filesystem_arn = aws_fsx_windows_file_system.test.arn + user = "SomeUser" + password = "SuperSecretPassw0rd" + security_group_arns = [aws_security_group.test1.arn] +} +`) +} + +func testAccAWSDataSyncLocationFsxWindowsConfigSubdirectory(subdirectory string) string { + return testAccAwsFsxWindowsFileSystemConfigSecurityGroupIds1() + fmt.Sprintf(` +resource "aws_datasync_location_fsx_windows_file_system" "test" { + fsx_filesystem_arn = aws_fsx_windows_file_system.test.arn + user = "SomeUser" + password = "SuperSecretPassw0rd" + security_group_arns = [aws_security_group.test1.arn] + subdirectory = %[1]q +} +`, subdirectory) +} + +func testAccAWSDataSyncLocationFsxWindowsConfigTags1(key1, value1 string) string { + return testAccAwsFsxWindowsFileSystemConfigSecurityGroupIds1() + fmt.Sprintf(` +resource "aws_datasync_location_fsx_windows_file_system" "test" { + fsx_filesystem_arn = aws_fsx_windows_file_system.test.arn + user = "SomeUser" + password = "SuperSecretPassw0rd" + security_group_arns = [aws_security_group.test1.arn] + + tags = { + %[1]q = %[2]q + } +} +`, key1, value1) +} + +func testAccAWSDataSyncLocationFsxWindowsConfigTags2(key1, value1, key2, value2 string) string { + return testAccAwsFsxWindowsFileSystemConfigSecurityGroupIds1() + fmt.Sprintf(` +resource "aws_datasync_location_fsx_windows_file_system" "test" { + fsx_filesystem_arn = aws_fsx_windows_file_system.test.arn + user = "SomeUser" + password = "SuperSecretPassw0rd" + security_group_arns = [aws_security_group.test1.arn] + + tags = { + %[1]q = %[2]q + %[3]q = %[4]q + } +} +`, key1, value1, key2, value2) +} diff --git a/website/docs/r/datasync_location_fsx_windows_file_system.html.markdown b/website/docs/r/datasync_location_fsx_windows_file_system.html.markdown new file mode 100644 index 000000000000..3d7b3a581a5e --- /dev/null +++ b/website/docs/r/datasync_location_fsx_windows_file_system.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "DataSync" +layout: "aws" +page_title: "AWS: aws_datasync_location_fsx_windows_file_system" +description: |- + Manages an FSx Windows Location within AWS DataSync. +--- + +# Resource: aws_datasync_location_fsx_windows_file_system + +Manages an AWS DataSync FSx Windows Location. + +## Example Usage + +```hcl +resource "aws_datasync_location_fsx_windows_file_system" "example" { + fsx_filesystem_arn = aws_fsx_windows_file_system.example.arn + user = "SomeUser" + password = "SuperSecretPassw0rd" + security_group_arns = [aws_security_group.example.arn] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `fsx_filesystem_arn` - (Required) The Amazon Resource Name (ARN) for the FSx for Windows file system. +* `password` - (Required) The password of the user who has the permissions to access files and folders in the FSx for Windows file system. +* `user` - (Required) The user who has the permissions to access files and folders in the FSx for Windows file system. +* `domain` - (Optional) The name of the Windows domain that the FSx for Windows server belongs to. +* `security_group_arns` - (Optional) The Amazon Resource Names (ARNs) of the security groups that are to use to configure the FSx for Windows file system. +* `subdirectory` - (Optional) Subdirectory to perform actions as source or destination. +* `tags` - (Optional) Key-value pairs of resource tags to assign to the DataSync Location. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Amazon Resource Name (ARN) of the DataSync Location. +* `arn` - Amazon Resource Name (ARN) of the DataSync Location. +* `uri` - The URL of the FSx for Windows location that was described. +* `creation_time` - The time that the FSx for Windows location was created. + +## Import + +`aws_datasync_location_fsx_windows_file_system` can be imported by using the `DataSync-ARN#FSx-Windows-ARN`, e.g. + +``` +$ terraform import aws_datasync_location_fsx_windows_file_system.example arn:aws:datasync:us-west-2:123456789012:location/loc-12345678901234567#arn:aws:fsx:us-west-2:476956259333:file-system/fs-08e04cd442c1bb94a +```