From cd96b8a7fc3b1d6a2bed5036011c4694ba079fe2 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 2 Jun 2015 21:19:50 +0100 Subject: [PATCH 1/3] provider/aws: Add efs_file_system --- builtin/providers/aws/config.go | 5 + builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_efs_file_system.go | 165 ++++++++++++++++++ .../aws/resource_aws_efs_file_system_test.go | 133 ++++++++++++++ builtin/providers/aws/tagsEFS.go | 94 ++++++++++ builtin/providers/aws/tagsEFS_test.go | 85 +++++++++ 6 files changed, 483 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_efs_file_system.go create mode 100644 builtin/providers/aws/resource_aws_efs_file_system_test.go create mode 100644 builtin/providers/aws/tagsEFS.go create mode 100644 builtin/providers/aws/tagsEFS_test.go diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 8f94430d24..f462810fc1 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/efs" "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/elb" "github.com/aws/aws-sdk-go/service/iam" @@ -47,6 +48,7 @@ type AWSClient struct { dynamodbconn *dynamodb.DynamoDB ec2conn *ec2.EC2 ecsconn *ecs.ECS + efsconn *efs.EFS elbconn *elb.ELB autoscalingconn *autoscaling.AutoScaling s3conn *s3.S3 @@ -140,6 +142,9 @@ func (c *Config) Client() (interface{}, error) { log.Println("[INFO] Initializing ECS Connection") client.ecsconn = ecs.New(awsConfig) + log.Println("[INFO] Initializing EFS Connection") + client.efsconn = efs.New(awsConfig) + // aws-sdk-go uses v4 for signing requests, which requires all global // endpoints to use 'us-east-1'. // See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 8596b844e5..f57cd47ea0 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -175,6 +175,7 @@ func Provider() terraform.ResourceProvider { "aws_ecs_cluster": resourceAwsEcsCluster(), "aws_ecs_service": resourceAwsEcsService(), "aws_ecs_task_definition": resourceAwsEcsTaskDefinition(), + "aws_efs_file_system": resourceAwsEfsFileSystem(), "aws_eip": resourceAwsEip(), "aws_elasticache_cluster": resourceAwsElasticacheCluster(), "aws_elasticache_parameter_group": resourceAwsElasticacheParameterGroup(), diff --git a/builtin/providers/aws/resource_aws_efs_file_system.go b/builtin/providers/aws/resource_aws_efs_file_system.go new file mode 100644 index 0000000000..4beae81e02 --- /dev/null +++ b/builtin/providers/aws/resource_aws_efs_file_system.go @@ -0,0 +1,165 @@ +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/efs" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsEfsFileSystem() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEfsFileSystemCreate, + Read: resourceAwsEfsFileSystemRead, + Update: resourceAwsEfsFileSystemUpdate, + Delete: resourceAwsEfsFileSystemDelete, + + Schema: map[string]*schema.Schema{ + "reference_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceAwsEfsFileSystemCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + + referenceName := "" + if v, ok := d.GetOk("reference_name"); ok { + referenceName = v.(string) + "-" + } + token := referenceName + resource.UniqueId() + fs, err := conn.CreateFileSystem(&efs.CreateFileSystemInput{ + CreationToken: aws.String(token), + }) + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating EFS file system: %s", *fs) + d.SetId(*fs.FileSystemId) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"creating"}, + Target: "available", + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{ + FileSystemId: aws.String(d.Id()), + }) + if err != nil { + return nil, "error", err + } + + if len(resp.FileSystems) < 1 { + return nil, "not-found", fmt.Errorf("EFS file system %q not found", d.Id()) + } + + fs := resp.FileSystems[0] + log.Printf("[DEBUG] current status of %q: %q", *fs.FileSystemId, *fs.LifeCycleState) + return fs, *fs.LifeCycleState, nil + }, + Timeout: 10 * time.Minute, + Delay: 2 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + 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) + + return resourceAwsEfsFileSystemUpdate(d, meta) +} + +func resourceAwsEfsFileSystemUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + err := setTagsEFS(conn, d) + if err != nil { + return err + } + + return resourceAwsEfsFileSystemRead(d, meta) +} + +func resourceAwsEfsFileSystemRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + + resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{ + FileSystemId: aws.String(d.Id()), + }) + if err != nil { + return err + } + if len(resp.FileSystems) < 1 { + return fmt.Errorf("EFS file system %q not found", d.Id()) + } + + tagsResp, err := conn.DescribeTags(&efs.DescribeTagsInput{ + FileSystemId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + d.Set("tags", tagsToMapEFS(tagsResp.Tags)) + + return nil +} + +func resourceAwsEfsFileSystemDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + + log.Printf("[DEBUG] Deleting EFS file system %s", d.Id()) + _, err := conn.DeleteFileSystem(&efs.DeleteFileSystemInput{ + FileSystemId: aws.String(d.Id()), + }) + stateConf := &resource.StateChangeConf{ + Pending: []string{"available", "deleting"}, + Target: "", + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeFileSystems(&efs.DescribeFileSystemsInput{ + FileSystemId: 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 len(resp.FileSystems) < 1 { + return nil, "", nil + } + + fs := resp.FileSystems[0] + log.Printf("[DEBUG] current status of %q: %q", + *fs.FileSystemId, *fs.LifeCycleState) + return fs, *fs.LifeCycleState, nil + }, + Timeout: 10 * time.Minute, + Delay: 2 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return err + } + + log.Printf("[DEBUG] EFS file system %q deleted.", d.Id()) + + return nil +} diff --git a/builtin/providers/aws/resource_aws_efs_file_system_test.go b/builtin/providers/aws/resource_aws_efs_file_system_test.go new file mode 100644 index 0000000000..03e683476c --- /dev/null +++ b/builtin/providers/aws/resource_aws_efs_file_system_test.go @@ -0,0 +1,133 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/efs" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSEFSFileSystem(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckEfsFileSystemDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSEFSFileSystemConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckEfsFileSystem( + "aws_efs_file_system.foo", + ), + ), + }, + resource.TestStep{ + Config: testAccAWSEFSFileSystemConfigWithTags, + Check: resource.ComposeTestCheckFunc( + testAccCheckEfsFileSystem( + "aws_efs_file_system.foo-with-tags", + ), + testAccCheckEfsFileSystemTags( + "aws_efs_file_system.foo-with-tags", + map[string]string{ + "Name": "foo-efs", + "Another": "tag", + }, + ), + ), + }, + }, + }) +} + +func testAccCheckEfsFileSystemDestroy(s *terraform.State) error { + if len(s.RootModule().Resources) > 0 { + return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources) + } + + return nil +} + +func testAccCheckEfsFileSystem(resourceID 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") + } + + 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), + }) + + 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") + } + + fs, ok := s.RootModule().Resources[resourceID] + if !ok { + return fmt.Errorf("Not found: %s", resourceID) + } + + conn := testAccProvider.Meta().(*AWSClient).efsconn + resp, err := conn.DescribeTags(&efs.DescribeTagsInput{ + FileSystemId: aws.String(fs.Primary.ID), + }) + + if !reflect.DeepEqual(expectedTags, tagsToMapEFS(resp.Tags)) { + return fmt.Errorf("Tags mismatch.\nExpected: %#v\nGiven: %#v", + expectedTags, resp.Tags) + } + + if err != nil { + return err + } + + return nil + } +} + +const testAccAWSEFSFileSystemConfig = ` +resource "aws_efs_file_system" "foo" { + reference_name = "radeksimko" +} +` + +const testAccAWSEFSFileSystemConfigWithTags = ` +resource "aws_efs_file_system" "foo-with-tags" { + reference_name = "yada_yada" + tags { + Name = "foo-efs" + Another = "tag" + } +} +` diff --git a/builtin/providers/aws/tagsEFS.go b/builtin/providers/aws/tagsEFS.go new file mode 100644 index 0000000000..8303d68882 --- /dev/null +++ b/builtin/providers/aws/tagsEFS.go @@ -0,0 +1,94 @@ +package aws + +import ( + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/efs" + "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" +func setTagsEFS(conn *efs.EFS, 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 := diffTagsEFS(tagsFromMapEFS(o), tagsFromMapEFS(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.DeleteTags(&efs.DeleteTagsInput{ + FileSystemId: aws.String(d.Id()), + TagKeys: k, + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + _, err := conn.CreateTags(&efs.CreateTagsInput{ + FileSystemId: aws.String(d.Id()), + 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 diffTagsEFS(oldTags, newTags []*efs.Tag) ([]*efs.Tag, []*efs.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 []*efs.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapEFS(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapEFS(m map[string]interface{}) []*efs.Tag { + var result []*efs.Tag + for k, v := range m { + result = append(result, &efs.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapEFS(ts []*efs.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + result[*t.Key] = *t.Value + } + + return result +} diff --git a/builtin/providers/aws/tagsEFS_test.go b/builtin/providers/aws/tagsEFS_test.go new file mode 100644 index 0000000000..ca2ae88436 --- /dev/null +++ b/builtin/providers/aws/tagsEFS_test.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/service/efs" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestDiffEFSTags(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + c, r := diffTagsEFS(tagsFromMapEFS(tc.Old), tagsFromMapEFS(tc.New)) + cm := tagsToMapEFS(c) + rm := tagsToMapEFS(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, cm) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, rm) + } + } +} + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckEFSTags( + ts *[]*efs.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMapEFS(*ts) + v, ok := m[key] + if value != "" && !ok { + return fmt.Errorf("Missing tag: %s", key) + } else if value == "" && ok { + return fmt.Errorf("Extra tag: %s", key) + } + if value == "" { + return nil + } + + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} From 167b44770f00b786622cf211b19d3091439e60b7 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 15 Sep 2015 23:11:53 +0100 Subject: [PATCH 2/3] provider/aws: Add efs_mount_target --- builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_efs_mount_target.go | 223 ++++++++++++++++++ .../aws/resource_aws_efs_mount_target_test.go | 135 +++++++++++ builtin/providers/aws/structure.go | 13 +- 4 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/aws/resource_aws_efs_mount_target.go create mode 100644 builtin/providers/aws/resource_aws_efs_mount_target_test.go diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index f57cd47ea0..350aeb75da 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -176,6 +176,7 @@ func Provider() terraform.ResourceProvider { "aws_ecs_service": resourceAwsEcsService(), "aws_ecs_task_definition": resourceAwsEcsTaskDefinition(), "aws_efs_file_system": resourceAwsEfsFileSystem(), + "aws_efs_mount_target": resourceAwsEfsMountTarget(), "aws_eip": resourceAwsEip(), "aws_elasticache_cluster": resourceAwsElasticacheCluster(), "aws_elasticache_parameter_group": resourceAwsElasticacheParameterGroup(), diff --git a/builtin/providers/aws/resource_aws_efs_mount_target.go b/builtin/providers/aws/resource_aws_efs_mount_target.go new file mode 100644 index 0000000000..ca7656e634 --- /dev/null +++ b/builtin/providers/aws/resource_aws_efs_mount_target.go @@ -0,0 +1,223 @@ +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/efs" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsEfsMountTarget() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEfsMountTargetCreate, + Read: resourceAwsEfsMountTargetRead, + Update: resourceAwsEfsMountTargetUpdate, + Delete: resourceAwsEfsMountTargetDelete, + + Schema: map[string]*schema.Schema{ + "file_system_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "ip_address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: true, + }, + + "security_groups": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Computed: true, + Optional: true, + }, + + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "network_interface_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsEfsMountTargetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + + input := efs.CreateMountTargetInput{ + FileSystemId: aws.String(d.Get("file_system_id").(string)), + SubnetId: aws.String(d.Get("subnet_id").(string)), + } + + if v, ok := d.GetOk("ip_address"); ok { + input.IpAddress = aws.String(v.(string)) + } + if v, ok := d.GetOk("security_groups"); ok { + input.SecurityGroups = expandStringList(v.(*schema.Set).List()) + } + + log.Printf("[DEBUG] Creating EFS mount target: %#v", input) + + mt, err := conn.CreateMountTarget(&input) + if err != nil { + return err + } + + d.SetId(*mt.MountTargetId) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"creating"}, + Target: "available", + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ + MountTargetId: aws.String(d.Id()), + }) + if err != nil { + return nil, "error", err + } + + if len(resp.MountTargets) < 1 { + return nil, "error", fmt.Errorf("EFS mount target %q not found", d.Id()) + } + + mt := resp.MountTargets[0] + + log.Printf("[DEBUG] Current status of %q: %q", *mt.MountTargetId, *mt.LifeCycleState) + return mt, *mt.LifeCycleState, nil + }, + Timeout: 10 * time.Minute, + Delay: 2 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for EFS mount target (%s) to create: %s", d.Id(), err) + } + + log.Printf("[DEBUG] EFS mount target created: %s", *mt.MountTargetId) + + return resourceAwsEfsMountTargetRead(d, meta) +} + +func resourceAwsEfsMountTargetUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + + if d.HasChange("security_groups") { + input := efs.ModifyMountTargetSecurityGroupsInput{ + MountTargetId: aws.String(d.Id()), + SecurityGroups: expandStringList(d.Get("security_groups").(*schema.Set).List()), + } + _, err := conn.ModifyMountTargetSecurityGroups(&input) + if err != nil { + return err + } + } + + return resourceAwsEfsMountTargetRead(d, meta) +} + +func resourceAwsEfsMountTargetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ + MountTargetId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + if len(resp.MountTargets) < 1 { + return fmt.Errorf("EFS mount target %q not found", d.Id()) + } + + mt := resp.MountTargets[0] + + log.Printf("[DEBUG] Found EFS mount target: %#v", mt) + + d.SetId(*mt.MountTargetId) + d.Set("file_system_id", *mt.FileSystemId) + d.Set("ip_address", *mt.IpAddress) + d.Set("subnet_id", *mt.SubnetId) + d.Set("network_interface_id", *mt.NetworkInterfaceId) + + sgResp, err := conn.DescribeMountTargetSecurityGroups(&efs.DescribeMountTargetSecurityGroupsInput{ + MountTargetId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + d.Set("security_groups", schema.NewSet(schema.HashString, flattenStringList(sgResp.SecurityGroups))) + + return nil +} + +func resourceAwsEfsMountTargetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).efsconn + + log.Printf("[DEBUG] Deleting EFS mount target %q", d.Id()) + _, err := conn.DeleteMountTarget(&efs.DeleteMountTargetInput{ + MountTargetId: aws.String(d.Id()), + }) + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"available", "deleting", "deleted"}, + Target: "", + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ + MountTargetId: aws.String(d.Id()), + }) + if err != nil { + awsErr, ok := err.(awserr.Error) + if !ok { + return nil, "error", err + } + + if awsErr.Code() == "MountTargetNotFound" { + return nil, "", nil + } + + return nil, "error", awsErr + } + + if len(resp.MountTargets) < 1 { + return nil, "", nil + } + + mt := resp.MountTargets[0] + + log.Printf("[DEBUG] Current status of %q: %q", *mt.MountTargetId, *mt.LifeCycleState) + return mt, *mt.LifeCycleState, nil + }, + Timeout: 10 * time.Minute, + Delay: 2 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for EFS mount target (%q) to delete: %q", + d.Id(), err.Error()) + } + + log.Printf("[DEBUG] EFS mount target %q deleted.", d.Id()) + + return nil +} diff --git a/builtin/providers/aws/resource_aws_efs_mount_target_test.go b/builtin/providers/aws/resource_aws_efs_mount_target_test.go new file mode 100644 index 0000000000..e9d624e03f --- /dev/null +++ b/builtin/providers/aws/resource_aws_efs_mount_target_test.go @@ -0,0 +1,135 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/efs" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSEFSMountTarget(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckEfsMountTargetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSEFSMountTargetConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckEfsMountTarget( + "aws_efs_mount_target.alpha", + ), + ), + }, + resource.TestStep{ + Config: testAccAWSEFSMountTargetConfigModified, + Check: resource.ComposeTestCheckFunc( + testAccCheckEfsMountTarget( + "aws_efs_mount_target.alpha", + ), + testAccCheckEfsMountTarget( + "aws_efs_mount_target.beta", + ), + ), + }, + }, + }) +} + +func testAccCheckEfsMountTargetDestroy(s *terraform.State) error { + if len(s.RootModule().Resources) > 0 { + return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources) + } + + return nil +} + +func testAccCheckEfsMountTarget(resourceID 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") + } + + fs, ok := s.RootModule().Resources[resourceID] + if !ok { + return fmt.Errorf("Not found: %s", resourceID) + } + + conn := testAccProvider.Meta().(*AWSClient).efsconn + mt, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ + MountTargetId: aws.String(fs.Primary.ID), + }) + if err != nil { + return err + } + + if *mt.MountTargets[0].MountTargetId != fs.Primary.ID { + return fmt.Errorf("Mount target ID mismatch: %q != %q", + *mt.MountTargets[0].MountTargetId, fs.Primary.ID) + } + + return nil + } +} + +const testAccAWSEFSMountTargetConfig = ` +resource "aws_efs_file_system" "foo" { + reference_name = "radeksimko" +} + +resource "aws_efs_mount_target" "alpha" { + file_system_id = "${aws_efs_file_system.foo.id}" + subnet_id = "${aws_subnet.alpha.id}" +} + +resource "aws_vpc" "foo" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "alpha" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + cidr_block = "10.0.1.0/24" +} +` + +const testAccAWSEFSMountTargetConfigModified = ` +resource "aws_efs_file_system" "foo" { + reference_name = "radeksimko" +} + +resource "aws_efs_mount_target" "alpha" { + file_system_id = "${aws_efs_file_system.foo.id}" + subnet_id = "${aws_subnet.alpha.id}" +} + +resource "aws_efs_mount_target" "beta" { + file_system_id = "${aws_efs_file_system.foo.id}" + subnet_id = "${aws_subnet.beta.id}" +} + +resource "aws_vpc" "foo" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "alpha" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + cidr_block = "10.0.1.0/24" +} + +resource "aws_subnet" "beta" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2b" + cidr_block = "10.0.2.0/24" +} +` diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 9b1c0ab790..d736e0ad5a 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -368,7 +368,7 @@ func flattenElastiCacheParameters(list []*elasticache.Parameter) []map[string]in } // Takes the result of flatmap.Expand for an array of strings -// and returns a []string +// and returns a []*string func expandStringList(configured []interface{}) []*string { vs := make([]*string, 0, len(configured)) for _, v := range configured { @@ -377,6 +377,17 @@ func expandStringList(configured []interface{}) []*string { return vs } +// Takes list of pointers to strings. Expand to an array +// of raw strings and returns a []interface{} +// to keep compatibility w/ schema.NewSetschema.NewSet +func flattenStringList(list []*string) []interface{} { + vs := make([]interface{}, 0, len(list)) + for _, v := range list { + vs = append(vs, *v) + } + return vs +} + //Flattens an array of private ip addresses into a []string, where the elements returned are the IP strings e.g. "192.168.0.0" func flattenNetworkInterfacesPrivateIPAddresses(dtos []*ec2.NetworkInterfacePrivateIpAddress) []string { ips := make([]string, 0, len(dtos)) From d86c753cf179b0181dfab1148ba8712e263dc60d Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 16 Sep 2015 12:44:41 +0100 Subject: [PATCH 3/3] provider/aws: Add docs for EFS resources --- .../aws/r/efs_file_system.html.markdown | 35 ++++++++++++++ .../aws/r/efs_mount_target.html.markdown | 47 +++++++++++++++++++ website/source/layouts/aws.erb | 16 +++++++ 3 files changed, 98 insertions(+) create mode 100644 website/source/docs/providers/aws/r/efs_file_system.html.markdown create mode 100644 website/source/docs/providers/aws/r/efs_mount_target.html.markdown 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 new file mode 100644 index 0000000000..ac4cbb0b3a --- /dev/null +++ b/website/source/docs/providers/aws/r/efs_file_system.html.markdown @@ -0,0 +1,35 @@ +--- +layout: "aws" +page_title: "AWS: aws_efs_file_system" +sidebar_current: "docs-aws-resource-efs-file-system" +description: |- + Provides an EFS file system. +--- + +# aws\_efs\_file\_system + +Provides an EFS file system. + +## Example Usage + +``` +resource "aws_efs_file_system" "foo" { + reference_name = "my-product" + tags { + Name = "MyProduct" + } +} +``` + +## Argument Reference + +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 + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID that identifies the file system 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 new file mode 100644 index 0000000000..59bd3bee22 --- /dev/null +++ b/website/source/docs/providers/aws/r/efs_mount_target.html.markdown @@ -0,0 +1,47 @@ +--- +layout: "aws" +page_title: "AWS: aws_efs_mount_target" +sidebar_current: "docs-aws-resource-efs-mount-target" +description: |- + Provides an EFS mount target. +--- + +# aws\_efs\_file\_system + +Provides an EFS file system. Per [documentation](http://docs.aws.amazon.com/efs/latest/ug/limits.html) +the limit is 1 mount target per AZ. + +## Example Usage + +``` +resource "aws_efs_mount_target" "alpha" { + file_system_id = "${aws_efs_file_system.foo.id}" + subnet_id = "${aws_subnet.alpha.id}" +} + +resource "aws_vpc" "foo" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "alpha" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + cidr_block = "10.0.1.0/24" +} +``` + +## Argument Reference + +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. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the mount target +* `network_interface_id` - The ID of the network interface that Amazon EFS created when it created the mount target. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 1f5c7dee15..17281a9f84 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -132,6 +132,22 @@ + > + EFS Resources + + + + > ElastiCache Resources