From 68587eb51e6e909eae62915491a2b4f50baffd45 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 22 May 2015 16:06:28 -0500 Subject: [PATCH 1/6] provider/aws: Add resource_aws_volume_attachment --- builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_volume_attachment.go | 158 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_volume_attachment.go diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index db90549d2..6a0d1d3a1 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -124,6 +124,7 @@ func Provider() terraform.ResourceProvider { "aws_security_group_rule": resourceAwsSecurityGroupRule(), "aws_sqs_queue": resourceAwsSqsQueue(), "aws_subnet": resourceAwsSubnet(), + "aws_volume_attachment": resourceAwsVolumeAttachment(), "aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(), "aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(), "aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(), diff --git a/builtin/providers/aws/resource_aws_volume_attachment.go b/builtin/providers/aws/resource_aws_volume_attachment.go new file mode 100644 index 000000000..25519833a --- /dev/null +++ b/builtin/providers/aws/resource_aws_volume_attachment.go @@ -0,0 +1,158 @@ +package aws + +import ( + "bytes" + "fmt" + "time" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/aws/awserr" + "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsVolumeAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsVolumeAttachmentCreate, + Read: resourceAwsVolumeAttachmentRead, + Delete: resourceAwsVolumeAttachmentDelete, + + Schema: map[string]*schema.Schema{ + "device_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "instance_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "volume_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "force_detach": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + name := d.Get("device_name").(string) + iID := d.Get("instance_id").(string) + vID := d.Get("volume_id").(string) + + opts := &ec2.AttachVolumeInput{ + Device: aws.String(name), + InstanceID: aws.String(iID), + VolumeID: aws.String(vID), + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: "attaching", + Refresh: attachVolumeFunc(conn, opts), + Timeout: 1 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error attaching volume %s to instance %s: %s", vID, iID, err) + } + + d.SetId(volumeAttachmentID(name, vID, iID)) + return resourceAwsVolumeAttachmentRead(d, meta) +} + +func attachVolumeFunc(conn *ec2.EC2, opts *ec2.AttachVolumeInput) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + va, err := conn.AttachVolume(opts) + if err != nil { + awsErr, ok := err.(awserr.Error) + if ok && awsErr.Code() == "VolumeInUse" && *va.InstanceID == *opts.InstanceID { + return nil, "attaching", nil + } + return nil, "error", err + } + return va, *va.State, nil + } +} + +func resourceAwsVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + request := &ec2.DescribeVolumesInput{ + VolumeIDs: []*string{aws.String(d.Get("volume_id").(string))}, + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("attachment.instance-id"), + Values: []*string{aws.String(d.Get("instance_id").(string))}, + }, + }, + } + + _, err := conn.DescribeVolumes(request) + if err != nil { + if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVolume.NotFound" { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading EC2 volume %s for instance: %s: %#v", d.Get("volume_id").(string), d.Get("instance_id").(string), err) + } + return nil +} + +func resourceAwsVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + volume := d.Get("volume_id").(string) + instance := d.Get("instance_id").(string) + + opts := &ec2.DetachVolumeInput{ + Device: aws.String(d.Get("device_name").(string)), + InstanceID: aws.String(instance), + VolumeID: aws.String(volume), + Force: aws.Boolean(d.Get("force_detach").(bool)), + } + + return resource.Retry(1*time.Minute, func() error { + resp, err := conn.DetachVolume(opts) + if err != nil { + awsErr, ok := err.(awserr.Error) + if ok && (awsErr.Code() == "IncorrectState" || awsErr.Code() == "InvalidVolume.NotFound") { + // volume attachment is not in a valid "attachment state" + return nil + } + + return err + } + + if resp.State != nil && *resp.State == "detaching" { + return fmt.Errorf("waiting for volume %s to detach from instance %s", volume, instance) + } else if *resp.State == "detached" { + return nil + } + return fmt.Errorf("Error detaching volume %s from instance %s", volume, instance) + }) +} + +func volumeAttachmentID(name, volumeID, instanceID string) string { + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf("%s-", name)) + buf.WriteString(fmt.Sprintf("%s-", instanceID)) + buf.WriteString(fmt.Sprintf("%s-", volumeID)) + + return fmt.Sprintf("vai-%d", hashcode.String(buf.String())) +} From fa44e455fa7860f54e7f3394440f0767033d5959 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 22 May 2015 17:02:05 -0500 Subject: [PATCH 2/6] provider/aws: Add accdeptance test for volume attachment --- .../aws/resource_aws_ebs_volume_test.go | 36 +++++++ .../resource_aws_volume_attachment_test.go | 93 +++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_volume_attachment_test.go diff --git a/builtin/providers/aws/resource_aws_ebs_volume_test.go b/builtin/providers/aws/resource_aws_ebs_volume_test.go index 8a555183f..d5bc21203 100644 --- a/builtin/providers/aws/resource_aws_ebs_volume_test.go +++ b/builtin/providers/aws/resource_aws_ebs_volume_test.go @@ -1,23 +1,59 @@ package aws import ( + "fmt" "testing" + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" ) func TestAccAWSEBSVolume(t *testing.T) { + var v ec2.Volume resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ resource.TestStep{ Config: testAccAwsEbsVolumeConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVolumeExists("aws_ebs_volume.test", &v), + ), }, }, }) } +func testAccCheckVolumeExists(n string, v *ec2.Volume) 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).ec2conn + + request := &ec2.DescribeVolumesInput{ + VolumeIDs: []*string{aws.String(rs.Primary.ID)}, + } + + response, err := conn.DescribeVolumes(request) + if err == nil { + if response.Volumes != nil && len(response.Volumes) > 0 { + *v = *response.Volumes[0] + return nil + } + } + return fmt.Errorf("Error finding EC2 volume %s", rs.Primary.ID) + } +} + const testAccAwsEbsVolumeConfig = ` resource "aws_ebs_volume" "test" { availability_zone = "us-west-2a" diff --git a/builtin/providers/aws/resource_aws_volume_attachment_test.go b/builtin/providers/aws/resource_aws_volume_attachment_test.go new file mode 100644 index 000000000..dcbcea1f2 --- /dev/null +++ b/builtin/providers/aws/resource_aws_volume_attachment_test.go @@ -0,0 +1,93 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSVolumeAttachment_basic(t *testing.T) { + var i ec2.Instance + var v ec2.Volume + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVolumeAttachmentDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVolumeAttachmentConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_volume_attachment.ebs_att", "device_name", "/dev/sdh"), + testAccCheckInstanceExists( + "aws_instance.web", &i), + testAccCheckVolumeExists( + "aws_ebs_volume.example", &v), + testAccCheckVolumeAttachmentExists( + "aws_volume_attachment.ebs_att", &i, &v), + ), + }, + }, + }) +} + +func testAccCheckVolumeAttachmentExists(n string, i *ec2.Instance, v *ec2.Volume) 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") + } + + for _, b := range i.BlockDeviceMappings { + if rs.Primary.Attributes["device_name"] == *b.DeviceName { + if b.EBS.VolumeID != nil && rs.Primary.Attributes["volume_id"] == *b.EBS.VolumeID { + // pass + return nil + } + } + } + + return fmt.Errorf("Error finding instance/volume") + } +} + +func testAccCheckVolumeAttachmentDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + log.Printf("\n\n----- This is never called") + if rs.Type != "aws_volume_attachment" { + continue + } + } + return nil +} + +const testAccVolumeAttachmentConfig = ` +resource "aws_instance" "web" { + ami = "ami-21f78e11" + availability_zone = "us-west-2a" + instance_type = "t1.micro" + tags { + Name = "HelloWorld" + } +} + +resource "aws_ebs_volume" "example" { + availability_zone = "us-west-2a" + size = 5 +} + +resource "aws_volume_attachment" "ebs_att" { + device_name = "/dev/sdh" + volume_id = "${aws_ebs_volume.example.id}" + instance_id = "${aws_instance.web.id}" +} +` From c523401f461cdf08d399662af8043b306d3a70d1 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 27 May 2015 17:07:04 -0500 Subject: [PATCH 3/6] provider/aws: Wait for EBS Volume to be available --- .../providers/aws/resource_aws_ebs_volume.go | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/builtin/providers/aws/resource_aws_ebs_volume.go b/builtin/providers/aws/resource_aws_ebs_volume.go index 815ec59a0..450fd7f74 100644 --- a/builtin/providers/aws/resource_aws_ebs_volume.go +++ b/builtin/providers/aws/resource_aws_ebs_volume.go @@ -2,11 +2,14 @@ package aws import ( "fmt" + "log" + "time" "github.com/awslabs/aws-sdk-go/aws" "github.com/awslabs/aws-sdk-go/aws/awserr" "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -91,9 +94,55 @@ func resourceAwsEbsVolumeCreate(d *schema.ResourceData, meta interface{}) error if err != nil { return fmt.Errorf("Error creating EC2 volume: %s", err) } + + log.Printf( + "[DEBUG] Waiting for Volume (%s) to become available", + d.Id()) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"creating"}, + Target: "available", + Refresh: volumeStateRefreshFunc(conn, *result.VolumeID), + Timeout: 5 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for Volume (%s) to become available: %s", + *result.VolumeID, err) + } + return readVolume(d, result) } +// volumeStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// a the state of a Volume. Returns successfully when volume is available +func volumeStateRefreshFunc(conn *ec2.EC2, volumeID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{ + VolumeIDs: []*string{aws.String(volumeID)}, + }) + + if err != nil { + if ec2err, ok := err.(awserr.Error); ok { + // Set this to nil as if we didn't find anything. + log.Printf("Error on Volume State Refresh: message: \"%s\", code:\"%s\"", ec2err.Message(), ec2err.Code()) + resp = nil + return nil, "", err + } else { + log.Printf("Error on Volume State Refresh: %s", err) + return nil, "", err + } + } + + v := resp.Volumes[0] + return v, *v.State, nil + } +} + func resourceAwsEbsVolumeRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn From 3af25c1b9782e01e88db25ea8abbf8a6df833bfd Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 27 May 2015 17:07:34 -0500 Subject: [PATCH 4/6] refactor volume attachment to not retry attachment --- .../aws/resource_aws_volume_attachment.go | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/builtin/providers/aws/resource_aws_volume_attachment.go b/builtin/providers/aws/resource_aws_volume_attachment.go index 25519833a..007f4109e 100644 --- a/builtin/providers/aws/resource_aws_volume_attachment.go +++ b/builtin/providers/aws/resource_aws_volume_attachment.go @@ -58,38 +58,20 @@ func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) InstanceID: aws.String(iID), VolumeID: aws.String(vID), } - stateConf := &resource.StateChangeConf{ - Pending: []string{"pending"}, - Target: "attaching", - Refresh: attachVolumeFunc(conn, opts), - Timeout: 1 * time.Minute, - Delay: 5 * time.Second, - MinTimeout: 3 * time.Second, - } - _, err := stateConf.WaitForState() + _, err := conn.AttachVolume(opts) if err != nil { - return fmt.Errorf("Error attaching volume %s to instance %s: %s", vID, iID, err) + if awsErr, ok := err.(awserr.Error); ok { + return fmt.Errorf("[WARN] Error attaching volume (%s) to instance (%s), message: \"%s\", code: \"%s\"", + vID, iID, awsErr.Message(), awsErr.Code()) + } + return err } d.SetId(volumeAttachmentID(name, vID, iID)) return resourceAwsVolumeAttachmentRead(d, meta) } -func attachVolumeFunc(conn *ec2.EC2, opts *ec2.AttachVolumeInput) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - va, err := conn.AttachVolume(opts) - if err != nil { - awsErr, ok := err.(awserr.Error) - if ok && awsErr.Code() == "VolumeInUse" && *va.InstanceID == *opts.InstanceID { - return nil, "attaching", nil - } - return nil, "error", err - } - return va, *va.State, nil - } -} - func resourceAwsVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn @@ -127,7 +109,7 @@ func resourceAwsVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) Force: aws.Boolean(d.Get("force_detach").(bool)), } - return resource.Retry(1*time.Minute, func() error { + return resource.Retry(3*time.Minute, func() error { resp, err := conn.DetachVolume(opts) if err != nil { awsErr, ok := err.(awserr.Error) From 9329073dae0678634ffa182a4ec74d3b466b33b5 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 28 May 2015 10:13:46 -0500 Subject: [PATCH 5/6] refactor to poll for status consistently --- .../aws/resource_aws_volume_attachment.go | 95 ++++++++++++++----- .../resource_aws_volume_attachment_test.go | 2 +- 2 files changed, 74 insertions(+), 23 deletions(-) diff --git a/builtin/providers/aws/resource_aws_volume_attachment.go b/builtin/providers/aws/resource_aws_volume_attachment.go index 007f4109e..14a028d4e 100644 --- a/builtin/providers/aws/resource_aws_volume_attachment.go +++ b/builtin/providers/aws/resource_aws_volume_attachment.go @@ -3,6 +3,7 @@ package aws import ( "bytes" "fmt" + "log" "time" "github.com/awslabs/aws-sdk-go/aws" @@ -59,6 +60,7 @@ func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) VolumeID: aws.String(vID), } + log.Printf("[DEBUG] Attaching Volume (%s) to Instance (%s)", vID, iID) _, err := conn.AttachVolume(opts) if err != nil { if awsErr, ok := err.(awserr.Error); ok { @@ -68,10 +70,59 @@ func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) return err } + stateConf := &resource.StateChangeConf{ + Pending: []string{"attaching"}, + Target: "attached", + Refresh: volumeAttachmentStateRefreshFunc(conn, vID, iID), + Timeout: 5 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for Volume (%s) to attach to Instance: %s, error:", + vID, iID, err) + } + d.SetId(volumeAttachmentID(name, vID, iID)) return resourceAwsVolumeAttachmentRead(d, meta) } +func volumeAttachmentStateRefreshFunc(conn *ec2.EC2, volumeID, instanceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + + request := &ec2.DescribeVolumesInput{ + VolumeIDs: []*string{aws.String(volumeID)}, + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("attachment.instance-id"), + Values: []*string{aws.String(instanceID)}, + }, + }, + } + + resp, err := conn.DescribeVolumes(request) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + return nil, "failed", fmt.Errorf("code: %s, message: %s", awsErr.Code(), awsErr.Message()) + } + return nil, "failed", err + } + + if len(resp.Volumes) > 0 { + v := resp.Volumes[0] + for _, a := range v.Attachments { + if a.InstanceID != nil && *a.InstanceID == instanceID { + return a, *a.State, nil + } + } + } + // assume detached if volume count is 0 + return 42, "detached", nil + } +} func resourceAwsVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn @@ -99,35 +150,35 @@ func resourceAwsVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) e func resourceAwsVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - volume := d.Get("volume_id").(string) - instance := d.Get("instance_id").(string) + vID := d.Get("volume_id").(string) + iID := d.Get("instance_id").(string) opts := &ec2.DetachVolumeInput{ Device: aws.String(d.Get("device_name").(string)), - InstanceID: aws.String(instance), - VolumeID: aws.String(volume), + InstanceID: aws.String(iID), + VolumeID: aws.String(vID), Force: aws.Boolean(d.Get("force_detach").(bool)), } - return resource.Retry(3*time.Minute, func() error { - resp, err := conn.DetachVolume(opts) - if err != nil { - awsErr, ok := err.(awserr.Error) - if ok && (awsErr.Code() == "IncorrectState" || awsErr.Code() == "InvalidVolume.NotFound") { - // volume attachment is not in a valid "attachment state" - return nil - } + _, err := conn.DetachVolume(opts) + stateConf := &resource.StateChangeConf{ + Pending: []string{"detaching"}, + Target: "detached", + Refresh: volumeAttachmentStateRefreshFunc(conn, vID, iID), + Timeout: 5 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } - return err - } - - if resp.State != nil && *resp.State == "detaching" { - return fmt.Errorf("waiting for volume %s to detach from instance %s", volume, instance) - } else if *resp.State == "detached" { - return nil - } - return fmt.Errorf("Error detaching volume %s from instance %s", volume, instance) - }) + log.Printf("[DEBUG] Detaching Volume (%s) from Instance (%s)", vID, iID) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for Volume (%s) to detach from Instance: %s", + vID, iID) + } + d.SetId("") + return nil } func volumeAttachmentID(name, volumeID, instanceID string) string { diff --git a/builtin/providers/aws/resource_aws_volume_attachment_test.go b/builtin/providers/aws/resource_aws_volume_attachment_test.go index dcbcea1f2..22df8c085 100644 --- a/builtin/providers/aws/resource_aws_volume_attachment_test.go +++ b/builtin/providers/aws/resource_aws_volume_attachment_test.go @@ -82,7 +82,7 @@ resource "aws_instance" "web" { resource "aws_ebs_volume" "example" { availability_zone = "us-west-2a" - size = 5 + size = 1 } resource "aws_volume_attachment" "ebs_att" { From 1f5c0385709d0669ace12726f64d8c76c873b364 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 28 May 2015 10:33:20 -0500 Subject: [PATCH 6/6] provider/aws: Document Volume Attachment --- .../aws/r/volume_attachment.html.markdown | 57 +++++++++++++++++++ website/source/layouts/aws.erb | 4 ++ 2 files changed, 61 insertions(+) create mode 100644 website/source/docs/providers/aws/r/volume_attachment.html.markdown diff --git a/website/source/docs/providers/aws/r/volume_attachment.html.markdown b/website/source/docs/providers/aws/r/volume_attachment.html.markdown new file mode 100644 index 000000000..d3421dc8c --- /dev/null +++ b/website/source/docs/providers/aws/r/volume_attachment.html.markdown @@ -0,0 +1,57 @@ +--- +layout: "aws" +page_title: "AWS: aws_volume_attachment" +sidebar_current: "docs-aws-resource-volume-attachment" +description: |- + Provides an AWS EBS Volume Attachment +--- + +# aws\_volume\_attachment + +Provides an AWS EBS Volume Attachment as a top level resource, to attach and +detach volumes from AWS Instances. + +## Example Usage + +``` +resource "aws_volume_attachment" "ebs_att" { + device_name = "/dev/sdh" + volume_id = "${aws_ebs_volume.example.id}" + instance_id = "${aws_instance.web.id}" +} + +resource "aws_instance" "web" { + ami = "ami-21f78e11" + availability_zone = "us-west-2a" + instance_type = "t1.micro" + tags { + Name = "HelloWorld" + } +} + +resource "aws_ebs_volume" "example" { + availability_zone = "us-west-2a" + size = 1 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `device_name` - (Required) The device name to expose to the instance (for +example, `/dev/sdh` or `xvdh`) +* `instance_id` - (Required) ID of the Instance to attach to +* `volume_id` - (Required) ID of the Volume to be attached +* `force_detach` - (Optional, Boolean) Set to `true` if you want to force the +volume to detach. Useful if previous attempts failed, but use this option only +as a last resort, as this can result in **data loss**. See +[Detaching an Amazon EBS Volume from an Instance][1] for more information. + +## Attributes Reference + +* `device_name` - The device name exposed to the instance +* `instance_id` - ID of the Instance +* `volume_id` - ID of the Volume + +[1]: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-detaching-volume.html diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index d2ccd1cf9..3b55600f0 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -180,6 +180,10 @@ aws_subnet + > + aws_volume_attachment + + > aws_vpc