From 415961a1d037e5b9a239b698211daf0d7ae164cd Mon Sep 17 00:00:00 2001 From: Doug Neal Date: Wed, 1 Feb 2017 15:41:53 +0000 Subject: [PATCH] Succeed creating aws_volume_attachment if identical attachment exists (#11060) If an `aws_volume_attachment` is identical to one that already exists in the API, don't attempt to re-create it (which fails), simply act as though the creation command had already been run and continue. This allows Terraform to cleanly recover from a situation where a volume attachment action hangs indefinitely, possibly due to a bad instance state, requiring manual intervention such as an instance reboot. In such a situation, Terraform believes the attachment has failed, when in fact it succeeded after the timeout had expired. On the subsequent retry run, attempting to re-create the attachment will fail outright, due to the AttachVolume API call being non-idempotent. This patch implements the idempotency client-side by matching the (name, vID, iID) tuple. Note that volume attachments are not assigned an ID by the API. --- .../aws/resource_aws_volume_attachment.go | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/builtin/providers/aws/resource_aws_volume_attachment.go b/builtin/providers/aws/resource_aws_volume_attachment.go index bde1a7c1f53d..b469417d4b25 100644 --- a/builtin/providers/aws/resource_aws_volume_attachment.go +++ b/builtin/providers/aws/resource_aws_volume_attachment.go @@ -59,20 +59,40 @@ func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) 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), + // Find out if the volume is already attached to the instance, in which case + // we have nothing to do + request := &ec2.DescribeVolumesInput{ + VolumeIds: []*string{aws.String(vID)}, + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("attachment.instance-id"), + Values: []*string{aws.String(iID)}, + }, + &ec2.Filter{ + Name: aws.String("attachment.device"), + Values: []*string{aws.String(name)}, + }, + }, } - 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 { - return fmt.Errorf("[WARN] Error attaching volume (%s) to instance (%s), message: \"%s\", code: \"%s\"", - vID, iID, awsErr.Message(), awsErr.Code()) + vols, err := conn.DescribeVolumes(request) + if (err != nil) || (len(vols.Volumes) == 0) { + // not attached + opts := &ec2.AttachVolumeInput{ + Device: aws.String(name), + InstanceId: aws.String(iID), + 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 { + return fmt.Errorf("[WARN] Error attaching volume (%s) to instance (%s), message: \"%s\", code: \"%s\"", + vID, iID, awsErr.Message(), awsErr.Code()) + } + return err } - return err } stateConf := &resource.StateChangeConf{