provider/aws: Fix AMI creation from snapshot issue
Previously the AMI creation accepted a static value for the AMI's block device's volume size. This change allows the user to omit the `volume_size` attribute, in order to mimic the AWS API behavior, which will use the EBS Volume's size. Also fixes a potential panic case when setting `iops` on the AMI's block device. The `aws_ami` resource previously didn't have any acceptance tests, adds two acceptance tests and a full testing suite for the `aws_ami` resource, so further tests can be written, as well as expansion upon the other `aws_ami_*` acceptance tests ``` $ make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSAMI_' ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2017/02/09 20:18:22 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSAMI_ -timeout 120m === RUN TestAccAWSAMI_basic --- PASS: TestAccAWSAMI_basic (44.21s) === RUN TestAccAWSAMI_snapshotSize --- PASS: TestAccAWSAMI_snapshotSize (45.08s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 89.320s ```
This commit is contained in:
parent
3b372b649b
commit
fcec0a9f3d
|
@ -62,12 +62,19 @@ func resourceAwsAmiCreate(d *schema.ResourceData, meta interface{}) error {
|
|||
DeviceName: aws.String(ebsBlockDev["device_name"].(string)),
|
||||
Ebs: &ec2.EbsBlockDevice{
|
||||
DeleteOnTermination: aws.Bool(ebsBlockDev["delete_on_termination"].(bool)),
|
||||
VolumeSize: aws.Int64(int64(ebsBlockDev["volume_size"].(int))),
|
||||
VolumeType: aws.String(ebsBlockDev["volume_type"].(string)),
|
||||
// VolumeSize: aws.Int64(int64(ebsBlockDev["volume_size"].(int))),
|
||||
VolumeType: aws.String(ebsBlockDev["volume_type"].(string)),
|
||||
},
|
||||
}
|
||||
if iops := ebsBlockDev["iops"].(int); iops != 0 {
|
||||
blockDev.Ebs.Iops = aws.Int64(int64(iops))
|
||||
if iops, ok := ebsBlockDev["iops"]; ok {
|
||||
if iop := iops.(int); iop != 0 {
|
||||
blockDev.Ebs.Iops = aws.Int64(int64(iop))
|
||||
}
|
||||
}
|
||||
if size, ok := ebsBlockDev["volume_size"]; ok {
|
||||
if s := size.(int); s != 0 {
|
||||
blockDev.Ebs.VolumeSize = aws.Int64(int64(s))
|
||||
}
|
||||
}
|
||||
encrypted := ebsBlockDev["encrypted"].(bool)
|
||||
if snapshotId := ebsBlockDev["snapshot_id"].(string); snapshotId != "" {
|
||||
|
@ -353,58 +360,58 @@ func resourceAwsAmiCommonSchema(computed bool) map[string]*schema.Schema {
|
|||
}
|
||||
|
||||
return map[string]*schema.Schema{
|
||||
"id": &schema.Schema{
|
||||
"id": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"image_location": &schema.Schema{
|
||||
"image_location": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: true,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"architecture": &schema.Schema{
|
||||
"architecture": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
Default: architectureDefault,
|
||||
},
|
||||
"description": &schema.Schema{
|
||||
"description": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"kernel_id": &schema.Schema{
|
||||
"kernel_id": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"ramdisk_id": &schema.Schema{
|
||||
"ramdisk_id": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"root_device_name": &schema.Schema{
|
||||
"root_device_name": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
"sriov_net_support": &schema.Schema{
|
||||
"sriov_net_support": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
Default: sriovNetSupportDefault,
|
||||
},
|
||||
"virtualization_type": &schema.Schema{
|
||||
"virtualization_type": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
|
@ -419,13 +426,13 @@ func resourceAwsAmiCommonSchema(computed bool) map[string]*schema.Schema {
|
|||
// on which root device attributes can be overridden for an instance to
|
||||
// not apply when registering an AMI.
|
||||
|
||||
"ebs_block_device": &schema.Schema{
|
||||
"ebs_block_device": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"delete_on_termination": &schema.Schema{
|
||||
"delete_on_termination": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: !computed,
|
||||
Default: deleteEbsOnTerminationDefault,
|
||||
|
@ -433,42 +440,42 @@ func resourceAwsAmiCommonSchema(computed bool) map[string]*schema.Schema {
|
|||
Computed: computed,
|
||||
},
|
||||
|
||||
"device_name": &schema.Schema{
|
||||
"device_name": {
|
||||
Type: schema.TypeString,
|
||||
Required: !computed,
|
||||
ForceNew: !computed,
|
||||
Computed: computed,
|
||||
},
|
||||
|
||||
"encrypted": &schema.Schema{
|
||||
"encrypted": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"iops": &schema.Schema{
|
||||
"iops": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"snapshot_id": &schema.Schema{
|
||||
"snapshot_id": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"volume_size": &schema.Schema{
|
||||
"volume_size": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: !computed,
|
||||
Computed: true,
|
||||
ForceNew: !computed,
|
||||
},
|
||||
|
||||
"volume_type": &schema.Schema{
|
||||
"volume_type": {
|
||||
Type: schema.TypeString,
|
||||
Optional: !computed,
|
||||
Computed: computed,
|
||||
|
@ -486,20 +493,20 @@ func resourceAwsAmiCommonSchema(computed bool) map[string]*schema.Schema {
|
|||
},
|
||||
},
|
||||
|
||||
"ephemeral_block_device": &schema.Schema{
|
||||
"ephemeral_block_device": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"device_name": &schema.Schema{
|
||||
"device_name": {
|
||||
Type: schema.TypeString,
|
||||
Required: !computed,
|
||||
Computed: computed,
|
||||
},
|
||||
|
||||
"virtual_name": &schema.Schema{
|
||||
"virtual_name": {
|
||||
Type: schema.TypeString,
|
||||
Required: !computed,
|
||||
Computed: computed,
|
||||
|
@ -521,7 +528,7 @@ func resourceAwsAmiCommonSchema(computed bool) map[string]*schema.Schema {
|
|||
// resources record that they implicitly created new EBS snapshots that we should
|
||||
// now manage. Not set by aws_ami, since the snapshots used there are presumed to
|
||||
// be independently managed.
|
||||
"manage_ebs_snapshots": &schema.Schema{
|
||||
"manage_ebs_snapshots": {
|
||||
Type: schema.TypeBool,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
|
|
|
@ -1,8 +1,230 @@
|
|||
package aws
|
||||
|
||||
// FIXME: The aws_ami resource doesn't currently have any acceptance tests,
|
||||
// since creating an AMI requires having an EBS snapshot and we don't yet
|
||||
// have a resource type for creating those.
|
||||
// Once there is an aws_ebs_snapshot resource we can use it to implement
|
||||
// a reasonable acceptance test for aws_ami. Until then it's necessary to
|
||||
// test manually using a pre-existing EBS snapshot.
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccAWSAMI_basic(t *testing.T) {
|
||||
var ami ec2.Image
|
||||
rInt := acctest.RandInt()
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAmiDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccAmiConfig_basic(rInt),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAmiExists("aws_ami.foo", &ami),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_ami.foo", "name", fmt.Sprintf("tf-testing-%d", rInt)),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSAMI_snapshotSize(t *testing.T) {
|
||||
var ami ec2.Image
|
||||
var bd ec2.BlockDeviceMapping
|
||||
rInt := acctest.RandInt()
|
||||
|
||||
expectedDevice := &ec2.EbsBlockDevice{
|
||||
DeleteOnTermination: aws.Bool(true),
|
||||
Encrypted: aws.Bool(false),
|
||||
Iops: aws.Int64(0),
|
||||
VolumeSize: aws.Int64(20),
|
||||
VolumeType: aws.String("standard"),
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckAmiDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccAmiConfig_snapshotSize(rInt),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckAmiExists("aws_ami.foo", &ami),
|
||||
testAccCheckAmiBlockDevice(&ami, &bd, "/dev/sda1"),
|
||||
testAccCheckAmiEbsBlockDevice(&bd, expectedDevice),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_ami.foo", "name", fmt.Sprintf("tf-testing-%d", rInt)),
|
||||
resource.TestCheckResourceAttr(
|
||||
"aws_ami.foo", "architecture", "x86_64"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAmiDestroy(s *terraform.State) error {
|
||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "aws_ami" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to find the AMI
|
||||
log.Printf("AMI-ID: %s", rs.Primary.ID)
|
||||
DescribeAmiOpts := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(rs.Primary.ID)},
|
||||
}
|
||||
resp, err := conn.DescribeImages(DescribeAmiOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(resp.Images) > 0 {
|
||||
state := resp.Images[0].State
|
||||
return fmt.Errorf("AMI %s still exists in the state: %s.", *resp.Images[0].ImageId, *state)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckAmiExists(n string, ami *ec2.Image) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("AMI Not found: %s", n)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("No AMI ID is set")
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*AWSClient).ec2conn
|
||||
opts := &ec2.DescribeImagesInput{
|
||||
ImageIds: []*string{aws.String(rs.Primary.ID)},
|
||||
}
|
||||
resp, err := conn.DescribeImages(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.Images) == 0 {
|
||||
return fmt.Errorf("AMI not found")
|
||||
}
|
||||
*ami = *resp.Images[0]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAmiBlockDevice(ami *ec2.Image, blockDevice *ec2.BlockDeviceMapping, n string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
devices := make(map[string]*ec2.BlockDeviceMapping)
|
||||
for _, device := range ami.BlockDeviceMappings {
|
||||
devices[*device.DeviceName] = device
|
||||
}
|
||||
|
||||
// Check if the block device exists
|
||||
if _, ok := devices[n]; !ok {
|
||||
return fmt.Errorf("block device doesn't exist: %s", n)
|
||||
}
|
||||
|
||||
*blockDevice = *devices[n]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAmiEbsBlockDevice(bd *ec2.BlockDeviceMapping, ed *ec2.EbsBlockDevice) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
// Test for things that ed has, don't care about unset values
|
||||
cd := bd.Ebs
|
||||
if ed.VolumeType != nil {
|
||||
if *ed.VolumeType != *cd.VolumeType {
|
||||
return fmt.Errorf("Volume type mismatch. Expected: %s Got: %s",
|
||||
*ed.VolumeType, *cd.VolumeType)
|
||||
}
|
||||
}
|
||||
if ed.DeleteOnTermination != nil {
|
||||
if *ed.DeleteOnTermination != *cd.DeleteOnTermination {
|
||||
return fmt.Errorf("DeleteOnTermination mismatch. Expected: %t Got: %t",
|
||||
*ed.DeleteOnTermination, *cd.DeleteOnTermination)
|
||||
}
|
||||
}
|
||||
if ed.Encrypted != nil {
|
||||
if *ed.Encrypted != *cd.Encrypted {
|
||||
return fmt.Errorf("Encrypted mismatch. Expected: %t Got: %t",
|
||||
*ed.Encrypted, *cd.Encrypted)
|
||||
}
|
||||
}
|
||||
// Integer defaults need to not be `0` so we don't get a panic
|
||||
if ed.Iops != nil && *ed.Iops != 0 {
|
||||
if *ed.Iops != *cd.Iops {
|
||||
return fmt.Errorf("IOPS mismatch. Expected: %d Got: %d",
|
||||
*ed.Iops, *cd.Iops)
|
||||
}
|
||||
}
|
||||
if ed.VolumeSize != nil && *ed.VolumeSize != 0 {
|
||||
if *ed.VolumeSize != *cd.VolumeSize {
|
||||
return fmt.Errorf("Volume Size mismatch. Expected: %d Got: %d",
|
||||
*ed.VolumeSize, *cd.VolumeSize)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccAmiConfig_basic(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_ebs_volume" "foo" {
|
||||
availability_zone = "us-west-2a"
|
||||
size = 8
|
||||
tags {
|
||||
Name = "tf-acc-test"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ebs_snapshot" "foo" {
|
||||
volume_id = "${aws_ebs_volume.foo.id}"
|
||||
}
|
||||
|
||||
resource "aws_ami" "foo" {
|
||||
name = "tf-testing-%d"
|
||||
virtualization_type = "hvm"
|
||||
root_device_name = "/dev/sda1"
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sda1"
|
||||
snapshot_id = "${aws_ebs_snapshot.foo.id}"
|
||||
}
|
||||
}
|
||||
`, rInt)
|
||||
}
|
||||
|
||||
func testAccAmiConfig_snapshotSize(rInt int) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "aws_ebs_volume" "foo" {
|
||||
availability_zone = "us-west-2a"
|
||||
size = 20
|
||||
tags {
|
||||
Name = "tf-acc-test"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ebs_snapshot" "foo" {
|
||||
volume_id = "${aws_ebs_volume.foo.id}"
|
||||
}
|
||||
|
||||
resource "aws_ami" "foo" {
|
||||
name = "tf-testing-%d"
|
||||
virtualization_type = "hvm"
|
||||
root_device_name = "/dev/sda1"
|
||||
ebs_block_device {
|
||||
device_name = "/dev/sda1"
|
||||
snapshot_id = "${aws_ebs_snapshot.foo.id}"
|
||||
}
|
||||
}
|
||||
`, rInt)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue