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  }