github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"instance_id": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  				ForceNew: true,
    34  			},
    35  
    36  			"volume_id": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Required: true,
    39  				ForceNew: true,
    40  			},
    41  
    42  			"force_detach": &schema.Schema{
    43  				Type:     schema.TypeBool,
    44  				Optional: true,
    45  				Computed: true,
    46  			},
    47  		},
    48  	}
    49  }
    50  
    51  func resourceAwsVolumeAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
    52  	conn := meta.(*AWSClient).ec2conn
    53  	name := d.Get("device_name").(string)
    54  	iID := d.Get("instance_id").(string)
    55  	vID := d.Get("volume_id").(string)
    56  
    57  	opts := &ec2.AttachVolumeInput{
    58  		Device:     aws.String(name),
    59  		InstanceId: aws.String(iID),
    60  		VolumeId:   aws.String(vID),
    61  	}
    62  
    63  	log.Printf("[DEBUG] Attaching Volume (%s) to Instance (%s)", vID, iID)
    64  	_, err := conn.AttachVolume(opts)
    65  	if err != nil {
    66  		if awsErr, ok := err.(awserr.Error); ok {
    67  			return fmt.Errorf("[WARN] Error attaching volume (%s) to instance (%s), message: \"%s\", code: \"%s\"",
    68  				vID, iID, awsErr.Message(), awsErr.Code())
    69  		}
    70  		return err
    71  	}
    72  
    73  	stateConf := &resource.StateChangeConf{
    74  		Pending:    []string{"attaching"},
    75  		Target:     []string{"attached"},
    76  		Refresh:    volumeAttachmentStateRefreshFunc(conn, vID, iID),
    77  		Timeout:    5 * time.Minute,
    78  		Delay:      10 * time.Second,
    79  		MinTimeout: 3 * time.Second,
    80  	}
    81  
    82  	_, err = stateConf.WaitForState()
    83  	if err != nil {
    84  		return fmt.Errorf(
    85  			"Error waiting for Volume (%s) to attach to Instance: %s, error: %s",
    86  			vID, iID, err)
    87  	}
    88  
    89  	d.SetId(volumeAttachmentID(name, vID, iID))
    90  	return resourceAwsVolumeAttachmentRead(d, meta)
    91  }
    92  
    93  func volumeAttachmentStateRefreshFunc(conn *ec2.EC2, volumeID, instanceID string) resource.StateRefreshFunc {
    94  	return func() (interface{}, string, error) {
    95  
    96  		request := &ec2.DescribeVolumesInput{
    97  			VolumeIds: []*string{aws.String(volumeID)},
    98  			Filters: []*ec2.Filter{
    99  				&ec2.Filter{
   100  					Name:   aws.String("attachment.instance-id"),
   101  					Values: []*string{aws.String(instanceID)},
   102  				},
   103  			},
   104  		}
   105  
   106  		resp, err := conn.DescribeVolumes(request)
   107  		if err != nil {
   108  			if awsErr, ok := err.(awserr.Error); ok {
   109  				return nil, "failed", fmt.Errorf("code: %s, message: %s", awsErr.Code(), awsErr.Message())
   110  			}
   111  			return nil, "failed", err
   112  		}
   113  
   114  		if len(resp.Volumes) > 0 {
   115  			v := resp.Volumes[0]
   116  			for _, a := range v.Attachments {
   117  				if a.InstanceId != nil && *a.InstanceId == instanceID {
   118  					return a, *a.State, nil
   119  				}
   120  			}
   121  		}
   122  		// assume detached if volume count is 0
   123  		return 42, "detached", nil
   124  	}
   125  }
   126  func resourceAwsVolumeAttachmentRead(d *schema.ResourceData, meta interface{}) error {
   127  	conn := meta.(*AWSClient).ec2conn
   128  
   129  	request := &ec2.DescribeVolumesInput{
   130  		VolumeIds: []*string{aws.String(d.Get("volume_id").(string))},
   131  		Filters: []*ec2.Filter{
   132  			&ec2.Filter{
   133  				Name:   aws.String("attachment.instance-id"),
   134  				Values: []*string{aws.String(d.Get("instance_id").(string))},
   135  			},
   136  		},
   137  	}
   138  
   139  	vols, err := conn.DescribeVolumes(request)
   140  	if err != nil {
   141  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVolume.NotFound" {
   142  			d.SetId("")
   143  			return nil
   144  		}
   145  		return fmt.Errorf("Error reading EC2 volume %s for instance: %s: %#v", d.Get("volume_id").(string), d.Get("instance_id").(string), err)
   146  	}
   147  
   148  	if len(vols.Volumes) == 0 || *vols.Volumes[0].State == "available" {
   149  		log.Printf("[DEBUG] Volume Attachment (%s) not found, removing from state", d.Id())
   150  		d.SetId("")
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  func resourceAwsVolumeAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
   157  	conn := meta.(*AWSClient).ec2conn
   158  
   159  	vID := d.Get("volume_id").(string)
   160  	iID := d.Get("instance_id").(string)
   161  
   162  	opts := &ec2.DetachVolumeInput{
   163  		Device:     aws.String(d.Get("device_name").(string)),
   164  		InstanceId: aws.String(iID),
   165  		VolumeId:   aws.String(vID),
   166  		Force:      aws.Bool(d.Get("force_detach").(bool)),
   167  	}
   168  
   169  	_, err := conn.DetachVolume(opts)
   170  	if err != nil {
   171  		return fmt.Errorf("Failed to detach Volume (%s) from Instance (%s): %s",
   172  			vID, iID, err)
   173  	}
   174  	stateConf := &resource.StateChangeConf{
   175  		Pending:    []string{"detaching"},
   176  		Target:     []string{"detached"},
   177  		Refresh:    volumeAttachmentStateRefreshFunc(conn, vID, iID),
   178  		Timeout:    5 * time.Minute,
   179  		Delay:      10 * time.Second,
   180  		MinTimeout: 3 * time.Second,
   181  	}
   182  
   183  	log.Printf("[DEBUG] Detaching Volume (%s) from Instance (%s)", vID, iID)
   184  	_, err = stateConf.WaitForState()
   185  	if err != nil {
   186  		return fmt.Errorf(
   187  			"Error waiting for Volume (%s) to detach from Instance: %s",
   188  			vID, iID)
   189  	}
   190  	d.SetId("")
   191  	return nil
   192  }
   193  
   194  func volumeAttachmentID(name, volumeID, instanceID string) string {
   195  	var buf bytes.Buffer
   196  	buf.WriteString(fmt.Sprintf("%s-", name))
   197  	buf.WriteString(fmt.Sprintf("%s-", instanceID))
   198  	buf.WriteString(fmt.Sprintf("%s-", volumeID))
   199  
   200  	return fmt.Sprintf("vai-%d", hashcode.String(buf.String()))
   201  }