github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/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  		// not attached
    81  		opts := &ec2.AttachVolumeInput{
    82  			Device:     aws.String(name),
    83  			InstanceId: aws.String(iID),
    84  			VolumeId:   aws.String(vID),
    85  		}
    86  
    87  		log.Printf("[DEBUG] Attaching Volume (%s) to Instance (%s)", vID, iID)
    88  		_, err := conn.AttachVolume(opts)
    89  		if err != nil {
    90  			if awsErr, ok := err.(awserr.Error); ok {
    91  				return fmt.Errorf("[WARN] Error attaching volume (%s) to instance (%s), message: \"%s\", code: \"%s\"",
    92  					vID, iID, awsErr.Message(), awsErr.Code())
    93  			}
    94  			return err
    95  		}
    96  	}
    97  
    98  	stateConf := &resource.StateChangeConf{
    99  		Pending:    []string{"attaching"},
   100  		Target:     []string{"attached"},
   101  		Refresh:    volumeAttachmentStateRefreshFunc(conn, vID, iID),
   102  		Timeout:    5 * time.Minute,
   103  		Delay:      10 * time.Second,
   104  		MinTimeout: 3 * time.Second,
   105  	}
   106  
   107  	_, err = stateConf.WaitForState()
   108  	if err != nil {
   109  		return fmt.Errorf(
   110  			"Error waiting for Volume (%s) to attach to Instance: %s, error: %s",
   111  			vID, iID, err)
   112  	}
   113  
   114  	d.SetId(volumeAttachmentID(name, vID, iID))
   115  	return resourceAwsVolumeAttachmentRead(d, meta)
   116  }
   117  
   118  func volumeAttachmentStateRefreshFunc(conn *ec2.EC2, volumeID, instanceID string) resource.StateRefreshFunc {
   119  	return func() (interface{}, string, error) {
   120  
   121  		request := &ec2.DescribeVolumesInput{
   122  			VolumeIds: []*string{aws.String(volumeID)},
   123  			Filters: []*ec2.Filter{
   124  				&ec2.Filter{
   125  					Name:   aws.String("attachment.instance-id"),
   126  					Values: []*string{aws.String(instanceID)},
   127  				},
   128  			},
   129  		}
   130  
   131  		resp, err := conn.DescribeVolumes(request)
   132  		if err != nil {
   133  			if awsErr, ok := err.(awserr.Error); ok {
   134  				return nil, "failed", fmt.Errorf("code: %s, message: %s", awsErr.Code(), awsErr.Message())
   135  			}
   136  			return nil, "failed", err
   137  		}
   138  
   139  		if len(resp.Volumes) > 0 {
   140  			v := resp.Volumes[0]
   141  			for _, a := range v.Attachments {
   142  				if a.InstanceId != nil && *a.InstanceId == instanceID {
   143  					return a, *a.State, nil
   144  				}
   145  			}
   146  		}
   147  		// assume detached if volume count is 0
   148  		return 42, "detached", nil
   149  	}
   150  }
   151  func resourceAwsVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error {
   152  	conn := meta.(*AWSClient).ec2conn
   153  
   154  	request := &ec2.DescribeVolumesInput{
   155  		VolumeIds: []*string{aws.String(d.Get("volume_id").(string))},
   156  		Filters: []*ec2.Filter{
   157  			&ec2.Filter{
   158  				Name:   aws.String("attachment.instance-id"),
   159  				Values: []*string{aws.String(d.Get("instance_id").(string))},
   160  			},
   161  		},
   162  	}
   163  
   164  	vols, err := conn.DescribeVolumes(request)
   165  	if err != nil {
   166  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVolume.NotFound" {
   167  			d.SetId("")
   168  			return nil
   169  		}
   170  		return fmt.Errorf("Error reading EC2 volume %s for instance: %s: %#v", d.Get("volume_id").(string), d.Get("instance_id").(string), err)
   171  	}
   172  
   173  	if len(vols.Volumes) == 0 || *vols.Volumes[0].State == "available" {
   174  		log.Printf("[DEBUG] Volume Attachment (%s) not found, removing from state", d.Id())
   175  		d.SetId("")
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func resourceAwsVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
   182  	conn := meta.(*AWSClient).ec2conn
   183  
   184  	if _, ok := d.GetOk("skip_destroy"); ok {
   185  		log.Printf("[INFO] Found skip_destroy to be true, removing attachment %q from state", d.Id())
   186  		d.SetId("")
   187  		return nil
   188  	}
   189  
   190  	vID := d.Get("volume_id").(string)
   191  	iID := d.Get("instance_id").(string)
   192  
   193  	opts := &ec2.DetachVolumeInput{
   194  		Device:     aws.String(d.Get("device_name").(string)),
   195  		InstanceId: aws.String(iID),
   196  		VolumeId:   aws.String(vID),
   197  		Force:      aws.Bool(d.Get("force_detach").(bool)),
   198  	}
   199  
   200  	_, err := conn.DetachVolume(opts)
   201  	if err != nil {
   202  		return fmt.Errorf("Failed to detach Volume (%s) from Instance (%s): %s",
   203  			vID, iID, err)
   204  	}
   205  	stateConf := &resource.StateChangeConf{
   206  		Pending:    []string{"detaching"},
   207  		Target:     []string{"detached"},
   208  		Refresh:    volumeAttachmentStateRefreshFunc(conn, vID, iID),
   209  		Timeout:    5 * time.Minute,
   210  		Delay:      10 * time.Second,
   211  		MinTimeout: 3 * time.Second,
   212  	}
   213  
   214  	log.Printf("[DEBUG] Detaching Volume (%s) from Instance (%s)", vID, iID)
   215  	_, err = stateConf.WaitForState()
   216  	if err != nil {
   217  		return fmt.Errorf(
   218  			"Error waiting for Volume (%s) to detach from Instance: %s",
   219  			vID, iID)
   220  	}
   221  	d.SetId("")
   222  	return nil
   223  }
   224  
   225  func volumeAttachmentID(name, volumeID, instanceID string) string {
   226  	var buf bytes.Buffer
   227  	buf.WriteString(fmt.Sprintf("%s-", name))
   228  	buf.WriteString(fmt.Sprintf("%s-", instanceID))
   229  	buf.WriteString(fmt.Sprintf("%s-", volumeID))
   230  
   231  	return fmt.Sprintf("vai-%d", hashcode.String(buf.String()))
   232  }