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  }