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