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 }