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