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 }