github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/aws/resource_aws_ebs_volume.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/awserr" 10 "github.com/aws/aws-sdk-go/service/ec2" 11 12 "github.com/hashicorp/errwrap" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsEbsVolume() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsEbsVolumeCreate, 20 Read: resourceAwsEbsVolumeRead, 21 Update: resourceAWSEbsVolumeUpdate, 22 Delete: resourceAwsEbsVolumeDelete, 23 Importer: &schema.ResourceImporter{ 24 State: schema.ImportStatePassthrough, 25 }, 26 27 Schema: map[string]*schema.Schema{ 28 "availability_zone": { 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 }, 33 "encrypted": { 34 Type: schema.TypeBool, 35 Optional: true, 36 Computed: true, 37 ForceNew: true, 38 }, 39 "iops": { 40 Type: schema.TypeInt, 41 Optional: true, 42 Computed: true, 43 ForceNew: true, 44 }, 45 "kms_key_id": { 46 Type: schema.TypeString, 47 Optional: true, 48 Computed: true, 49 ForceNew: true, 50 ValidateFunc: validateArn, 51 }, 52 "size": { 53 Type: schema.TypeInt, 54 Optional: true, 55 Computed: true, 56 ForceNew: true, 57 }, 58 "snapshot_id": { 59 Type: schema.TypeString, 60 Optional: true, 61 Computed: true, 62 ForceNew: true, 63 }, 64 "type": { 65 Type: schema.TypeString, 66 Optional: true, 67 Computed: true, 68 ForceNew: true, 69 }, 70 "tags": tagsSchema(), 71 }, 72 } 73 } 74 75 func resourceAwsEbsVolumeCreate(d *schema.ResourceData, meta interface{}) error { 76 conn := meta.(*AWSClient).ec2conn 77 78 request := &ec2.CreateVolumeInput{ 79 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 80 } 81 if value, ok := d.GetOk("encrypted"); ok { 82 request.Encrypted = aws.Bool(value.(bool)) 83 } 84 if value, ok := d.GetOk("kms_key_id"); ok { 85 request.KmsKeyId = aws.String(value.(string)) 86 } 87 if value, ok := d.GetOk("size"); ok { 88 request.Size = aws.Int64(int64(value.(int))) 89 } 90 if value, ok := d.GetOk("snapshot_id"); ok { 91 request.SnapshotId = aws.String(value.(string)) 92 } 93 94 // IOPs are only valid, and required for, storage type io1. The current minimu 95 // is 100. Instead of a hard validation we we only apply the IOPs to the 96 // request if the type is io1, and log a warning otherwise. This allows users 97 // to "disable" iops. See https://github.com/hashicorp/terraform/pull/4146 98 var t string 99 if value, ok := d.GetOk("type"); ok { 100 t = value.(string) 101 request.VolumeType = aws.String(t) 102 } 103 104 iops := d.Get("iops").(int) 105 if t != "io1" && iops > 0 { 106 log.Printf("[WARN] IOPs is only valid for storate type io1 for EBS Volumes") 107 } else if t == "io1" { 108 // We add the iops value without validating it's size, to allow AWS to 109 // enforce a size requirement (currently 100) 110 request.Iops = aws.Int64(int64(iops)) 111 } 112 113 log.Printf( 114 "[DEBUG] EBS Volume create opts: %s", request) 115 result, err := conn.CreateVolume(request) 116 if err != nil { 117 return fmt.Errorf("Error creating EC2 volume: %s", err) 118 } 119 120 log.Println( 121 "[DEBUG] Waiting for Volume to become available") 122 123 stateConf := &resource.StateChangeConf{ 124 Pending: []string{"creating"}, 125 Target: []string{"available"}, 126 Refresh: volumeStateRefreshFunc(conn, *result.VolumeId), 127 Timeout: 5 * time.Minute, 128 Delay: 10 * time.Second, 129 MinTimeout: 3 * time.Second, 130 } 131 132 _, err = stateConf.WaitForState() 133 if err != nil { 134 return fmt.Errorf( 135 "Error waiting for Volume (%s) to become available: %s", 136 *result.VolumeId, err) 137 } 138 139 d.SetId(*result.VolumeId) 140 141 if _, ok := d.GetOk("tags"); ok { 142 if err := setTags(conn, d); err != nil { 143 return errwrap.Wrapf("Error setting tags for EBS Volume: {{err}}", err) 144 } 145 } 146 147 return readVolume(d, result) 148 } 149 150 func resourceAWSEbsVolumeUpdate(d *schema.ResourceData, meta interface{}) error { 151 conn := meta.(*AWSClient).ec2conn 152 if _, ok := d.GetOk("tags"); ok { 153 if err := setTags(conn, d); err != nil { 154 return errwrap.Wrapf("Error updating tags for EBS Volume: {{err}}", err) 155 } 156 } 157 return resourceAwsEbsVolumeRead(d, meta) 158 } 159 160 // volumeStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 161 // a the state of a Volume. Returns successfully when volume is available 162 func volumeStateRefreshFunc(conn *ec2.EC2, volumeID string) resource.StateRefreshFunc { 163 return func() (interface{}, string, error) { 164 resp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{ 165 VolumeIds: []*string{aws.String(volumeID)}, 166 }) 167 168 if err != nil { 169 if ec2err, ok := err.(awserr.Error); ok { 170 // Set this to nil as if we didn't find anything. 171 log.Printf("Error on Volume State Refresh: message: \"%s\", code:\"%s\"", ec2err.Message(), ec2err.Code()) 172 resp = nil 173 return nil, "", err 174 } else { 175 log.Printf("Error on Volume State Refresh: %s", err) 176 return nil, "", err 177 } 178 } 179 180 v := resp.Volumes[0] 181 return v, *v.State, nil 182 } 183 } 184 185 func resourceAwsEbsVolumeRead(d *schema.ResourceData, meta interface{}) error { 186 conn := meta.(*AWSClient).ec2conn 187 188 request := &ec2.DescribeVolumesInput{ 189 VolumeIds: []*string{aws.String(d.Id())}, 190 } 191 192 response, err := conn.DescribeVolumes(request) 193 if err != nil { 194 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVolume.NotFound" { 195 d.SetId("") 196 return nil 197 } 198 return fmt.Errorf("Error reading EC2 volume %s: %s", d.Id(), err) 199 } 200 201 return readVolume(d, response.Volumes[0]) 202 } 203 204 func resourceAwsEbsVolumeDelete(d *schema.ResourceData, meta interface{}) error { 205 conn := meta.(*AWSClient).ec2conn 206 207 return resource.Retry(5*time.Minute, func() *resource.RetryError { 208 request := &ec2.DeleteVolumeInput{ 209 VolumeId: aws.String(d.Id()), 210 } 211 _, err := conn.DeleteVolume(request) 212 if err == nil { 213 return nil 214 } 215 216 ebsErr, ok := err.(awserr.Error) 217 if ebsErr.Code() == "VolumeInUse" { 218 return resource.RetryableError(fmt.Errorf("EBS VolumeInUse - trying again while it detaches")) 219 } 220 221 if !ok { 222 return resource.NonRetryableError(err) 223 } 224 225 return resource.NonRetryableError(err) 226 }) 227 228 } 229 230 func readVolume(d *schema.ResourceData, volume *ec2.Volume) error { 231 d.SetId(*volume.VolumeId) 232 233 d.Set("availability_zone", *volume.AvailabilityZone) 234 if volume.Encrypted != nil { 235 d.Set("encrypted", *volume.Encrypted) 236 } 237 if volume.KmsKeyId != nil { 238 d.Set("kms_key_id", *volume.KmsKeyId) 239 } 240 if volume.Size != nil { 241 d.Set("size", *volume.Size) 242 } 243 if volume.SnapshotId != nil { 244 d.Set("snapshot_id", *volume.SnapshotId) 245 } 246 if volume.VolumeType != nil { 247 d.Set("type", *volume.VolumeType) 248 } 249 250 if volume.VolumeType != nil && *volume.VolumeType == "io1" { 251 // Only set the iops attribute if the volume type is io1. Setting otherwise 252 // can trigger a refresh/plan loop based on the computed value that is given 253 // from AWS, and prevent us from specifying 0 as a valid iops. 254 // See https://github.com/hashicorp/terraform/pull/4146 255 if volume.Iops != nil { 256 d.Set("iops", *volume.Iops) 257 } 258 } 259 260 if volume.Tags != nil { 261 d.Set("tags", tagsToMap(volume.Tags)) 262 } 263 264 return nil 265 }