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  }