github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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: %#v", 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  }