From d1107e337ad83a781f3b961935d51fd91b7c992f Mon Sep 17 00:00:00 2001 From: "Aaron J. Smith" Date: Wed, 9 Jan 2019 11:45:10 -0600 Subject: [PATCH 1/8] r/aws_fsx_file_system: Adding support and resource for managing FSx filesystems --- aws/config.go | 3 + aws/provider.go | 1 + aws/resource_aws_fsx_file_system.go | 448 +++++++++++++++++++ aws/resource_aws_fsx_file_system_test.go | 365 +++++++++++++++ aws/tagsFSX.go | 117 +++++ website/aws.erb | 11 + website/docs/r/fsx_file_system.html.markdown | 91 ++++ 7 files changed, 1036 insertions(+) create mode 100644 aws/resource_aws_fsx_file_system.go create mode 100644 aws/resource_aws_fsx_file_system_test.go create mode 100644 aws/tagsFSX.go create mode 100644 website/docs/r/fsx_file_system.html.markdown diff --git a/aws/config.go b/aws/config.go index 03a1b69d2b6..e249576ec2d 100644 --- a/aws/config.go +++ b/aws/config.go @@ -62,6 +62,7 @@ import ( "github.com/aws/aws-sdk-go/service/emr" "github.com/aws/aws-sdk-go/service/firehose" "github.com/aws/aws-sdk-go/service/fms" + "github.com/aws/aws-sdk-go/service/fsx" "github.com/aws/aws-sdk-go/service/gamelift" "github.com/aws/aws-sdk-go/service/glacier" "github.com/aws/aws-sdk-go/service/glue" @@ -263,6 +264,7 @@ type AWSClient struct { workspacesconn *workspaces.WorkSpaces appmeshconn *appmesh.AppMesh transferconn *transfer.Transfer + fsxconn *fsx.FSx } func (c *AWSClient) S3() *s3.S3 { @@ -552,6 +554,7 @@ func (c *Config) Client() (interface{}, error) { client.esconn = elasticsearch.New(awsEsSess) client.firehoseconn = firehose.New(sess) client.fmsconn = fms.New(sess) + client.fsxconn = fsx.New(sess) client.inspectorconn = inspector.New(sess) client.gameliftconn = gamelift.New(sess) client.glacierconn = glacier.New(sess) diff --git a/aws/provider.go b/aws/provider.go index 074aa98995a..45e57be6e1d 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -463,6 +463,7 @@ func Provider() terraform.ResourceProvider { "aws_emr_instance_group": resourceAwsEMRInstanceGroup(), "aws_emr_security_configuration": resourceAwsEMRSecurityConfiguration(), "aws_flow_log": resourceAwsFlowLog(), + "aws_fsx_file_system": resourceAwsFsxFileSystem(), "aws_gamelift_alias": resourceAwsGameliftAlias(), "aws_gamelift_build": resourceAwsGameliftBuild(), "aws_gamelift_fleet": resourceAwsGameliftFleet(), diff --git a/aws/resource_aws_fsx_file_system.go b/aws/resource_aws_fsx_file_system.go new file mode 100644 index 00000000000..91413b7091b --- /dev/null +++ b/aws/resource_aws_fsx_file_system.go @@ -0,0 +1,448 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/fsx" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsFsxFileSystem() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsFsxFileSystemCreate, + Read: resourceAwsFsxFileSystemRead, + Update: resourceAwsFsxFileSystemUpdate, + Delete: resourceAwsFsxFileSystemDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"LUSTRE", "WINDOWS"}, false), + }, + "capacity": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(300), + }, + "kms_key_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subnet_ids": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "security_group_ids": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "lustre_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{"windows_configuration"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "import_path": { + Type: schema.TypeString, + Required: true, + }, + "chunk_size": { + Type: schema.TypeInt, + Optional: true, + Default: 1024, + }, + "weekly_maintenance_start_time": { + Type: schema.TypeString, + Optional: true, + Default: "3:03:30", + }, + }, + }, + }, + "windows_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{"lustre_configuration"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "active_directory_id": { + Type: schema.TypeString, + Required: true, + }, + "backup_retention": { + Type: schema.TypeInt, + Optional: true, + Default: 7, + }, + "copy_tags_to_backups": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "daily_backup_start_time": { + Type: schema.TypeString, + Optional: true, + Default: "06:00", + }, + "throughput_capacity": { + Type: schema.TypeInt, + Required: true, + }, + "weekly_maintenance_start_time": { + Type: schema.TypeString, + Optional: true, + Default: "3:03:30", + }, + }, + }, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "dns_name": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), + }, + } +} + +func resourceAwsFsxFileSystemCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).fsxconn + + request := &fsx.CreateFileSystemInput{ + FileSystemType: aws.String(d.Get("type").(string)), + StorageCapacity: aws.Int64(int64(d.Get("capacity").(int))), + SubnetIds: expandStringList(d.Get("subnet_ids").([]interface{})), + KmsKeyId: aws.String(d.Get("kms_key_id").(string)), + } + + if value, ok := d.GetOk("security_group_ids"); ok { + request.SecurityGroupIds = expandStringList(value.([]interface{})) + } + + if _, ok := d.GetOk("lustre_configuration"); ok { + request.LustreConfiguration = expandFsxLustreConfigurationCreate(d.Get("lustre_configuration").([]interface{})) + } + + if _, ok := d.GetOk("windows_configuration"); ok { + request.WindowsConfiguration = expandFsxWindowsConfigurationCreate(d.Get("windows_configuration").([]interface{})) + } + + if value, ok := d.GetOk("tags"); ok { + request.Tags = tagsFromMapFSX(value.(map[string]interface{})) + } + + log.Printf("[DEBUG] FSx Filesystem create opts: %s", request) + result, err := conn.CreateFileSystem(request) + if err != nil { + return fmt.Errorf("Error creating FSx filesystem: %s", err) + } + + log.Println("[DEBUG] Waiting for filesystem to become available") + + stateConf := &resource.StateChangeConf{ + Pending: []string{"CREATING"}, + Target: []string{"AVAILABLE"}, + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeFileSystems(&fsx.DescribeFileSystemsInput{ + FileSystemIds: []*string{aws.String(*result.FileSystem.FileSystemId)}, + }) + + if err != nil { + if ec2err, ok := err.(awserr.Error); ok { + log.Printf("Error on FSx State Refresh: message: \"%s\", code:\"%s\"", ec2err.Message(), ec2err.Code()) + resp = nil + return nil, "", err + } else { + log.Printf("Error on FSx State Refresh: %s", err) + return nil, "", err + } + } + + v := resp.FileSystems[0] + return v, *v.Lifecycle, nil + }, + Timeout: 60 * time.Minute, + Delay: 30 * time.Second, + MinTimeout: 15 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for filesystem (%s) to become available: %s", + *result.FileSystem.FileSystemId, err) + } + + d.SetId(*result.FileSystem.FileSystemId) + + return resourceAwsFsxFileSystemRead(d, meta) +} + +func resourceAwsFsxFileSystemUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).fsxconn + + if _, ok := d.GetOk("tags"); ok { + if err := setTagsFSX(conn, d); err != nil { + return fmt.Errorf("Error updating tags for FSx filesystem: %s", err) + } + } + + requestUpdate := false + params := &fsx.UpdateFileSystemInput{ + FileSystemId: aws.String(d.Id()), + } + + if d.HasChange("lustre_configuration") { + params.LustreConfiguration = expandFsxLustreConfigurationUpdate(d.Get("lustre_configuration").([]interface{})) + requestUpdate = true + } + + if d.HasChange("windows_configuration") { + params.WindowsConfiguration = expandFsxWindowsConfigurationUpdate(d.Get("windows_configuration").([]interface{})) + requestUpdate = true + } + + if requestUpdate { + _, err := conn.UpdateFileSystem(params) + if err != nil { + return err + } + } + + return resourceAwsFsxFileSystemRead(d, meta) +} + +func resourceAwsFsxFileSystemRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).fsxconn + + request := &fsx.DescribeFileSystemsInput{ + FileSystemIds: []*string{aws.String(d.Id())}, + } + + response, err := conn.DescribeFileSystems(request) + if err != nil { + return fmt.Errorf("Error reading FSx filesystem %s: %s", d.Id(), err) + } + + d.Set("type", *response.FileSystems[0].FileSystemType) + d.Set("capacity", *response.FileSystems[0].StorageCapacity) + d.Set("arn", *response.FileSystems[0].ResourceARN) + d.Set("dns_name", *response.FileSystems[0].DNSName) + + d.Set("tags", tagsToMapFSX(response.FileSystems[0].Tags)) + + subnets := make([]string, 0) + for _, subnet := range response.FileSystems[0].SubnetIds { + subnets = append(subnets, aws.StringValue(subnet)) + } + + d.Set("subnet_ids", subnets) + + if response.FileSystems[0].KmsKeyId != nil { + d.Set("kms_key_id", *response.FileSystems[0].KmsKeyId) + } + + if _, ok := d.GetOk("lustre_configuration"); ok { + err = d.Set("lustre_configuration", flattenLustreOptsConfig(response.FileSystems[0].LustreConfiguration)) + if err != nil { + return err + } + } + + if _, ok := d.GetOk("windows_configuration"); ok { + err = d.Set("windows_configuration", flattenWindowsOptsConfig(response.FileSystems[0].WindowsConfiguration)) + if err != nil { + return err + } + } + + return nil +} + +func resourceAwsFsxFileSystemDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).fsxconn + + request := &fsx.DeleteFileSystemInput{ + FileSystemId: aws.String(d.Id()), + } + + _, err := conn.DeleteFileSystem(request) + if err != nil { + return fmt.Errorf("Error deleting FSx filesystem: %s", err) + } + + log.Println("[DEBUG] Waiting for filesystem to delete") + + stateConf := &resource.StateChangeConf{ + Pending: []string{"AVAILABLE", "DELETING"}, + Target: []string{}, + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeFileSystems(&fsx.DescribeFileSystemsInput{ + FileSystemIds: []*string{aws.String(d.Id())}, + }) + if err != nil { + efsErr, ok := err.(awserr.Error) + if ok && efsErr.Code() == "FileSystemNotFound" { + return nil, "", nil + } + return nil, "error", err + } + + if hasEmptyFsxFileSystems(resp) { + return nil, "", nil + } + + v := resp.FileSystems[0] + log.Printf("[DEBUG] current status of %q: %q", *v.FileSystemId, *v.Lifecycle) + return v, *v.Lifecycle, nil + }, + Timeout: 60 * time.Minute, + Delay: 30 * time.Second, + MinTimeout: 15 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for filesystem (%s) to delete: %s", d.Id(), err) + } + + return nil +} + +func hasEmptyFsxFileSystems(fs *fsx.DescribeFileSystemsOutput) bool { + if fs != nil && len(fs.FileSystems) > 0 { + return false + } + return true +} + +func expandFsxLustreConfigurationCreate(l []interface{}) *fsx.CreateFileSystemLustreConfiguration { + data := l[0].(map[string]interface{}) + req := &fsx.CreateFileSystemLustreConfiguration{} + + if data["import_path"].(string) != "" { + req.ImportPath = aws.String(data["import_path"].(string)) + } + + if data["chunk_size"] != nil { + req.ImportedFileChunkSize = aws.Int64(int64(data["chunk_size"].(int))) + } + + if data["weekly_maintenance_start_time"].(string) != "" { + req.WeeklyMaintenanceStartTime = aws.String(data["weekly_maintenance_start_time"].(string)) + } + + return req +} + +func expandFsxLustreConfigurationUpdate(l []interface{}) *fsx.UpdateFileSystemLustreConfiguration { + data := l[0].(map[string]interface{}) + req := &fsx.UpdateFileSystemLustreConfiguration{} + + if data["weekly_maintenance_start_time"].(string) != "" { + req.WeeklyMaintenanceStartTime = aws.String(data["weekly_maintenance_start_time"].(string)) + } + + return req +} + +func expandFsxWindowsConfigurationCreate(l []interface{}) *fsx.CreateFileSystemWindowsConfiguration { + data := l[0].(map[string]interface{}) + req := &fsx.CreateFileSystemWindowsConfiguration{ + ThroughputCapacity: aws.Int64(int64(data["throughput_capacity"].(int))), + } + + if data["active_directory_id"].(string) != "" { + req.ActiveDirectoryId = aws.String(data["active_directory_id"].(string)) + } + + if data["backup_retention"] != nil { + req.AutomaticBackupRetentionDays = aws.Int64(int64(data["backup_retention"].(int))) + } + + if data["copy_tags_to_backups"] != nil { + req.CopyTagsToBackups = aws.Bool(data["copy_tags_to_backups"].(bool)) + } + + if data["daily_backup_start_time"].(string) != "" { + req.DailyAutomaticBackupStartTime = aws.String(data["daily_backup_start_time"].(string)) + } + + if data["weekly_maintenance_start_time"].(string) != "" { + req.WeeklyMaintenanceStartTime = aws.String(data["weekly_maintenance_start_time"].(string)) + } + + return req +} + +func expandFsxWindowsConfigurationUpdate(l []interface{}) *fsx.UpdateFileSystemWindowsConfiguration { + data := l[0].(map[string]interface{}) + req := &fsx.UpdateFileSystemWindowsConfiguration{} + + if data["backup_retention"] != nil { + req.AutomaticBackupRetentionDays = aws.Int64(int64(data["backup_retention"].(int))) + } + + if data["daily_backup_start_time"].(string) != "" { + req.DailyAutomaticBackupStartTime = aws.String(data["daily_backup_start_time"].(string)) + } + + if data["weekly_maintenance_start_time"].(string) != "" { + req.WeeklyMaintenanceStartTime = aws.String(data["weekly_maintenance_start_time"].(string)) + } + + return req +} + +func flattenLustreOptsConfig(lopts *fsx.LustreFileSystemConfiguration) []map[string]interface{} { + if lopts == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "import_path": aws.StringValue(lopts.DataRepositoryConfiguration.ImportPath), + "chunk_size": aws.Int64Value(lopts.DataRepositoryConfiguration.ImportedFileChunkSize), + "weekly_maintenance_start_time": aws.StringValue(lopts.WeeklyMaintenanceStartTime), + } + + return []map[string]interface{}{m} +} + +func flattenWindowsOptsConfig(wopts *fsx.WindowsFileSystemConfiguration) []map[string]interface{} { + if wopts == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "active_directory_id": aws.StringValue(wopts.ActiveDirectoryId), + "backup_retention": aws.Int64Value(wopts.AutomaticBackupRetentionDays), + "copy_tags_to_backups": aws.BoolValue(wopts.CopyTagsToBackups), + "daily_backup_start_time": aws.StringValue(wopts.DailyAutomaticBackupStartTime), + "throughput_capacity": aws.Int64Value(wopts.ThroughputCapacity), + "weekly_maintenance_start_time": aws.StringValue(wopts.WeeklyMaintenanceStartTime), + } + + return []map[string]interface{}{m} +} diff --git a/aws/resource_aws_fsx_file_system_test.go b/aws/resource_aws_fsx_file_system_test.go new file mode 100644 index 00000000000..e5fcb44c6bb --- /dev/null +++ b/aws/resource_aws_fsx_file_system_test.go @@ -0,0 +1,365 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/fsx" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSFsxFileSystem_lustreBasic(t *testing.T) { + var v fsx.FileSystem + resourceName := "aws_fsx_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsFsxFileSystemLustreBasic, + Check: resource.ComposeTestCheckFunc( + testAccCheckFileSystemExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "type", "LUSTRE"), + resource.TestCheckResourceAttr(resourceName, "capacity", "3600"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"kms_key_id"}, + }, + }, + }) +} + +func TestAccAWSFsxFileSystem_lustreConfig(t *testing.T) { + var v fsx.FileSystem + resourceName := "aws_fsx_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsFsxFileSystemLustreConfigOpts, + Check: resource.ComposeTestCheckFunc( + testAccCheckFileSystemExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "lustre_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "lustre_configuration.0.import_path", "s3://nasanex"), + resource.TestCheckResourceAttr(resourceName, "lustre_configuration.0.chunk_size", "2048"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"kms_key_id"}, + }, + }, + }) +} + +func TestAccAWSFsxFileSystem_lustreUpdate(t *testing.T) { + var v fsx.FileSystem + resourceName := "aws_fsx_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsFsxFileSystemLustreConfigOpts, + Check: resource.ComposeTestCheckFunc( + testAccCheckFileSystemExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "lustre_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "lustre_configuration.0.weekly_maintenance_start_time", "3:03:30"), + ), + }, + { + Config: testAccAwsFsxFileSystemLustreUpdateOpts, + Check: resource.ComposeTestCheckFunc( + testAccCheckFileSystemExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "lustre_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "lustre_configuration.0.weekly_maintenance_start_time", "5:05:50"), + ), + }, + }, + }) +} + +func TestAccAWSFsxFileSystem_windowsConfig(t *testing.T) { + var v fsx.FileSystem + resourceName := "aws_fsx_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsFsxFileSystemWindowsConfigOpts, + Check: resource.ComposeTestCheckFunc( + testAccCheckFileSystemExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.0.backup_retention", "3"), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.0.copy_tags_to_backups", "true"), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.0.throughput_capacity", "1024"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"kms_key_id"}, + }, + }, + }) +} + +func TestAccAWSFsxFileSystem_windowsUpdate(t *testing.T) { + var v fsx.FileSystem + resourceName := "aws_fsx_file_system.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAwsFsxFileSystemWindowsConfigOpts, + Check: resource.ComposeTestCheckFunc( + testAccCheckFileSystemExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.0.backup_retention", "3"), + ), + }, + { + Config: testAccAwsFsxFileSystemWindowsUpdateOpts, + Check: resource.ComposeTestCheckFunc( + testAccCheckFileSystemExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "windows_configuration.0.backup_retention", "30"), + ), + }, + }, + }) +} + +func testAccCheckFileSystemExists(n string, v *fsx.FileSystem) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).fsxconn + + request := &fsx.DescribeFileSystemsInput{ + FileSystemIds: []*string{aws.String(rs.Primary.ID)}, + } + + response, err := conn.DescribeFileSystems(request) + if err == nil { + if response.FileSystems != nil && len(response.FileSystems) > 0 { + *v = *response.FileSystems[0] + return nil + } + } + return fmt.Errorf("Error finding FSx filesystem %s", rs.Primary.ID) + } +} + +const testAccAwsFsxFileSystemLustreBasic = ` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "test" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "us-east-1a" +} + +resource "aws_kms_key" "test" { + description = "FSx KMS Testing key" + deletion_window_in_days = 7 +} + +resource "aws_fsx_file_system" "test" { + depends_on = ["aws_subnet.test", "aws_kms_key.test"] + type = "LUSTRE" + capacity = 3600 + kms_key_id = "${aws_kms_key.test.key_id}" + subnet_ids = ["${aws_subnet.test.id}"] +} +` + +const testAccAwsFsxFileSystemLustreConfigOpts = ` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "test" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "us-east-1a" +} + +resource "aws_kms_key" "test" { + description = "FSx KMS Testing key" + deletion_window_in_days = 7 +} + +resource "aws_fsx_file_system" "test" { + depends_on = ["aws_subnet.test", "aws_kms_key.test"] + type = "LUSTRE" + capacity = 3600 + kms_key_id = "${aws_kms_key.test.key_id}" + subnet_ids = ["${aws_subnet.test.id}"] + + lustre_configuration { + import_path = "s3://nasanex" + chunk_size = 2048 + } +} +` + +const testAccAwsFsxFileSystemLustreUpdateOpts = ` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "test" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "us-east-1a" +} + +resource "aws_kms_key" "test" { + description = "FSx KMS Testing key" + deletion_window_in_days = 7 +} + +resource "aws_fsx_file_system" "test" { + depends_on = ["aws_subnet.test", "aws_kms_key.test"] + type = "LUSTRE" + capacity = 3600 + kms_key_id = "${aws_kms_key.test.key_id}" + subnet_ids = ["${aws_subnet.test.id}"] + + lustre_configuration { + import_path = "s3://nasanex" + chunk_size = 2048 + weekly_maintenance_start_time = "5:05:50" + } +} +` + +const testAccAwsFsxFileSystemWindowsConfigOpts = ` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "test1" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "us-east-1a" +} + +resource "aws_subnet" "test2" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.2.0/24" + availability_zone = "us-east-1d" +} + +resource "aws_kms_key" "test" { + description = "FSx KMS Testing key" + deletion_window_in_days = 7 +} + +resource "aws_directory_service_directory" "test" { + name = "corp.notexample.com" + password = "SuperSecretPassw0rd" + type = "MicrosoftAD" + + vpc_settings { + vpc_id = "${aws_vpc.test.id}" + subnet_ids = ["${aws_subnet.test1.id}", "${aws_subnet.test2.id}"] + } +} + +resource "aws_fsx_file_system" "test" { + depends_on = ["aws_subnet.test1", "aws_kms_key.test", "aws_directory_service_directory.test"] + type = "WINDOWS" + capacity = 300 + kms_key_id = "${aws_kms_key.test.arn}" + subnet_ids = ["${aws_subnet.test1.id}"] + + windows_configuration { + active_directory_id = "${aws_directory_service_directory.test.id}" + backup_retention = 3 + copy_tags_to_backups = true + throughput_capacity = 1024 + } +} +` + +const testAccAwsFsxFileSystemWindowsUpdateOpts = ` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "test1" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "us-east-1a" +} + +resource "aws_subnet" "test2" { + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.2.0/24" + availability_zone = "us-east-1d" +} + +resource "aws_kms_key" "test" { + description = "FSx KMS Testing key" + deletion_window_in_days = 7 +} + +resource "aws_directory_service_directory" "test" { + name = "corp.notexample.com" + password = "SuperSecretPassw0rd" + type = "MicrosoftAD" + + vpc_settings { + vpc_id = "${aws_vpc.test.id}" + subnet_ids = ["${aws_subnet.test1.id}", "${aws_subnet.test2.id}"] + } +} + +resource "aws_fsx_file_system" "test" { + depends_on = ["aws_subnet.test1", "aws_kms_key.test", "aws_directory_service_directory.test"] + type = "WINDOWS" + capacity = 300 + kms_key_id = "${aws_kms_key.test.arn}" + subnet_ids = ["${aws_subnet.test1.id}"] + + windows_configuration { + active_directory_id = "${aws_directory_service_directory.test.id}" + backup_retention = 30 + copy_tags_to_backups = true + throughput_capacity = 1024 + } +} +` diff --git a/aws/tagsFSX.go b/aws/tagsFSX.go new file mode 100644 index 00000000000..f2eed55a87b --- /dev/null +++ b/aws/tagsFSX.go @@ -0,0 +1,117 @@ +package aws + +import ( + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/fsx" + "github.com/hashicorp/terraform/helper/schema" +) + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags". It also expects to take the resource +// ARN as the primary ID based on the requirements of the FSx API (as +// opposed to the resource ID like other tagging helpers). +func setTagsFSX(conn *fsx.FSx, d *schema.ResourceData) error { + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTagsFSX(tagsFromMapFSX(o), tagsFromMapFSX(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %#v", remove) + k := make([]*string, 0, len(remove)) + for _, t := range remove { + k = append(k, t.Key) + } + _, err := conn.UntagResource(&fsx.UntagResourceInput{ + ResourceARN: aws.String(d.Get("arn").(string)), + TagKeys: k, + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + _, err := conn.TagResource(&fsx.TagResourceInput{ + ResourceARN: aws.String(d.Get("arn").(string)), + Tags: create, + }) + if err != nil { + return err + } + } + } + + return nil +} + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffTagsFSX(oldTags, newTags []*fsx.Tag) ([]*fsx.Tag, []*fsx.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[*t.Key] = *t.Value + } + + // Build the list of what to remove + var remove []*fsx.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapFSX(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapFSX(m map[string]interface{}) []*fsx.Tag { + var result []*fsx.Tag + for k, v := range m { + t := &fsx.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + } + if !tagIgnoredFSX(t) { + result = append(result, t) + } + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapFSX(ts []*fsx.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + if !tagIgnoredFSX(t) { + result[*t.Key] = *t.Value + } + } + + return result +} + +// compare a tag against a list of strings and checks if it should +// be ignored or not +func tagIgnoredFSX(t *fsx.Tag) bool { + filter := []string{"^aws:"} + for _, v := range filter { + log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) + r, _ := regexp.MatchString(v, *t.Key) + if r { + log.Printf("[DEBUG] Found AWS specific tag %s (val: %s), ignoring.\n", *t.Key, *t.Value) + return true + } + } + return false +} diff --git a/website/aws.erb b/website/aws.erb index d16a7f690d7..a1291ddd53a 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1366,6 +1366,17 @@ + > + FSx Resources + + + > Gamelift Resources
  • - FSx Resources -