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