github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_volume_attachment.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsVolumeAttachment() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsVolumeAttachmentCreate,
    20  		Read:   resourceAwsVolumeAttachmentRead,
    21  		Delete: resourceAwsVolumeAttachmentDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"device_name": {
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"instance_id": {
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  				ForceNew: true,
    34  			},
    35  
    36  			"volume_id": {
    37  				Type:     schema.TypeString,
    38  				Required: true,
    39  				ForceNew: true,
    40  			},
    41  
    42  			"force_detach": {
    43  				Type:     schema.TypeBool,
    44  				Optional: true,
    45  				Computed: true,
    46  			},
    47  			"skip_destroy": {
    48  				Type:     schema.TypeBool,
    49  				Optional: true,
    50  				Computed: true,
    51  			},
    52  		},
    53  	}
    54  }
    55  
    56  func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
    57  	conn := meta.(*AWSClient).ec2conn
    58  	name := d.Get("device_name").(string)
    59  	iID := d.Get("instance_id").(string)
    60  	vID := d.Get("volume_id").(string)
    61  
    62  	// Find out if the volume is already attached to the instance, in which case
    63  	// we have nothing to do
    64  	request := &ec2.DescribeVolumesInput{
    65  		VolumeIds: []*string{aws.String(vID)},
    66  		Filters: []*ec2.Filter{
    67  			&ec2.Filter{
    68  				Name:   aws.String("attachment.instance-id"),
    69  				Values: []*string{aws.String(iID)},
    70  			},
    71  			&ec2.Filter{
    72  				Name:   aws.String("attachment.device"),
    73  				Values: []*string{aws.String(name)},
    74  			},
    75  		},
    76  	}
    77  
    78  	vols, err := conn.DescribeVolumes(request)
    79  	if (err != nil) || (len(vols.Volumes) == 0) {
    80  		// This handles the situation where the instance is created by
    81  		// a spot request and whilst the request has been fulfilled the
    82  		// instance is not running yet
    83  		stateConf := &resource.StateChangeConf{
    84  			Pending:    []string{"pending"},
    85  			Target:     []string{"running"},
    86  			Refresh:    InstanceStateRefreshFunc(conn, iID),
    87  			Timeout:    10 * time.Minute,
    88  			Delay:      10 * time.Second,
    89  			MinTimeout: 3 * time.Second,
    90  		}
    91  
    92  		_, err = stateConf.WaitForState()
    93  		if err != nil {
    94  			return fmt.Errorf(
    95  				"Error waiting for instance (%s) to become ready: %s",
    96  				iID, err)
    97  		}
    98  
    99  		// not attached
   100  		opts := &ec2.AttachVolumeInput{
   101  			Device:     aws.String(name),
   102  			InstanceId: aws.String(iID),
   103  			VolumeId:   aws.String(vID),
   104  		}
   105  
   106  		log.Printf("[DEBUG] Attaching Volume (%s) to Instance (%s)", vID, iID)
   107  		_, err := conn.AttachVolume(opts)
   108  		if err != nil {
   109  			if awsErr, ok := err.(awserr.Error); ok {
   110  				return fmt.Errorf("[WARN] Error attaching volume (%s) to instance (%s), message: \"%s\", code: \"%s\"",
   111  					vID, iID, awsErr.Message(), awsErr.Code())
   112  			}
   113  			return err
   114  		}
   115  	}
   116  
   117  	stateConf := &resource.StateChangeConf{
   118  		Pending:    []string{"attaching"},
   119  		Target:     []string{"attached"},
   120  		Refresh:    volumeAttachmentStateRefreshFunc(conn, vID, iID),
   121  		Timeout:    5 * time.Minute,
   122  		Delay:      10 * time.Second,
   123  		MinTimeout: 3 * time.Second,
   124  	}
   125  
   126  	_, err = stateConf.WaitForState()
   127  	if err != nil {
   128  		return fmt.Errorf(
   129  			"Error waiting for Volume (%s) to attach to Instance: %s, error: %s",
   130  			vID, iID, err)
   131  	}
   132  
   133  	d.SetId(volumeAttachmentID(name, vID, iID))
   134  	return resourceAwsVolumeAttachmentRead(d, meta)
   135  }
   136  
   137  func volumeAttachmentStateRefreshFunc(conn *ec2.EC2, volumeID, instanceID string) resource.StateRefreshFunc {
   138  	return func() (interface{}, string, error) {
   139  
   140  		request := &ec2.DescribeVolumesInput{
   141  			VolumeIds: []*string{aws.String(volumeID)},
   142  			Filters: []*ec2.Filter{
   143  				&ec2.Filter{
   144  					Name:   aws.String("attachment.instance-id"),
   145  					Values: []*string{aws.String(instanceID)},
   146  				},
   147  			},
   148  		}
   149  
   150  		resp, err := conn.DescribeVolumes(request)
   151  		if err != nil {
   152  			if awsErr, ok := err.(awserr.Error); ok {
   153  				return nil, "failed", fmt.Errorf("code: %s, message: %s", awsErr.Code(), awsErr.Message())
   154  			}
   155  			return nil, "failed", err
   156  		}
   157  
   158  		if len(resp.Volumes) > 0 {
   159  			v := resp.Volumes[0]
   160  			for _, a := range v.Attachments {
   161  				if a.InstanceId != nil && *a.InstanceId == instanceID {
   162  					return a, *a.State, nil
   163  				}
   164  			}
   165  		}
   166  		// assume detached if volume count is 0
   167  		return 42, "detached", nil
   168  	}
   169  }
   170  func resourceAwsVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error {
   171  	conn := meta.(*AWSClient).ec2conn
   172  
   173  	request := &ec2.DescribeVolumesInput{
   174  		VolumeIds: []*string{aws.String(d.Get("volume_id").(string))},
   175  		Filters: []*ec2.Filter{
   176  			&ec2.Filter{
   177  				Name:   aws.String("attachment.instance-id"),
   178  				Values: []*string{aws.String(d.Get("instance_id").(string))},
   179  			},
   180  		},
   181  	}
   182  
   183  	vols, err := conn.DescribeVolumes(request)
   184  	if err != nil {
   185  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVolume.NotFound" {
   186  			d.SetId("")
   187  			return nil
   188  		}
   189  		return fmt.Errorf("Error reading EC2 volume %s for instance: %s: %#v", d.Get("volume_id").(string), d.Get("instance_id").(string), err)
   190  	}
   191  
   192  	if len(vols.Volumes) == 0 || *vols.Volumes[0].State == "available" {
   193  		log.Printf("[DEBUG] Volume Attachment (%s) not found, removing from state", d.Id())
   194  		d.SetId("")
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func resourceAwsVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
   201  	conn := meta.(*AWSClient).ec2conn
   202  
   203  	if _, ok := d.GetOk("skip_destroy"); ok {
   204  		log.Printf("[INFO] Found skip_destroy to be true, removing attachment %q from state", d.Id())
   205  		d.SetId("")
   206  		return nil
   207  	}
   208  
   209  	vID := d.Get("volume_id").(string)
   210  	iID := d.Get("instance_id").(string)
   211  
   212  	opts := &ec2.DetachVolumeInput{
   213  		Device:     aws.String(d.Get("device_name").(string)),
   214  		InstanceId: aws.String(iID),
   215  		VolumeId:   aws.String(vID),
   216  		Force:      aws.Bool(d.Get("force_detach").(bool)),
   217  	}
   218  
   219  	_, err := conn.DetachVolume(opts)
   220  	if err != nil {
   221  		return fmt.Errorf("Failed to detach Volume (%s) from Instance (%s): %s",
   222  			vID, iID, err)
   223  	}
   224  	stateConf := &resource.StateChangeConf{
   225  		Pending:    []string{"detaching"},
   226  		Target:     []string{"detached"},
   227  		Refresh:    volumeAttachmentStateRefreshFunc(conn, vID, iID),
   228  		Timeout:    5 * time.Minute,
   229  		Delay:      10 * time.Second,
   230  		MinTimeout: 3 * time.Second,
   231  	}
   232  
   233  	log.Printf("[DEBUG] Detaching Volume (%s) from Instance (%s)", vID, iID)
   234  	_, err = stateConf.WaitForState()
   235  	if err != nil {
   236  		return fmt.Errorf(
   237  			"Error waiting for Volume (%s) to detach from Instance: %s",
   238  			vID, iID)
   239  	}
   240  	d.SetId("")
   241  	return nil
   242  }
   243  
   244  func volumeAttachmentID(name, volumeID, instanceID string) string {
   245  	var buf bytes.Buffer
   246  	buf.WriteString(fmt.Sprintf("%s-", name))
   247  	buf.WriteString(fmt.Sprintf("%s-", instanceID))
   248  	buf.WriteString(fmt.Sprintf("%s-", volumeID))
   249  
   250  	return fmt.Sprintf("vai-%d", hashcode.String(buf.String()))
   251  }