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