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 }