diff --git a/aws/resource_aws_ebs_volume.go b/aws/resource_aws_ebs_volume.go index 8013ed5dc9d3..433e6c352854 100644 --- a/aws/resource_aws_ebs_volume.go +++ b/aws/resource_aws_ebs_volume.go @@ -66,6 +66,10 @@ func resourceAwsEbsVolume() *schema.Resource { Computed: true, ForceNew: true, }, + "termination_snapshot_name": { + Type: schema.TypeString, + Optional: true, + }, "type": { Type: schema.TypeString, Optional: true, @@ -117,7 +121,7 @@ func resourceAwsEbsVolumeCreate(d *schema.ResourceData, meta interface{}) error if t != "io1" && iops > 0 { log.Printf("[WARN] IOPs is only valid for storate type io1 for EBS Volumes") } else if t == "io1" { - // We add the iops value without validating it's size, to allow AWS to + // We add the iops value without validating its size, to allow AWS to // enforce a size requirement (currently 100) request.Iops = aws.Int64(int64(iops)) } @@ -280,6 +284,43 @@ func resourceAwsEbsVolumeRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsEbsVolumeDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn + tsn := d.Get("termination_snapshot_name") + if tsn != "" { + log.Println("[DEBUG] Termination snapshot configured - waiting for snapshot to complete before deleting the volume") + + opts := ec2.CreateSnapshotInput{ + VolumeId: aws.String(d.Id()), + Description: aws.String(tsn.(string)), + } + + res, err := conn.CreateSnapshot(&opts) + if err != nil { + return err + } + + tags := ec2.CreateTagsInput{ + Resources: []*string{res.SnapshotId}, + Tags: []*ec2.Tag{ + { + Key: aws.String("Name"), + Value: aws.String(tsn.(string)), + }, + }, + } + + _, err = conn.CreateTags(&tags) + if err != nil { + return err + } + + rd := &schema.ResourceData{} + rd.SetId(*res.SnapshotId) + err = resourceAwsEbsSnapshotWaitForAvailable(rd, conn) + if err != nil { + return err + } + } + input := &ec2.DeleteVolumeInput{ VolumeId: aws.String(d.Id()), } diff --git a/aws/resource_aws_ebs_volume_test.go b/aws/resource_aws_ebs_volume_test.go index b1633080e3dd..2e15ee4d0f15 100644 --- a/aws/resource_aws_ebs_volume_test.go +++ b/aws/resource_aws_ebs_volume_test.go @@ -311,6 +311,42 @@ func TestAccAWSEBSVolume_withTags(t *testing.T) { }) } +func TestAccAWSEBSVolume_terminationSnapshot(t *testing.T) { + var v ec2.Volume + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_ebs_volume.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckTerminationSnapshot("tf-acc-test-ebs-volume-test"), + Steps: []resource.TestStep{ + { + Config: testAccAwsEbsVolumeConfigWithTerminationSnapshot, + Check: resource.ComposeTestCheckFunc( + testAccCheckVolumeExists("aws_ebs_volume.test", &v), + ), + }, + }, + }) +} + +func TestAccAWSEBSVolume_noTerminationSnapshot(t *testing.T) { + var v ec2.Volume + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_ebs_volume.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckNoTerminationSnapshot, + Steps: []resource.TestStep{ + { + Config: testAccAwsEbsVolumeConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVolumeExists("aws_ebs_volume.test", &v), + ), + }, + }, + }) +} + func testAccCheckVolumeDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -371,6 +407,66 @@ func testAccCheckVolumeExists(n string, v *ec2.Volume) resource.TestCheckFunc { } } +func testAccCheckTerminationSnapshot(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + out, err := conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("tag:Name"), + Values: []*string{aws.String(n)}, + }, + }, + }) + if err != nil { + return fmt.Errorf("Error finding final snapshot %s: %s", n, err) + } + + if out.Snapshots != nil && len(out.Snapshots) > 0 { + // Snapshot found - delete it + _, err := conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{ + SnapshotId: out.Snapshots[0].SnapshotId, + }) + if err != nil { + return err + } + } else { + // Snapshot not present in response + return fmt.Errorf("Snapshot %s not found", n) + } + + return nil + } +} + +func testAccCheckNoTerminationSnapshot(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ebs_volume" { + continue + } + + resp, err := conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("volume-id"), + Values: []*string{aws.String(rs.Primary.ID)}}, + }, + }) + if err != nil { + return err + } + + if len(resp.Snapshots) > 0 { + return fmt.Errorf("Termination snapshot for volume %v found and it shouldn't have been", rs.Primary.ID) + } + } + + return nil +} + const testAccAwsEbsVolumeConfig = ` data "aws_availability_zones" "available" {} @@ -608,3 +704,14 @@ resource "aws_ebs_volume" "test" { } } ` +const testAccAwsEbsVolumeConfigWithTerminationSnapshot = ` +resource "aws_ebs_volume" "test" { + availability_zone = "us-west-2a" + type = "gp2" + size = 1 + termination_snapshot_name = "tf-acc-test-ebs-volume-test" + tags = { + Name = "tf-acc-test-ebs-volume-test" + } +} +` diff --git a/website/docs/r/ebs_volume.html.markdown b/website/docs/r/ebs_volume.html.markdown index 518b3e1bd429..c7107a6a59d2 100644 --- a/website/docs/r/ebs_volume.html.markdown +++ b/website/docs/r/ebs_volume.html.markdown @@ -33,7 +33,8 @@ The following arguments are supported: * `encrypted` - (Optional) If true, the disk will be encrypted. * `iops` - (Optional) The amount of IOPS to provision for the disk. * `size` - (Optional) The size of the drive in GiBs. -* `snapshot_id` (Optional) A snapshot to base the EBS volume off of. +* `snapshot_id` - (Optional) A snapshot to base the EBS volume off of. +* `termination_snapshot_name` - (Optional) Create an EBS snapshot with this name before the volume is deleted. If omitted, no snapshot will be created. * `type` - (Optional) The type of EBS volume. Can be "standard", "gp2", "io1", "sc1" or "st1" (Default: "standard"). * `kms_key_id` - (Optional) The ARN for the KMS encryption key. When specifying `kms_key_id`, `encrypted` needs to be set to true. * `tags` - (Optional) A mapping of tags to assign to the resource.