diff --git a/builtin/providers/aws/import_aws_efs_file_system_test.go b/builtin/providers/aws/import_aws_efs_file_system_test.go index b40e7f4965..46e0de4595 100644 --- a/builtin/providers/aws/import_aws_efs_file_system_test.go +++ b/builtin/providers/aws/import_aws_efs_file_system_test.go @@ -22,7 +22,7 @@ func TestAccAWSEFSFileSystem_importBasic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"reference_name"}, + ImportStateVerifyIgnore: []string{"reference_name", "creation_token"}, }, }, }) diff --git a/builtin/providers/aws/resource_aws_efs_file_system.go b/builtin/providers/aws/resource_aws_efs_file_system.go index 28f4517569..d0524aef3c 100644 --- a/builtin/providers/aws/resource_aws_efs_file_system.go +++ b/builtin/providers/aws/resource_aws_efs_file_system.go @@ -24,10 +24,27 @@ func resourceAwsEfsFileSystem() *schema.Resource { }, Schema: map[string]*schema.Schema{ + "creation_token": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateMaxLength(64), + }, + "reference_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Deprecated: "Please use attribute `creation_token' instead. This attribute might be removed in future releases.", + ConflictsWith: []string{"creation_token"}, + ValidateFunc: validateReferenceName, + }, + + "performance_mode": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validatePerformanceModeType, }, "tags": tagsSchema(), @@ -38,20 +55,34 @@ func resourceAwsEfsFileSystem() *schema.Resource { func resourceAwsEfsFileSystemCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).efsconn - referenceName := "" - if v, ok := d.GetOk("reference_name"); ok { - referenceName = v.(string) + "-" + creationToken := "" + if v, ok := d.GetOk("creation_token"); ok { + creationToken = v.(string) + } else { + if v, ok := d.GetOk("reference_name"); ok { + creationToken = resource.PrefixedUniqueId(fmt.Sprintf("%s-", v.(string))) + log.Printf("[WARN] Using deprecated `reference_name' attribute.") + } else { + creationToken = resource.UniqueId() + } } - token := referenceName + resource.UniqueId() - fs, err := conn.CreateFileSystem(&efs.CreateFileSystemInput{ - CreationToken: aws.String(token), - }) + + createOpts := &efs.CreateFileSystemInput{ + CreationToken: aws.String(creationToken), + } + + if v, ok := d.GetOk("performance_mode"); ok { + createOpts.PerformanceMode = aws.String(v.(string)) + } + + log.Printf("[DEBUG] EFS file system create options: %#v", *createOpts) + fs, err := conn.CreateFileSystem(createOpts) if err != nil { - return err + return fmt.Errorf("Error creating EFS file system: %s", err) } - log.Printf("[DEBUG] Creating EFS file system: %s", *fs) d.SetId(*fs.FileSystemId) + log.Printf("[INFO] EFS file system ID: %s", d.Id()) stateConf := &resource.StateChangeConf{ Pending: []string{"creating"}, @@ -82,7 +113,7 @@ func resourceAwsEfsFileSystemCreate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error waiting for EFS file system (%q) to create: %q", d.Id(), err.Error()) } - log.Printf("[DEBUG] EFS file system created: %q", *fs.FileSystemId) + log.Printf("[DEBUG] EFS file system %q created.", d.Id()) return resourceAwsEfsFileSystemUpdate(d, meta) } @@ -91,7 +122,8 @@ func resourceAwsEfsFileSystemUpdate(d *schema.ResourceData, meta interface{}) er conn := meta.(*AWSClient).efsconn err := setTagsEFS(conn, d) if err != nil { - return err + return fmt.Errorf("Error setting EC2 tags for EFS file system (%q): %q", + d.Id(), err.Error()) } return resourceAwsEfsFileSystemRead(d, meta) @@ -119,7 +151,8 @@ func resourceAwsEfsFileSystemRead(d *schema.ResourceData, meta interface{}) erro FileSystemId: aws.String(d.Id()), }) if err != nil { - return err + return fmt.Errorf("Error retrieving EC2 tags for EFS file system (%q): %q", + d.Id(), err.Error()) } d.Set("tags", tagsToMapEFS(tagsResp.Tags)) @@ -130,7 +163,7 @@ func resourceAwsEfsFileSystemRead(d *schema.ResourceData, meta interface{}) erro func resourceAwsEfsFileSystemDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).efsconn - log.Printf("[DEBUG] Deleting EFS file system %s", d.Id()) + log.Printf("[DEBUG] Deleting EFS file system: %s", d.Id()) _, err := conn.DeleteFileSystem(&efs.DeleteFileSystemInput{ FileSystemId: aws.String(d.Id()), }) @@ -154,8 +187,7 @@ func resourceAwsEfsFileSystemDelete(d *schema.ResourceData, meta interface{}) er } fs := resp.FileSystems[0] - log.Printf("[DEBUG] current status of %q: %q", - *fs.FileSystemId, *fs.LifeCycleState) + log.Printf("[DEBUG] current status of %q: %q", *fs.FileSystemId, *fs.LifeCycleState) return fs, *fs.LifeCycleState, nil }, Timeout: 10 * time.Minute, @@ -165,10 +197,31 @@ func resourceAwsEfsFileSystemDelete(d *schema.ResourceData, meta interface{}) er _, err = stateConf.WaitForState() if err != nil { - return err + return fmt.Errorf("Error waiting for EFS file system (%q) to delete: %q", + d.Id(), err.Error()) } log.Printf("[DEBUG] EFS file system %q deleted.", d.Id()) return nil } + +func validateReferenceName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + creationToken := resource.PrefixedUniqueId(fmt.Sprintf("%s-", value)) + if len(creationToken) > 64 { + errors = append(errors, fmt.Errorf( + "%q cannot take the Creation Token over the limit of 64 characters: %q", k, value)) + } + return +} + +func validatePerformanceModeType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "generalPurpose" && value != "maxIO" { + errors = append(errors, fmt.Errorf( + "%q contains an invalid Performance Mode %q. Valid modes are either %q or %q", + k, value, "generalPurpose", "maxIO")) + } + return +} diff --git a/builtin/providers/aws/resource_aws_efs_file_system_test.go b/builtin/providers/aws/resource_aws_efs_file_system_test.go index 2f9ff9a4c2..bc22c9807a 100644 --- a/builtin/providers/aws/resource_aws_efs_file_system_test.go +++ b/builtin/providers/aws/resource_aws_efs_file_system_test.go @@ -9,10 +9,71 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/efs" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) +func TestResourceAWSEFSReferenceName_validation(t *testing.T) { + var value string + var errors []error + + value = acctest.RandString(128) + _, errors = validateReferenceName(value, "reference_name") + if len(errors) == 0 { + t.Fatalf("Expected to trigger a validation error") + } + + value = acctest.RandString(32) + _, errors = validateReferenceName(value, "reference_name") + if len(errors) != 0 { + t.Fatalf("Expected to trigger a validation error") + } +} + +func TestResourceAWSEFSPerformanceMode_validation(t *testing.T) { + type testCase struct { + Value string + ErrCount int + } + + invalidCases := []testCase{ + { + Value: "garrusVakarian", + ErrCount: 1, + }, + { + Value: acctest.RandString(80), + ErrCount: 1, + }, + } + + for _, tc := range invalidCases { + _, errors := validatePerformanceModeType(tc.Value, "performance_mode") + if len(errors) != tc.ErrCount { + t.Fatalf("Expected to trigger a validation error") + } + } + + validCases := []testCase{ + { + Value: "generalPurpose", + ErrCount: 0, + }, + { + Value: "maxIO", + ErrCount: 0, + }, + } + + for _, tc := range validCases { + _, errors := validatePerformanceModeType(tc.Value, "aws_efs_file_system") + if len(errors) != tc.ErrCount { + t.Fatalf("Expected not to trigger a validation error") + } + } +} + func TestAccAWSEFSFileSystem_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -25,6 +86,10 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) { testAccCheckEfsFileSystem( "aws_efs_file_system.foo", ), + testAccCheckEfsFileSystemPerformanceMode( + "aws_efs_file_system.foo", + "generalPurpose", + ), ), }, resource.TestStep{ @@ -33,6 +98,10 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) { testAccCheckEfsFileSystem( "aws_efs_file_system.foo-with-tags", ), + testAccCheckEfsFileSystemPerformanceMode( + "aws_efs_file_system.foo-with-tags", + "generalPurpose", + ), testAccCheckEfsFileSystemTags( "aws_efs_file_system.foo-with-tags", map[string]string{ @@ -42,6 +111,22 @@ func TestAccAWSEFSFileSystem_basic(t *testing.T) { ), ), }, + resource.TestStep{ + Config: testAccAWSEFSFileSystemConfigWithPerformanceMode, + Check: resource.ComposeTestCheckFunc( + testAccCheckEfsFileSystem( + "aws_efs_file_system.foo-with-performance-mode", + ), + testAccCheckEfsCreationToken( + "aws_efs_file_system.foo-with-performance-mode", + "supercalifragilisticexpialidocious", + ), + testAccCheckEfsFileSystemPerformanceMode( + "aws_efs_file_system.foo-with-performance-mode", + "maxIO", + ), + ), + }, }, }) } @@ -82,14 +167,9 @@ func testAccCheckEfsFileSystem(resourceID string) resource.TestCheckFunc { return fmt.Errorf("No ID is set") } - fs, ok := s.RootModule().Resources[resourceID] - if !ok { - return fmt.Errorf("Not found: %s", resourceID) - } - conn := testAccProvider.Meta().(*AWSClient).efsconn _, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{ - FileSystemId: aws.String(fs.Primary.ID), + FileSystemId: aws.String(rs.Primary.ID), }) if err != nil { @@ -100,7 +180,7 @@ func testAccCheckEfsFileSystem(resourceID string) resource.TestCheckFunc { } } -func testAccCheckEfsFileSystemTags(resourceID string, expectedTags map[string]string) resource.TestCheckFunc { +func testAccCheckEfsCreationToken(resourceID string, expectedToken string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceID] if !ok { @@ -111,14 +191,39 @@ func testAccCheckEfsFileSystemTags(resourceID string, expectedTags map[string]st return fmt.Errorf("No ID is set") } - fs, ok := s.RootModule().Resources[resourceID] + conn := testAccProvider.Meta().(*AWSClient).efsconn + resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{ + FileSystemId: aws.String(rs.Primary.ID), + }) + + fs := resp.FileSystems[0] + if *fs.CreationToken != expectedToken { + return fmt.Errorf("Creation Token mismatch.\nExpected: %s\nGiven: %v", + expectedToken, *fs.CreationToken) + } + + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckEfsFileSystemTags(resourceID string, expectedTags map[string]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceID] if !ok { return fmt.Errorf("Not found: %s", resourceID) } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + conn := testAccProvider.Meta().(*AWSClient).efsconn resp, err := conn.DescribeTags(&efs.DescribeTagsInput{ - FileSystemId: aws.String(fs.Primary.ID), + FileSystemId: aws.String(rs.Primary.ID), }) if !reflect.DeepEqual(expectedTags, tagsToMapEFS(resp.Tags)) { @@ -134,6 +239,36 @@ func testAccCheckEfsFileSystemTags(resourceID string, expectedTags map[string]st } } +func testAccCheckEfsFileSystemPerformanceMode(resourceID string, expectedMode string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceID] + if !ok { + return fmt.Errorf("Not found: %s", resourceID) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).efsconn + resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{ + FileSystemId: aws.String(rs.Primary.ID), + }) + + fs := resp.FileSystems[0] + if *fs.PerformanceMode != expectedMode { + return fmt.Errorf("Performance Mode mismatch.\nExpected: %s\nGiven: %v", + expectedMode, *fs.PerformanceMode) + } + + if err != nil { + return err + } + + return nil + } +} + const testAccAWSEFSFileSystemConfig = ` resource "aws_efs_file_system" "foo" { reference_name = "radeksimko" @@ -149,3 +284,10 @@ resource "aws_efs_file_system" "foo-with-tags" { } } ` + +const testAccAWSEFSFileSystemConfigWithPerformanceMode = ` +resource "aws_efs_file_system" "foo-with-performance-mode" { + creation_token = "supercalifragilisticexpialidocious" + performance_mode = "maxIO" +} +` diff --git a/website/source/docs/providers/aws/r/efs_file_system.html.markdown b/website/source/docs/providers/aws/r/efs_file_system.html.markdown index ac017144f8..060f8eec8c 100644 --- a/website/source/docs/providers/aws/r/efs_file_system.html.markdown +++ b/website/source/docs/providers/aws/r/efs_file_system.html.markdown @@ -3,12 +3,12 @@ layout: "aws" page_title: "AWS: aws_efs_file_system" sidebar_current: "docs-aws-resource-efs-file-system" description: |- - Provides an EFS file system. + Provides an Elastic File System (EFS) resource. --- # aws\_efs\_file\_system -Provides an EFS file system. +Provides an Elastic File System (EFS) resource. ## Example Usage @@ -23,22 +23,32 @@ resource "aws_efs_file_system" "foo" { ## Argument Reference +~> **NOTE:** The `reference_name` attribute has been deprecated and might +be removed in future releases, please use `creation_token` instead. + The following arguments are supported: -* `reference_name` - (Optional) A reference name used in Creation Token -* `tags` - (Optional) A mapping of tags to assign to the file system +* `creation_token` - (Optional) A unique name (a maximum of 64 characters are allowed) +used as reference when creating the the Elastic File System to ensure idempotent file +system creation. By default generated by Terraform. See [Elastic File System] +(http://docs.aws.amazon.com/efs/latest/ug/) user guide for more information. +* `reference_name` - **DEPRECATED** (Optional) A reference name used when creating the +`Creation Token` which Amazon EFS uses to ensure idempotent file system creation. By +default generated by Terraform. +* `performance_mode` - (Optional) The file system performance mode. Can be either +`"generalPurpose"` or `"maxIO"` (Default: `"generalPurpose"`). +* `tags` - (Optional) A mapping of tags to assign to the file system. ## Attributes Reference The following attributes are exported: -* `id` - The ID that identifies the file system - +* `id` - The ID that identifies the file system (e.g. fs-ccfc0d65). ## Import -EFS Filesystems can be imported using the `id`, e.g. +The EFS file systems can be imported using the `id`, e.g. ``` $ terraform import aws_efs_file_system.foo fs-6fa144c6 -``` \ No newline at end of file +``` diff --git a/website/source/docs/providers/aws/r/efs_mount_target.html.markdown b/website/source/docs/providers/aws/r/efs_mount_target.html.markdown index 3764926dfa..b80216c0f4 100644 --- a/website/source/docs/providers/aws/r/efs_mount_target.html.markdown +++ b/website/source/docs/providers/aws/r/efs_mount_target.html.markdown @@ -3,13 +3,15 @@ layout: "aws" page_title: "AWS: aws_efs_mount_target" sidebar_current: "docs-aws-resource-efs-mount-target" description: |- - Provides an EFS mount target. + Provides an Elastic File System (EFS) mount target. --- # aws\_efs\_mount\_target -Provides an EFS mount target. Per [documentation](https://docs.aws.amazon.com/efs/latest/ug/limits.html) -the limit is 1 mount target per AZ for a single EFS file system. +Provides an Elastic File System (EFS) mount target. + +~> **NOTE:** As per the current [documentation](https://docs.aws.amazon.com/efs/latest/ug/limits.html) +the limit is 1 mount target per Availability Zone for a single EFS file system. ## Example Usage @@ -35,25 +37,28 @@ resource "aws_subnet" "alpha" { The following arguments are supported: * `file_system_id` - (Required) The ID of the file system for which the mount target is intended. -* `subnet_id` - (Required) The ID of the subnet that the mount target is in. -* `ip_address` - (Optional) The address at which the file system may be mounted via the mount target. -* `security_groups` - (Optional) A list of up to 5 VPC security group IDs in effect for the mount target. +* `subnet_id` - (Required) The ID of the subnet to add the mount target in. +* `ip_address` - (Optional) The address (within the address range of the specified subnet) at +which the file system may be mounted via the mount target. +* `security_groups` - (Optional) A list of up to 5 VPC security group IDs (that must +be for the same VPC as subnet specified) in effect for the mount target. ## Attributes Reference -~> **Note:** The `dns_name` attribute is only useful if the mount target is in a VPC with `enable_dns_hostnames = true`. +~> **Note:** The `dns_name` attribute is only useful if the mount target is in a VPC that has +support for DNS hostnames enabled. See [Using DNS with Your VPC](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-dns.html) +and [VPC resource](https://www.terraform.io/docs/providers/aws/r/vpc.html#enable_dns_hostnames) in Terraform for more information. The following attributes are exported: -* `id` - The ID of the mount target -* `dns_name` - The DNS name for the given subnet/AZ per [documented convention](http://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html) +* `id` - The ID of the mount target. +* `dns_name` - The DNS name for the given subnet/AZ per [documented convention](http://docs.aws.amazon.com/efs/latest/ug/mounting-fs-mount-cmd-dns-name.html). * `network_interface_id` - The ID of the network interface that Amazon EFS created when it created the mount target. - ## Import -EFS Mount Targets can be imported using the `id`, e.g. +The EFS mount targets can be imported using the `id`, e.g. ``` $ terraform import aws_efs_mount_target.alpha fsmt-52a643fb -``` \ No newline at end of file +```