From 590a912cc9a17cebab0b2f8615e32253d78ec09b Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Tue, 3 Mar 2015 01:33:42 +0000 Subject: [PATCH 01/46] first steps to add network interface --- Makefile | 2 +- builtin/providers/aws/config.go | 3 + builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_network_interface.go | 119 ++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/aws/resource_aws_network_interface.go diff --git a/Makefile b/Makefile index fdb9ab3d6e03..af89fc04fadc 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ updatedeps: | xargs go list -f '{{join .Deps "\n"}}' \ | grep -v github.com/hashicorp/terraform \ | sort -u \ - | xargs go get -f -u -v + | xargs go get -u -v cover: @go tool cover 2>/dev/null; if [ $$? -eq 3 ]; then \ diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index a357e5b1d472..9ec79d18c418 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -11,6 +11,7 @@ import ( "github.com/mitchellh/goamz/ec2" awsGo "github.com/hashicorp/aws-sdk-go/aws" + awsec2 "github.com/hashicorp/aws-sdk-go/gen/ec2" "github.com/hashicorp/aws-sdk-go/gen/autoscaling" "github.com/hashicorp/aws-sdk-go/gen/elb" "github.com/hashicorp/aws-sdk-go/gen/rds" @@ -27,6 +28,7 @@ type Config struct { type AWSClient struct { ec2conn *ec2.EC2 + ec2conn2 *awsec2.EC2 elbconn *elb.ELB autoscalingconn *autoscaling.AutoScaling s3conn *s3.S3 @@ -63,6 +65,7 @@ func (c *Config) Client() (interface{}, error) { log.Println("[INFO] Initializing EC2 connection") client.ec2conn = ec2.New(auth, region) + client.ec2conn2 = awsec2.New(creds, c.Region, nil) log.Println("[INFO] Initializing ELB connection") client.elbconn = elb.New(creds, c.Region, nil) log.Println("[INFO] Initializing AutoScaling connection") diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 0ab2919fd857..3e37a0308472 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -58,6 +58,7 @@ func Provider() terraform.ResourceProvider { "aws_launch_configuration": resourceAwsLaunchConfiguration(), "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), "aws_network_acl": resourceAwsNetworkAcl(), + "aws_network_interface": resourceAwsNetworkInterface(), "aws_route53_record": resourceAwsRoute53Record(), "aws_route53_zone": resourceAwsRoute53Zone(), "aws_route_table": resourceAwsRouteTable(), diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go new file mode 100644 index 000000000000..b4d23cd4318c --- /dev/null +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -0,0 +1,119 @@ +package aws + +import ( + //"bytes" + //"crypto/sha1" + //"encoding/hex" + "fmt" + "log" + //"strconv" + //"strings" + //"time" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/aws-sdk-go/gen/ec2" +) + +func resourceAwsNetworkInterface() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsNetworkInterfaceCreate, + Read: resourceAwsNetworkInterfaceRead, + Update: resourceAwsNetworkInterfaceUpdate, + Delete: resourceAwsNetworkInterfaceDelete, + + Schema: map[string]*schema.Schema{ + + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "private_ips": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + + "source_dest_check": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "security_groups": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { + + ec2conn := meta.(*AWSClient).ec2conn2 + + request := &ec2.CreateNetworkInterfaceRequest{ + Description: aws.String("xxx"), + Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), + SubnetID: aws.String(d.Get("subnet_id").(string)), + } + + log.Printf("[DEBUG] Creating network interface") + resp, err := ec2conn.CreateNetworkInterface(request) + if err != nil { + return fmt.Errorf("Error creating ENI: %s", err) + } + + new_interface_id := *resp.NetworkInterface.NetworkInterfaceID + d.SetId(new_interface_id) + + + // chain to update here + return nil + +} + +func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { + + ec2conn := meta.(*AWSClient).ec2conn2 + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + } + describeResp, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + return nil + } + if describeResp != nil { + return nil + } + + return nil +} + +func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { + return nil +} + +// InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// an EC2 instance. +func NetworkInterfaceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { + return nil +} \ No newline at end of file From 533b7238b1da196fea7191addbe3a930a8aa5da0 Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Tue, 3 Mar 2015 12:57:11 +0000 Subject: [PATCH 02/46] can create, update & delete eni --- .../aws/resource_aws_network_interface.go | 100 +++++++++++++++--- 1 file changed, 85 insertions(+), 15 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index b4d23cd4318c..96437c18de0c 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -42,11 +42,6 @@ func resourceAwsNetworkInterface() *schema.Resource { }, }, - "source_dest_check": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - }, - "security_groups": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -66,10 +61,10 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) ec2conn := meta.(*AWSClient).ec2conn2 - request := &ec2.CreateNetworkInterfaceRequest{ - Description: aws.String("xxx"), + request := &ec2.CreateNetworkInterfaceRequest{ Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), SubnetID: aws.String(d.Get("subnet_id").(string)), + PrivateIPAddresses: convertToPrivateIPAddresses(d.Get("private_ips").(*schema.Set).List()), } log.Printf("[DEBUG] Creating network interface") @@ -80,35 +75,76 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) new_interface_id := *resp.NetworkInterface.NetworkInterfaceID d.SetId(new_interface_id) + log.Printf("[INFO] ENI ID: %s", d.Id()) - - // chain to update here - return nil - + return resourceAwsNetworkInterfaceUpdate(d, meta) } func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).ec2conn2 describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{d.Id()}, } describeResp, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) if err != nil { - return nil + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidNetworkInterfaceID.NotFound" { + // The ENI is gone now, so just remove it from the state + d.SetId("") + return nil + } + + return fmt.Errorf("Error retrieving ENI: %s", err) } - if describeResp != nil { - return nil + if len(describeResp.NetworkInterfaces) != 1 { + return fmt.Errorf("Unable to find ENI: %#v", describeResp.NetworkInterfaces) } + eni := describeResp.NetworkInterfaces[0] + d.Set("subnet_id", eni.SubnetID) + d.Set("private_ips", convertToJustAddresses(eni.PrivateIPAddresses)) + d.Set("security_groups", convertToGroupIds(eni.Groups)) + return nil } func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { - return nil + + d.Partial(true) + + if d.HasChange("security_groups") { + request := &ec2.ModifyNetworkInterfaceAttributeRequest{ + NetworkInterfaceID: aws.String(d.Id()), + Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), + } + + ec2conn := meta.(*AWSClient).ec2conn2 + err := ec2conn.ModifyNetworkInterfaceAttribute(request) + if err != nil { + return fmt.Errorf("Failure updating ENI: %s", err) + } + + d.SetPartial("security_groups") + } + + d.Partial(false) + + return resourceAwsNetworkInterfaceRead(d, meta) } func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).ec2conn2 + + log.Printf("[INFO] Deleting ENI: %s", d.Id()) + + deleteEniOpts := ec2.DeleteNetworkInterfaceRequest{ + NetworkInterfaceID: aws.String(d.Id()), + } + if err := ec2conn.DeleteNetworkInterface(&deleteEniOpts); err != nil { + return fmt.Errorf("Error deleting ENI: %s", err) + } + return nil } @@ -116,4 +152,38 @@ func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) // an EC2 instance. func NetworkInterfaceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { return nil +} + +func convertToJustAddresses(dtos []ec2.NetworkInterfacePrivateIPAddress) []string { + ips := make([]string, 0, len(dtos)) + for _, v := range dtos { + ip := *v.PrivateIPAddress + ips = append(ips, ip) + } + return ips +} + +func convertToGroupIds(dtos []ec2.GroupIdentifier) []string { + ids := make([]string, 0, len(dtos)) + for _, v := range dtos { + group_id := *v.GroupID + ids = append(ids, group_id) + } + return ids +} + +func convertToPrivateIPAddresses(ips []interface{}) []ec2.PrivateIPAddressSpecification { + dtos := make([]ec2.PrivateIPAddressSpecification, 0, len(ips)) + for i, v := range ips { + new_private_ip := ec2.PrivateIPAddressSpecification{ + PrivateIPAddress: aws.String(v.(string)), + } + + if i == 0 { + new_private_ip.Primary = aws.Boolean(true) + } + + dtos = append(dtos, new_private_ip) + } + return dtos } \ No newline at end of file From 42aaee3e923efed7a8e9f6ebe11611cd11abb1f2 Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Tue, 3 Mar 2015 15:30:10 +0000 Subject: [PATCH 03/46] dealing with attach / detach --- .../aws/resource_aws_network_interface.go | 91 +++++++++++++++++-- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index 96437c18de0c..265ebe8dac9b 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -1,7 +1,7 @@ package aws import ( - //"bytes" + "bytes" //"crypto/sha1" //"encoding/hex" "fmt" @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/aws-sdk-go/aws" "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/helper/resource" + //"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/aws-sdk-go/gen/ec2" ) @@ -51,7 +51,28 @@ func resourceAwsNetworkInterface() *schema.Resource { return hashcode.String(v.(string)) }, }, - + + "attachment": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "device_index": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "attachment_id": &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + Set: resourceAwsEniAttachmentHash, + }, + "tags": tagsSchema(), }, } @@ -105,6 +126,12 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e d.Set("subnet_id", eni.SubnetID) d.Set("private_ips", convertToJustAddresses(eni.PrivateIPAddresses)) d.Set("security_groups", convertToGroupIds(eni.Groups)) + + if eni.Attachment != nil { + d.Set("attachment", flattenAttachment(eni.Attachment)) + } else { + d.Set("attachment", nil) + } return nil } @@ -113,6 +140,40 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) d.Partial(true) + if d.HasChange("attachment") { + ec2conn := meta.(*AWSClient).ec2conn2 + oa, na := d.GetChange("attachment") + + // if there was an old attachment, remove it + if oa != nil && len(oa.(*schema.Set).List()) > 0 { + old_attachment := oa.(*schema.Set).List()[0].(map[string]interface{}) + detach_request := &ec2.DetachNetworkInterfaceRequest{ + AttachmentID: aws.String(old_attachment["attachment_id"].(string)), + Force: aws.Boolean(true), + } + detach_err := ec2conn.DetachNetworkInterface(detach_request) + if detach_err != nil { + return fmt.Errorf("Error detaching ENI: %s", detach_err) + } + } + + // if there is a new attachment, attach it + if na != nil && len(na.(*schema.Set).List()) > 0 { + new_attachment := na.(*schema.Set).List()[0].(map[string]interface{}) + attach_request := &ec2.AttachNetworkInterfaceRequest{ + DeviceIndex: aws.Integer(new_attachment["device_index"].(int)), + InstanceID: aws.String(new_attachment["instance"].(string)), + NetworkInterfaceID: aws.String(d.Id()), + } + _, attach_err := ec2conn.AttachNetworkInterface(attach_request) + if attach_err != nil { + return fmt.Errorf("Error attaching ENI: %s", attach_err) + } + } + + d.SetPartial("attachment") + } + if d.HasChange("security_groups") { request := &ec2.ModifyNetworkInterfaceAttributeRequest{ NetworkInterfaceID: aws.String(d.Id()), @@ -148,12 +209,6 @@ func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) return nil } -// InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch -// an EC2 instance. -func NetworkInterfaceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc { - return nil -} - func convertToJustAddresses(dtos []ec2.NetworkInterfacePrivateIPAddress) []string { ips := make([]string, 0, len(dtos)) for _, v := range dtos { @@ -186,4 +241,22 @@ func convertToPrivateIPAddresses(ips []interface{}) []ec2.PrivateIPAddressSpecif dtos = append(dtos, new_private_ip) } return dtos +} + +func resourceAwsEniAttachmentHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", m["instance"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["device_index"].(int))) + return hashcode.String(buf.String()) +} + +func flattenAttachment(a *ec2.NetworkInterfaceAttachment) []map[string]interface{} { + result := make([]map[string]interface{}, 0, 1) + att := make(map[string]interface{}) + att["instance"] = *a.InstanceID + att["device_index"] = *a.DeviceIndex + att["attachment_id"] = *a.AttachmentID + result = append(result, att) + return result } \ No newline at end of file From e5a2504acf7e8022805585b08cfa53c23ac5702c Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 5 Mar 2015 14:40:45 +1000 Subject: [PATCH 04/46] First pass at aws_vpn_gateway resource Uses the aws-sdk-go module and is based on the way the existing aws_internet_gateway resource works. --- .../providers/aws/resource_aws_vpn_gateway.go | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_vpn_gateway.go diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go new file mode 100644 index 000000000000..4bf73b9752be --- /dev/null +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -0,0 +1,331 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsVpnGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsVpnGatewayCreate, + Read: resourceAwsVpnGatewayRead, + Update: resourceAwsVpnGatewayUpdate, + Delete: resourceAwsVpnGatewayDelete, + + Schema: map[string]*schema.Schema{ + "availability_zone": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vpc_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + }, + } +} + +func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + createOpts := &ec2.CreateVPNGatewayRequest{ + AvailabilityZone: aws.String(d.Get("availability_zone").(string)), + Type: aws.String(d.Get("type").(string)), + } + + // Create the VPN gateway + log.Printf("[DEBUG] Creating VPN gateway") + resp, err := ec2conn.CreateVPNGateway(createOpts) + if err != nil { + return fmt.Errorf("Error creating VPN gateway: %s", err) + } + + // Get the ID and store it + vpnGateway := resp.VPNGateway + d.SetId(*vpnGateway.VPNGatewayID) + log.Printf("[INFO] VPN Gateway ID: %s", vpnGateway.VPNGatewayID) + + // Attach the VPN gateway to the correct VPC + return resourceAwsVpnGatewayUpdate(d, meta) +} + +func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{d.Id()}, + }) + + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound " { + // Update state to indicate the subnet no longer exists. + d.SetId("") + return nil + } + return err + } + if resp == nil { + return nil + } + + vpnGateway := &resp.VPNGateways[0] + if len(vpnGateway.VPCAttachments) == 0 { + // VPN gateway exists but not attached to the VPC + d.Set("vpc_id", "") + } else { + d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) + } + d.Set("availability_zone", vpnGateway.AvailabilityZone) + d.Set("type", vpnGateway.Type) + d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) + + return nil +} + +func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error { + if d.HasChange("vpc_id") { + // If we're already attached, detach it first + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + // Attach the VPN gateway to the new vpc + if err := resourceAwsVpnGatewayAttach(d, meta); err != nil { + return err + } + } + + ec2conn := meta.(*AWSClient).awsEC2conn + + d.Partial(true) + + if err := setTagsSDK(ec2conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + + d.Partial(false) + + return resourceAwsVpnGatewayRead(d, meta) +} + +func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + // Detach if it is attached + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() error { + err := ec2conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + }) + if err == nil { + return nil + } + + ec2err, ok := err.(*aws.APIError) + if !ok { + return err + } + + switch ec2err.Code { + case "InvalidVpnGatewayID.NotFound": + return nil + case "DependencyViolation": + return err // retry + } + + return resource.RetryError{Err: err} + }) + + return nil +} + +func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + if d.Get("vpc_id").(string) == "" { + log.Printf( + "[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Attaching VPN Gateway '%s' to VPC '%s'", + d.Id(), + d.Get("vpc_id").(string)) + + _, err := ec2conn.AttachVPNGateway(&ec2.AttachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"detached", "attaching"}, + Target: "available", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "available"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for VPN gateway (%s) to attach: %s", + d.Id(), err) + } + + return nil +} + +func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error { + ec2conn := meta.(*AWSClient).awsEC2conn + + // Get the old VPC ID to detach from + vpcID, _ := d.GetChange("vpc_id") + + if vpcID.(string) == "" { + log.Printf( + "[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Detaching VPN Gateway '%s' from VPC '%s'", + d.Id(), + vpcID.(string)) + + wait := true + err := ec2conn.DetachVPNGateway(&ec2.DetachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + ec2err, ok := err.(*aws.APIError) + if ok { + if ec2err.Code == "InvalidVpnGatewayID.NotFound" { + err = nil + wait = false + } else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" { + err = nil + wait = false + } + } + + if err != nil { + return err + } + } + + if !wait { + return nil + } + + // Wait for it to be fully detached before continuing + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"attached", "detaching", "available"}, + Target: "detached", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "detached"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for vpn gateway (%s) to detach: %s", + d.Id(), err) + } + + return nil +} + + +// VpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. +func VpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + return vpnGateway, *vpnGateway.State, nil + } +} + +// VpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// the state of a VPN gateway's attachment +func VpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc { + var start time.Time + return func() (interface{}, string, error) { + if start.IsZero() { + start = time.Now() + } + + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + + if time.Now().Sub(start) > 10*time.Second { + return vpnGateway, expected, nil + } + + if len(vpnGateway.VPCAttachments) == 0 { + // No attachments, we're detached + return vpnGateway, "detached", nil + } + + return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil + } +} From 4706ee7ffc763eee1be05a76735a4c79c32773d4 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 5 Mar 2015 16:24:14 +1000 Subject: [PATCH 05/46] Add acceptance test for aws_vpn_gateway resource. --- builtin/providers/aws/provider.go | 1 + .../providers/aws/resource_aws_vpn_gateway.go | 5 +- .../aws/resource_aws_vpn_gateway_test.go | 231 ++++++++++++++++++ 3 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/aws/resource_aws_vpn_gateway_test.go diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 0ab2919fd857..8c9cbd5d3bc6 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -67,6 +67,7 @@ func Provider() terraform.ResourceProvider { "aws_subnet": resourceAwsSubnet(), "aws_vpc": resourceAwsVpc(), "aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(), + "aws_vpn_gateway": resourceAwsVpnGateway(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 4bf73b9752be..9e85540efe2d 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -27,7 +27,8 @@ func resourceAwsVpnGateway() *schema.Resource { "type": &schema.Schema{ Type: schema.TypeString, - Required: true, + Default: "ipsec.1", + Optional: true, ForceNew: true, }, @@ -36,6 +37,8 @@ func resourceAwsVpnGateway() *schema.Resource { Optional: true, }, + "tags": tagsSchema(), + }, } } diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go new file mode 100644 index 000000000000..25ab24ebca62 --- /dev/null +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -0,0 +1,231 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSVpnGateway(t *testing.T) { + var v, v2 ec2.VPNGateway + + testNotEqual := func(*terraform.State) error { + if len(v.VPCAttachments) == 0 { + return fmt.Errorf("VPN gateway A is not attached") + } + if len(v2.VPCAttachments) == 0 { + return fmt.Errorf("VPN gateway B is not attached") + } + + id1 := v.VPCAttachments[0].VPCID + id2 := v2.VPCAttachments[0].VPCID + if id1 == id2 { + return fmt.Errorf("Both attachment IDs are the same") + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpnGatewayConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists( + "aws_vpn_gateway.foo", &v), + ), + }, + + resource.TestStep{ + Config: testAccVpnGatewayConfigChangeVPC, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists( + "aws_vpn_gateway.foo", &v2), + testNotEqual, + ), + }, + }, + }) +} + +func TestAccAWSVpnGateway_delete(t *testing.T) { + var vpnGateway ec2.VPNGateway + + testDeleted := func(r string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[r] + if ok { + return fmt.Errorf("VPN Gateway %q should have been deleted", r) + } + return nil + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpnGatewayConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &vpnGateway)), + }, + resource.TestStep{ + Config: testAccNoVpnGatewayConfig, + Check: resource.ComposeTestCheckFunc(testDeleted("aws_vpn_gateway.foo")), + }, + }, + }) +} + +func TestAccVpnGateway_tags(t *testing.T) { + var v ec2.VPNGateway + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckVpnGatewayConfigTags, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), + ), + }, + + resource.TestStep{ + Config: testAccCheckVpnGatewayConfigTagsUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), + testAccCheckTagsSDK(&v.Tags, "foo", ""), + testAccCheckTagsSDK(&v.Tags, "bar", "baz"), + ), + }, + }, + }) +} + +func testAccCheckVpnGatewayDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpn_gateway" { + continue + } + + // Try to find the resource + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{rs.Primary.ID}, + }) + if err == nil { + if len(resp.VPNGateways) > 0 { + return fmt.Errorf("still exists") + } + + return nil + } + + // Verify the error is what we want + ec2err, ok := err.(*aws.APIError) + if !ok { + return err + } + if ec2err.Code != "InvalidVpnGatewayID.NotFound" { + return err + } + } + + return nil +} + +func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{rs.Primary.ID}, + }) + if err != nil { + return err + } + if len(resp.VPNGateways) == 0 { + return fmt.Errorf("VPNGateway not found") + } + + *ig = resp.VPNGateways[0] + + return nil + } +} + +const testAccNoVpnGatewayConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} +` + +const testAccVpnGatewayConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" +} +` + +const testAccVpnGatewayConfigChangeVPC = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpc" "bar" { + cidr_block = "10.2.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.bar.id}" +} +` + +const testAccCheckVpnGatewayConfigTags = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" + tags { + foo = "bar" + } +} +` + +const testAccCheckVpnGatewayConfigTagsUpdate = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_vpn_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" + tags { + bar = "baz" + } +} +` From b741e0c9a30c1b6dc602b616a0b3219a167f1869 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 5 Mar 2015 16:27:57 +1000 Subject: [PATCH 06/46] Add documentation --- .../providers/aws/r/vpn_gateway.html.markdown | 39 +++++++++++++++++++ website/source/layouts/aws.erb | 4 ++ 2 files changed, 43 insertions(+) create mode 100644 website/source/docs/providers/aws/r/vpn_gateway.html.markdown diff --git a/website/source/docs/providers/aws/r/vpn_gateway.html.markdown b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown new file mode 100644 index 000000000000..7f72d6ebacad --- /dev/null +++ b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "aws" +page_title: "AWS: aws_vpn_gateway" +sidebar_current: "docs-aws-resource-vpn-gateway" +description: |- + Provides a resource to create a VPC VPN Gateway. +--- + +# aws\_vpn\_gateway + +Provides a resource to create a VPC VPN Gateway. + +## Example Usage + +``` +resource "aws_vpn_gateway" "vpn_gw" { + vpc_id = "${aws_vpc.main.id}" + + tags { + Name = "main" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `vpc_id` - (Required) The VPC ID to create in. +* `type` - (Optional) The type of VPN connection this virtual private gateway supports. +* `availability_zone` - (Optional) The Availability Zone for the virtual private gateway. +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the VPN Gateway. + diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 030192dfde06..f73403d6cbfd 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -95,6 +95,10 @@ > aws_vpc + + > + aws_vpn_gateway + From 98d827b6f501f880f7e148df14817adc44e50fc6 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Fri, 6 Mar 2015 08:47:29 +1000 Subject: [PATCH 07/46] Match the internet gateway code better. --- .../providers/aws/resource_aws_vpn_gateway.go | 58 +++++++++---------- .../aws/resource_aws_vpn_gateway_test.go | 10 ++-- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 9e85540efe2d..34049e538ad9 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -48,7 +48,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error createOpts := &ec2.CreateVPNGatewayRequest{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String(d.Get("type").(string)), + Type: aws.String(d.Get("type").(string)), } // Create the VPN gateway @@ -61,7 +61,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error // Get the ID and store it vpnGateway := resp.VPNGateway d.SetId(*vpnGateway.VPNGatewayID) - log.Printf("[INFO] VPN Gateway ID: %s", vpnGateway.VPNGatewayID) + log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VPNGatewayID) // Attach the VPN gateway to the correct VPC return resourceAwsVpnGatewayUpdate(d, meta) @@ -70,29 +70,23 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).awsEC2conn - resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{d.Id()}, - }) - - if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound " { - // Update state to indicate the subnet no longer exists. - d.SetId("") - return nil - } - return err - } - if resp == nil { - return nil - } + vpnGatewayRaw, _, err := VpnGatewayStateRefreshFunc(ec2conn, d.Id())() + if err != nil { + return err + } + if vpnGatewayRaw == nil { + // Seems we have lost our VPN gateway + d.SetId("") + return nil + } - vpnGateway := &resp.VPNGateways[0] - if len(vpnGateway.VPCAttachments) == 0 { - // VPN gateway exists but not attached to the VPC - d.Set("vpc_id", "") - } else { - d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) - } + vpnGateway := vpnGatewayRaw.(*ec2.VPNGateway) + if len(vpnGateway.VPCAttachments) == 0 { + // Gateway exists but not attached to the VPC + d.Set("vpc_id", "") + } else { + d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) + } d.Set("availability_zone", vpnGateway.AvailabilityZone) d.Set("type", vpnGateway.Type) d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) @@ -115,17 +109,13 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error ec2conn := meta.(*AWSClient).awsEC2conn - d.Partial(true) - if err := setTagsSDK(ec2conn, d); err != nil { return err - } else { - d.SetPartial("tags") } - d.Partial(false) + d.SetPartial("tags") - return resourceAwsVpnGatewayRead(d, meta) + return nil } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { @@ -160,8 +150,6 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error return resource.RetryError{Err: err} }) - - return nil } func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { @@ -187,6 +175,12 @@ func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error return err } + // A note on the states below: the AWS docs (as of July, 2014) say + // that the states would be: attached, attaching, detached, detaching, + // but when running, I noticed that the state is usually "available" when + // it is attached. + + // Wait for it to be fully attached before continuing log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) stateConf := &resource.StateChangeConf{ Pending: []string{"detached", "attaching"}, diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 25ab24ebca62..7b2d98157ab1 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -114,7 +114,7 @@ func TestAccVpnGateway_tags(t *testing.T) { } func testAccCheckVpnGatewayDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_vpn_gateway" { @@ -122,7 +122,7 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error { } // Try to find the resource - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{rs.Primary.ID}, }) if err == nil { @@ -134,7 +134,7 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error { } // Verify the error is what we want - ec2err, ok := err.(*aws.APIError) + ec2err, ok := err.(aws.APIError) if !ok { return err } @@ -157,8 +157,8 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestChe return fmt.Errorf("No ID is set") } - conn := testAccProvider.Meta().(*AWSClient).awsEC2conn - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{rs.Primary.ID}, }) if err != nil { From 7240af439ca5e391d4086b95a2f44445f70bd435 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Fri, 6 Mar 2015 15:48:30 +1000 Subject: [PATCH 08/46] Minor test fixes. --- builtin/providers/aws/resource_aws_vpn_gateway.go | 2 +- builtin/providers/aws/resource_aws_vpn_gateway_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 34049e538ad9..3a9ad8563613 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -115,7 +115,7 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error d.SetPartial("tags") - return nil + return resourceAwsVpnGatewayRead(d, meta) } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 7b2d98157ab1..33d54d0ae1df 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -98,6 +98,7 @@ func TestAccVpnGateway_tags(t *testing.T) { Config: testAccCheckVpnGatewayConfigTags, Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), + testAccCheckTagsSDK(&v.Tags, "foo", "bar"), ), }, From 39346e6f1650888cb095bad2f1e5f968d50de67f Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Fri, 6 Mar 2015 09:29:25 +0000 Subject: [PATCH 09/46] tweaks in new aws network interface --- .../aws/resource_aws_network_interface.go | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index 265ebe8dac9b..3c7aa74a4403 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -1,18 +1,12 @@ package aws import ( - "bytes" - //"crypto/sha1" - //"encoding/hex" + "bytes" "fmt" "log" - //"strconv" - //"strings" - //"time" - + "github.com/hashicorp/aws-sdk-go/aws" - "github.com/hashicorp/terraform/helper/hashcode" - //"github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/aws-sdk-go/gen/ec2" ) @@ -66,7 +60,8 @@ func resourceAwsNetworkInterface() *schema.Resource { Required: true, }, "attachment_id": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeString, + Computed: true, }, }, }, @@ -136,6 +131,24 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e return nil } +func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}) error { + // if there was an old attachment, remove it + if oa != nil && len(oa.List()) > 0 { + old_attachment := oa.List()[0].(map[string]interface{}) + detach_request := &ec2.DetachNetworkInterfaceRequest{ + AttachmentID: aws.String(old_attachment["attachment_id"].(string)), + Force: aws.Boolean(true), + } + ec2conn := meta.(*AWSClient).ec2conn2 + detach_err := ec2conn.DetachNetworkInterface(detach_request) + if detach_err != nil { + return fmt.Errorf("Error detaching ENI: %s", detach_err) + } + } + + return nil +} + func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { d.Partial(true) @@ -144,17 +157,9 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) ec2conn := meta.(*AWSClient).ec2conn2 oa, na := d.GetChange("attachment") - // if there was an old attachment, remove it - if oa != nil && len(oa.(*schema.Set).List()) > 0 { - old_attachment := oa.(*schema.Set).List()[0].(map[string]interface{}) - detach_request := &ec2.DetachNetworkInterfaceRequest{ - AttachmentID: aws.String(old_attachment["attachment_id"].(string)), - Force: aws.Boolean(true), - } - detach_err := ec2conn.DetachNetworkInterface(detach_request) - if detach_err != nil { - return fmt.Errorf("Error detaching ENI: %s", detach_err) - } + detach_err := resourceAwsNetworkInterfaceDetach(oa.(*schema.Set), meta) + if detach_err != nil { + return detach_err } // if there is a new attachment, attach it @@ -199,6 +204,11 @@ func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) log.Printf("[INFO] Deleting ENI: %s", d.Id()) + detach_err := resourceAwsNetworkInterfaceDetach(d.Get("attachment").(*schema.Set), meta) + if detach_err != nil { + return detach_err + } + deleteEniOpts := ec2.DeleteNetworkInterfaceRequest{ NetworkInterfaceID: aws.String(d.Id()), } @@ -246,8 +256,8 @@ func convertToPrivateIPAddresses(ips []interface{}) []ec2.PrivateIPAddressSpecif func resourceAwsEniAttachmentHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", m["instance"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["device_index"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["instance"].(string))) + buf.WriteString(fmt.Sprintf("%d-", m["device_index"].(int))) return hashcode.String(buf.String()) } From c3ba0a7f6dee7ae800891c71c8833c51535a141d Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Fri, 6 Mar 2015 15:49:56 +0000 Subject: [PATCH 10/46] adding acceptance tests for network interface --- .../aws/resource_aws_network_interface.go | 41 ++- .../resource_aws_network_interface_test.go | 240 ++++++++++++++++++ grep | 200 +++++++++++++++ 3 files changed, 478 insertions(+), 3 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_network_interface_test.go create mode 100644 grep diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index 3c7aa74a4403..4241f8bde95d 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -4,9 +4,12 @@ import ( "bytes" "fmt" "log" + "strconv" + "time" "github.com/hashicorp/aws-sdk-go/aws" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/aws-sdk-go/gen/ec2" ) @@ -131,7 +134,27 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e return nil } -func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}) error { +func networkInterfaceAttachmentRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{id}, + } + describeResp, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + log.Printf("[ERROR] Could not find network interface %s. %s", id, err) + return nil, "", err + } + + eni := describeResp.NetworkInterfaces[0] + hasAttachment := strconv.FormatBool(eni.Attachment != nil) + log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment) + return eni, hasAttachment, nil + } +} + +func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId string) error { // if there was an old attachment, remove it if oa != nil && len(oa.List()) > 0 { old_attachment := oa.List()[0].(map[string]interface{}) @@ -144,6 +167,18 @@ func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}) error { if detach_err != nil { return fmt.Errorf("Error detaching ENI: %s", detach_err) } + + log.Printf("[DEBUG] Waiting for ENI (%s) to become dettached", eniId) + stateConf := &resource.StateChangeConf{ + Pending: []string{"true"}, + Target: "false", + Refresh: networkInterfaceAttachmentRefreshFunc(ec2conn, eniId), + Timeout: 10 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for ENI (%s) to become dettached: %s", eniId, err) + } } return nil @@ -157,7 +192,7 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) ec2conn := meta.(*AWSClient).ec2conn2 oa, na := d.GetChange("attachment") - detach_err := resourceAwsNetworkInterfaceDetach(oa.(*schema.Set), meta) + detach_err := resourceAwsNetworkInterfaceDetach(oa.(*schema.Set), meta, d.Id()) if detach_err != nil { return detach_err } @@ -204,7 +239,7 @@ func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) log.Printf("[INFO] Deleting ENI: %s", d.Id()) - detach_err := resourceAwsNetworkInterfaceDetach(d.Get("attachment").(*schema.Set), meta) + detach_err := resourceAwsNetworkInterfaceDetach(d.Get("attachment").(*schema.Set), meta, d.Id()) if detach_err != nil { return detach_err } diff --git a/builtin/providers/aws/resource_aws_network_interface_test.go b/builtin/providers/aws/resource_aws_network_interface_test.go new file mode 100644 index 000000000000..27eedba45027 --- /dev/null +++ b/builtin/providers/aws/resource_aws_network_interface_test.go @@ -0,0 +1,240 @@ +package aws + +import ( + //"log" + "fmt" + //"os" + //"reflect" + //"sort" + "testing" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSENI_basic(t *testing.T) { + var conf ec2.NetworkInterface + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSENIDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSENIConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSENIExists("aws_network_interface.bar", &conf), + testAccCheckAWSENIAttributes(&conf), + resource.TestCheckResourceAttr( + "aws_network_interface.bar", "private_ips.#", "1"), + resource.TestCheckResourceAttr( + "aws_network_interface.bar", "tags.Name", "bar_interface"), + ), + }, + }, + }) +} + +func TestAccAWSENI_attached(t *testing.T) { + var conf ec2.NetworkInterface + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSENIDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSENIConfigWithAttachment, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSENIExists("aws_network_interface.bar", &conf), + testAccCheckAWSENIAttributesWithAttachment(&conf), + resource.TestCheckResourceAttr( + "aws_network_interface.bar", "private_ips.#", "1"), + resource.TestCheckResourceAttr( + "aws_network_interface.bar", "tags.Name", "bar_interface"), + ), + }, + }, + }) +} + +func testAccCheckAWSENIExists(n string, res *ec2.NetworkInterface) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ENI ID is set") + } + + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn2 + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{rs.Primary.ID}, + } + describeResp, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + return err + } + + if len(describeResp.NetworkInterfaces) != 1 || + *describeResp.NetworkInterfaces[0].NetworkInterfaceID != rs.Primary.ID { + return fmt.Errorf("ENI not found") + } + + *res = describeResp.NetworkInterfaces[0] + + return nil + } +} + +func testAccCheckAWSENIAttributes(conf *ec2.NetworkInterface) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if conf.Attachment != nil { + return fmt.Errorf("expected attachment to be nil") + } + + if *conf.AvailabilityZone != "us-west-2a" { + return fmt.Errorf("expected availability_zone to be us-west-2a, but was %s", *conf.AvailabilityZone) + } + + if len(conf.Groups) != 1 && *conf.Groups[0].GroupName != "foo" { + return fmt.Errorf("expected security group to be foo, but was %#v", conf.Groups) + } + + if *conf.PrivateIPAddress != "172.16.10.100" { + return fmt.Errorf("expected private ip to be 172.16.10.100, but was %s", *conf.PrivateIPAddress) + } + + return nil + } +} + +func testAccCheckAWSENIAttributesWithAttachment(conf *ec2.NetworkInterface) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if conf.Attachment == nil { + return fmt.Errorf("expected attachment to be set, but was nil") + } + + if *conf.Attachment.DeviceIndex != 1 { + return fmt.Errorf("expected attachment device index to be 1, but was %d", *conf.Attachment.DeviceIndex) + } + + if *conf.AvailabilityZone != "us-west-2a" { + return fmt.Errorf("expected availability_zone to be us-west-2a, but was %s", *conf.AvailabilityZone) + } + + if len(conf.Groups) != 1 && *conf.Groups[0].GroupName != "foo" { + return fmt.Errorf("expected security group to be foo, but was %#v", conf.Groups) + } + + if *conf.PrivateIPAddress != "172.16.10.100" { + return fmt.Errorf("expected private ip to be 172.16.10.100, but was %s", *conf.PrivateIPAddress) + } + + return nil + } +} + +func testAccCheckAWSENIDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_network_interface" { + continue + } + + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn2 + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{rs.Primary.ID}, + } + _, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) + + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidNetworkInterfaceID.NotFound" { + return nil + } + + return err + } + } + + return nil +} + + +const testAccAWSENIConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "172.16.0.0/16" +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "172.16.10.0/24" + availability_zone = "us-west-2a" +} + +resource "aws_security_group" "foo" { + vpc_id = "${aws_vpc.foo.id}" + description = "foo" + name = "foo" +} + +resource "aws_network_interface" "bar" { + subnet_id = "${aws_subnet.foo.id}" + private_ips = ["172.16.10.100"] + security_groups = ["${aws_security_group.foo.id}"] + tags { + Name = "bar_interface" + } +} +` + +const testAccAWSENIConfigWithAttachment = ` +resource "aws_vpc" "foo" { + cidr_block = "172.16.0.0/16" +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "172.16.10.0/24" + availability_zone = "us-west-2a" +} + +resource "aws_subnet" "bar" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "172.16.11.0/24" + availability_zone = "us-west-2a" +} + +resource "aws_security_group" "foo" { + vpc_id = "${aws_vpc.foo.id}" + description = "foo" + name = "foo" +} + +resource "aws_instance" "foo" { + ami = "ami-c5eabbf5" + instance_type = "t2.micro" + subnet_id = "${aws_subnet.bar.id}" + associate_public_ip_address = false + private_ip = "172.16.11.50" +} + +resource "aws_network_interface" "bar" { + subnet_id = "${aws_subnet.foo.id}" + private_ips = ["172.16.10.100"] + security_groups = ["${aws_security_group.foo.id}"] + attachment { + instance = "${aws_instance.foo.id}" + device_index = 1 + } + tags { + Name = "bar_interface" + } +} +` \ No newline at end of file diff --git a/grep b/grep new file mode 100644 index 000000000000..2ba075b08b54 --- /dev/null +++ b/grep @@ -0,0 +1,200 @@ +go generate ./... +TF_ACC= go test ./builtin/providers/aws/ -v -timeout=30s -parallel=4 +=== RUN Test_expandNetworkAclEntry +--- PASS: Test_expandNetworkAclEntry (0.00s) +=== RUN Test_flattenNetworkAclEntry +--- PASS: Test_flattenNetworkAclEntry (0.00s) +=== RUN TestProvider +--- PASS: TestProvider (0.00s) +=== RUN TestProvider_impl +--- PASS: TestProvider_impl (0.00s) +=== RUN TestAccAWSAutoScalingGroup_basic +--- SKIP: TestAccAWSAutoScalingGroup_basic (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSAutoScalingGroupWithLoadBalancer +--- SKIP: TestAccAWSAutoScalingGroupWithLoadBalancer (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSDBInstance +--- SKIP: TestAccAWSDBInstance (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSDBParameterGroup +--- SKIP: TestAccAWSDBParameterGroup (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSDBParameterGroupOnly +--- SKIP: TestAccAWSDBParameterGroupOnly (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSDBSecurityGroup +--- SKIP: TestAccAWSDBSecurityGroup (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSDBSubnetGroup +--- SKIP: TestAccAWSDBSubnetGroup (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSEIP_normal +--- SKIP: TestAccAWSEIP_normal (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSEIP_instance +--- SKIP: TestAccAWSEIP_instance (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSELB_basic +--- SKIP: TestAccAWSELB_basic (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSELB_InstanceAttaching +--- SKIP: TestAccAWSELB_InstanceAttaching (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSELB_HealthCheck +--- SKIP: TestAccAWSELB_HealthCheck (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSELBUpdate_HealthCheck +--- SKIP: TestAccAWSELBUpdate_HealthCheck (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSInstance_normal +--- SKIP: TestAccAWSInstance_normal (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSInstance_blockDevices +--- SKIP: TestAccAWSInstance_blockDevices (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSInstance_sourceDestCheck +--- SKIP: TestAccAWSInstance_sourceDestCheck (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSInstance_vpc +--- SKIP: TestAccAWSInstance_vpc (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccInstance_tags +--- SKIP: TestAccInstance_tags (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccInstance_privateIP +--- SKIP: TestAccInstance_privateIP (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccInstance_associatePublicIPAndPrivateIP +--- SKIP: TestAccInstance_associatePublicIPAndPrivateIP (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestInstanceTenancySchema +--- PASS: TestInstanceTenancySchema (0.00s) +=== RUN TestAccAWSInternetGateway +--- SKIP: TestAccAWSInternetGateway (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSInternetGateway_delete +--- SKIP: TestAccAWSInternetGateway_delete (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccInternetGateway_tags +--- SKIP: TestAccInternetGateway_tags (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSKeyPair_normal +--- SKIP: TestAccAWSKeyPair_normal (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSLaunchConfiguration +--- SKIP: TestAccAWSLaunchConfiguration (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSMainRouteTableAssociation +--- SKIP: TestAccAWSMainRouteTableAssociation (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSNetworkAclsWithEgressAndIngressRules +--- SKIP: TestAccAWSNetworkAclsWithEgressAndIngressRules (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSNetworkAclsOnlyIngressRules +--- SKIP: TestAccAWSNetworkAclsOnlyIngressRules (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSNetworkAclsOnlyIngressRulesChange +--- SKIP: TestAccAWSNetworkAclsOnlyIngressRulesChange (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSNetworkAclsOnlyEgressRules +--- SKIP: TestAccAWSNetworkAclsOnlyEgressRules (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccNetworkAcl_SubnetChange +--- SKIP: TestAccNetworkAcl_SubnetChange (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSENI_basic +--- SKIP: TestAccAWSENI_basic (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccRoute53Record +--- SKIP: TestAccRoute53Record (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccRoute53Record_generatesSuffix +--- SKIP: TestAccRoute53Record_generatesSuffix (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestCleanPrefix +--- PASS: TestCleanPrefix (0.00s) +=== RUN TestCleanZoneID +--- PASS: TestCleanZoneID (0.00s) +=== RUN TestCleanChangeID +--- PASS: TestCleanChangeID (0.00s) +=== RUN TestAccRoute53Zone +--- SKIP: TestAccRoute53Zone (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSRouteTableAssociation +--- SKIP: TestAccAWSRouteTableAssociation (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSRouteTable_normal +--- SKIP: TestAccAWSRouteTable_normal (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSRouteTable_instance +--- SKIP: TestAccAWSRouteTable_instance (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSRouteTable_tags +--- SKIP: TestAccAWSRouteTable_tags (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSRouteTable_vpcPeering +--- SKIP: TestAccAWSRouteTable_vpcPeering (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSS3Bucket +--- SKIP: TestAccAWSS3Bucket (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSSecurityGroup_normal +--- SKIP: TestAccAWSSecurityGroup_normal (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSSecurityGroup_self +--- SKIP: TestAccAWSSecurityGroup_self (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSSecurityGroup_vpc +--- SKIP: TestAccAWSSecurityGroup_vpc (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSSecurityGroup_MultiIngress +--- SKIP: TestAccAWSSecurityGroup_MultiIngress (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSSecurityGroup_Change +--- SKIP: TestAccAWSSecurityGroup_Change (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSSecurityGroup_tags +--- SKIP: TestAccAWSSecurityGroup_tags (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSSubnet +--- SKIP: TestAccAWSSubnet (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccAWSVPCPeeringConnection_normal +--- SKIP: TestAccAWSVPCPeeringConnection_normal (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccVpc_basic +--- SKIP: TestAccVpc_basic (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccVpc_dedicatedTenancy +--- SKIP: TestAccVpc_dedicatedTenancy (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccVpc_tags +--- SKIP: TestAccVpc_tags (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestAccVpcUpdate +--- SKIP: TestAccVpcUpdate (0.00s) + testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set +=== RUN TestExpandIPPerms +--- PASS: TestExpandIPPerms (0.00s) +=== RUN TestFlattenIPPerms +--- PASS: TestFlattenIPPerms (0.00s) +=== RUN TestExpandListeners +--- PASS: TestExpandListeners (0.00s) +=== RUN TestFlattenHealthCheck +--- PASS: TestFlattenHealthCheck (0.00s) +=== RUN TestExpandStringList +--- PASS: TestExpandStringList (0.00s) +=== RUN TestExpandParameters +--- PASS: TestExpandParameters (0.00s) +=== RUN TestFlattenParameters +--- PASS: TestFlattenParameters (0.00s) +=== RUN TestExpandInstanceString +--- PASS: TestExpandInstanceString (0.00s) +=== RUN TestDiffTags +--- PASS: TestDiffTags (0.00s) +PASS +ok github.com/hashicorp/terraform/builtin/providers/aws 0.042s +make[1]: Entering directory `/home/peter/go/src/github.com/hashicorp/terraform' +go tool vet -asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr . +make[1]: Leaving directory `/home/peter/go/src/github.com/hashicorp/terraform' From efcba8df2e98139290beb73af71da9d7e260c13d Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Fri, 6 Mar 2015 16:08:51 +0000 Subject: [PATCH 11/46] tweaks after merge from master --- Makefile | 2 +- builtin/providers/aws/config.go | 3 +-- .../providers/aws/resource_aws_network_interface.go | 12 ++++++------ .../aws/resource_aws_network_interface_test.go | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index af89fc04fadc..fdb9ab3d6e03 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ updatedeps: | xargs go list -f '{{join .Deps "\n"}}' \ | grep -v github.com/hashicorp/terraform \ | sort -u \ - | xargs go get -u -v + | xargs go get -f -u -v cover: @go tool cover 2>/dev/null; if [ $$? -eq 3 ]; then \ diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 18dbe92e2ae2..65abbe23c7b2 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -65,8 +65,7 @@ func (c *Config) Client() (interface{}, error) { creds := awsGo.Creds(c.AccessKey, c.SecretKey, c.Token) log.Println("[INFO] Initializing EC2 connection") - client.ec2conn = ec2.New(auth, region) - client.ec2conn2 = awsec2.New(creds, c.Region, nil) + client.ec2conn = ec2.New(auth, region) log.Println("[INFO] Initializing ELB connection") client.elbconn = elb.New(creds, c.Region, nil) log.Println("[INFO] Initializing AutoScaling connection") diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index 4241f8bde95d..acf5b6a1708f 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -78,7 +78,7 @@ func resourceAwsNetworkInterface() *schema.Resource { func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn2 + ec2conn := meta.(*AWSClient).awsEC2conn request := &ec2.CreateNetworkInterfaceRequest{ Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), @@ -101,7 +101,7 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn2 + ec2conn := meta.(*AWSClient).awsEC2conn describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ NetworkInterfaceIDs: []string{d.Id()}, } @@ -162,7 +162,7 @@ func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId s AttachmentID: aws.String(old_attachment["attachment_id"].(string)), Force: aws.Boolean(true), } - ec2conn := meta.(*AWSClient).ec2conn2 + ec2conn := meta.(*AWSClient).awsEC2conn detach_err := ec2conn.DetachNetworkInterface(detach_request) if detach_err != nil { return fmt.Errorf("Error detaching ENI: %s", detach_err) @@ -189,7 +189,7 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) d.Partial(true) if d.HasChange("attachment") { - ec2conn := meta.(*AWSClient).ec2conn2 + ec2conn := meta.(*AWSClient).awsEC2conn oa, na := d.GetChange("attachment") detach_err := resourceAwsNetworkInterfaceDetach(oa.(*schema.Set), meta, d.Id()) @@ -220,7 +220,7 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), } - ec2conn := meta.(*AWSClient).ec2conn2 + ec2conn := meta.(*AWSClient).awsEC2conn err := ec2conn.ModifyNetworkInterfaceAttribute(request) if err != nil { return fmt.Errorf("Failure updating ENI: %s", err) @@ -235,7 +235,7 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) } func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn2 + ec2conn := meta.(*AWSClient).awsEC2conn log.Printf("[INFO] Deleting ENI: %s", d.Id()) diff --git a/builtin/providers/aws/resource_aws_network_interface_test.go b/builtin/providers/aws/resource_aws_network_interface_test.go index 27eedba45027..f48a4c4d8070 100644 --- a/builtin/providers/aws/resource_aws_network_interface_test.go +++ b/builtin/providers/aws/resource_aws_network_interface_test.go @@ -71,7 +71,7 @@ func testAccCheckAWSENIExists(n string, res *ec2.NetworkInterface) resource.Test return fmt.Errorf("No ENI ID is set") } - ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn2 + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ NetworkInterfaceIDs: []string{rs.Primary.ID}, } @@ -148,7 +148,7 @@ func testAccCheckAWSENIDestroy(s *terraform.State) error { continue } - ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn2 + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ NetworkInterfaceIDs: []string{rs.Primary.ID}, } From 3052edee6b7b8658304d349a8a34b2ad82876266 Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Fri, 6 Mar 2015 16:12:09 +0000 Subject: [PATCH 12/46] removing unrequired changes --- builtin/providers/aws/config.go | 2 +- grep | 200 -------------------------------- 2 files changed, 1 insertion(+), 201 deletions(-) delete mode 100644 grep diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 65abbe23c7b2..8bc9adab5634 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -65,7 +65,7 @@ func (c *Config) Client() (interface{}, error) { creds := awsGo.Creds(c.AccessKey, c.SecretKey, c.Token) log.Println("[INFO] Initializing EC2 connection") - client.ec2conn = ec2.New(auth, region) + client.ec2conn = ec2.New(auth, region) log.Println("[INFO] Initializing ELB connection") client.elbconn = elb.New(creds, c.Region, nil) log.Println("[INFO] Initializing AutoScaling connection") diff --git a/grep b/grep deleted file mode 100644 index 2ba075b08b54..000000000000 --- a/grep +++ /dev/null @@ -1,200 +0,0 @@ -go generate ./... -TF_ACC= go test ./builtin/providers/aws/ -v -timeout=30s -parallel=4 -=== RUN Test_expandNetworkAclEntry ---- PASS: Test_expandNetworkAclEntry (0.00s) -=== RUN Test_flattenNetworkAclEntry ---- PASS: Test_flattenNetworkAclEntry (0.00s) -=== RUN TestProvider ---- PASS: TestProvider (0.00s) -=== RUN TestProvider_impl ---- PASS: TestProvider_impl (0.00s) -=== RUN TestAccAWSAutoScalingGroup_basic ---- SKIP: TestAccAWSAutoScalingGroup_basic (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSAutoScalingGroupWithLoadBalancer ---- SKIP: TestAccAWSAutoScalingGroupWithLoadBalancer (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSDBInstance ---- SKIP: TestAccAWSDBInstance (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSDBParameterGroup ---- SKIP: TestAccAWSDBParameterGroup (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSDBParameterGroupOnly ---- SKIP: TestAccAWSDBParameterGroupOnly (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSDBSecurityGroup ---- SKIP: TestAccAWSDBSecurityGroup (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSDBSubnetGroup ---- SKIP: TestAccAWSDBSubnetGroup (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSEIP_normal ---- SKIP: TestAccAWSEIP_normal (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSEIP_instance ---- SKIP: TestAccAWSEIP_instance (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSELB_basic ---- SKIP: TestAccAWSELB_basic (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSELB_InstanceAttaching ---- SKIP: TestAccAWSELB_InstanceAttaching (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSELB_HealthCheck ---- SKIP: TestAccAWSELB_HealthCheck (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSELBUpdate_HealthCheck ---- SKIP: TestAccAWSELBUpdate_HealthCheck (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSInstance_normal ---- SKIP: TestAccAWSInstance_normal (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSInstance_blockDevices ---- SKIP: TestAccAWSInstance_blockDevices (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSInstance_sourceDestCheck ---- SKIP: TestAccAWSInstance_sourceDestCheck (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSInstance_vpc ---- SKIP: TestAccAWSInstance_vpc (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccInstance_tags ---- SKIP: TestAccInstance_tags (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccInstance_privateIP ---- SKIP: TestAccInstance_privateIP (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccInstance_associatePublicIPAndPrivateIP ---- SKIP: TestAccInstance_associatePublicIPAndPrivateIP (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestInstanceTenancySchema ---- PASS: TestInstanceTenancySchema (0.00s) -=== RUN TestAccAWSInternetGateway ---- SKIP: TestAccAWSInternetGateway (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSInternetGateway_delete ---- SKIP: TestAccAWSInternetGateway_delete (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccInternetGateway_tags ---- SKIP: TestAccInternetGateway_tags (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSKeyPair_normal ---- SKIP: TestAccAWSKeyPair_normal (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSLaunchConfiguration ---- SKIP: TestAccAWSLaunchConfiguration (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSMainRouteTableAssociation ---- SKIP: TestAccAWSMainRouteTableAssociation (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSNetworkAclsWithEgressAndIngressRules ---- SKIP: TestAccAWSNetworkAclsWithEgressAndIngressRules (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSNetworkAclsOnlyIngressRules ---- SKIP: TestAccAWSNetworkAclsOnlyIngressRules (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSNetworkAclsOnlyIngressRulesChange ---- SKIP: TestAccAWSNetworkAclsOnlyIngressRulesChange (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSNetworkAclsOnlyEgressRules ---- SKIP: TestAccAWSNetworkAclsOnlyEgressRules (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccNetworkAcl_SubnetChange ---- SKIP: TestAccNetworkAcl_SubnetChange (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSENI_basic ---- SKIP: TestAccAWSENI_basic (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccRoute53Record ---- SKIP: TestAccRoute53Record (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccRoute53Record_generatesSuffix ---- SKIP: TestAccRoute53Record_generatesSuffix (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestCleanPrefix ---- PASS: TestCleanPrefix (0.00s) -=== RUN TestCleanZoneID ---- PASS: TestCleanZoneID (0.00s) -=== RUN TestCleanChangeID ---- PASS: TestCleanChangeID (0.00s) -=== RUN TestAccRoute53Zone ---- SKIP: TestAccRoute53Zone (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSRouteTableAssociation ---- SKIP: TestAccAWSRouteTableAssociation (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSRouteTable_normal ---- SKIP: TestAccAWSRouteTable_normal (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSRouteTable_instance ---- SKIP: TestAccAWSRouteTable_instance (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSRouteTable_tags ---- SKIP: TestAccAWSRouteTable_tags (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSRouteTable_vpcPeering ---- SKIP: TestAccAWSRouteTable_vpcPeering (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSS3Bucket ---- SKIP: TestAccAWSS3Bucket (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSSecurityGroup_normal ---- SKIP: TestAccAWSSecurityGroup_normal (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSSecurityGroup_self ---- SKIP: TestAccAWSSecurityGroup_self (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSSecurityGroup_vpc ---- SKIP: TestAccAWSSecurityGroup_vpc (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSSecurityGroup_MultiIngress ---- SKIP: TestAccAWSSecurityGroup_MultiIngress (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSSecurityGroup_Change ---- SKIP: TestAccAWSSecurityGroup_Change (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSSecurityGroup_tags ---- SKIP: TestAccAWSSecurityGroup_tags (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSSubnet ---- SKIP: TestAccAWSSubnet (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccAWSVPCPeeringConnection_normal ---- SKIP: TestAccAWSVPCPeeringConnection_normal (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccVpc_basic ---- SKIP: TestAccVpc_basic (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccVpc_dedicatedTenancy ---- SKIP: TestAccVpc_dedicatedTenancy (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccVpc_tags ---- SKIP: TestAccVpc_tags (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestAccVpcUpdate ---- SKIP: TestAccVpcUpdate (0.00s) - testing.go:89: Acceptance tests skipped unless env 'TF_ACC' set -=== RUN TestExpandIPPerms ---- PASS: TestExpandIPPerms (0.00s) -=== RUN TestFlattenIPPerms ---- PASS: TestFlattenIPPerms (0.00s) -=== RUN TestExpandListeners ---- PASS: TestExpandListeners (0.00s) -=== RUN TestFlattenHealthCheck ---- PASS: TestFlattenHealthCheck (0.00s) -=== RUN TestExpandStringList ---- PASS: TestExpandStringList (0.00s) -=== RUN TestExpandParameters ---- PASS: TestExpandParameters (0.00s) -=== RUN TestFlattenParameters ---- PASS: TestFlattenParameters (0.00s) -=== RUN TestExpandInstanceString ---- PASS: TestExpandInstanceString (0.00s) -=== RUN TestDiffTags ---- PASS: TestDiffTags (0.00s) -PASS -ok github.com/hashicorp/terraform/builtin/providers/aws 0.042s -make[1]: Entering directory `/home/peter/go/src/github.com/hashicorp/terraform' -go tool vet -asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr . -make[1]: Leaving directory `/home/peter/go/src/github.com/hashicorp/terraform' From 79eb50e06b73f67735bbd2b1187ad9b8f6f4031d Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Fri, 6 Mar 2015 16:37:18 +0000 Subject: [PATCH 13/46] removing commented imports --- .../providers/aws/resource_aws_network_interface_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface_test.go b/builtin/providers/aws/resource_aws_network_interface_test.go index f48a4c4d8070..b6388151acb5 100644 --- a/builtin/providers/aws/resource_aws_network_interface_test.go +++ b/builtin/providers/aws/resource_aws_network_interface_test.go @@ -1,11 +1,7 @@ package aws -import ( - //"log" - "fmt" - //"os" - //"reflect" - //"sort" +import ( + "fmt" "testing" "github.com/hashicorp/aws-sdk-go/aws" From 810860ec3784dc568e98f16c369f665abf136852 Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Fri, 6 Mar 2015 16:39:00 +0000 Subject: [PATCH 14/46] fixing indentation --- builtin/providers/aws/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 3e37a0308472..4963bfa489c2 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -58,7 +58,7 @@ func Provider() terraform.ResourceProvider { "aws_launch_configuration": resourceAwsLaunchConfiguration(), "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), "aws_network_acl": resourceAwsNetworkAcl(), - "aws_network_interface": resourceAwsNetworkInterface(), + "aws_network_interface": resourceAwsNetworkInterface(), "aws_route53_record": resourceAwsRoute53Record(), "aws_route53_zone": resourceAwsRoute53Zone(), "aws_route_table": resourceAwsRouteTable(), From d253fff4e51cf291515d10050c06b3496c630509 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 09:49:46 +1000 Subject: [PATCH 15/46] Hardcode type parameter value. Current AWS documentation says there's only one type of VPN gateway for now. --- builtin/providers/aws/resource_aws_vpn_gateway.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 3a9ad8563613..4b047af320b8 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -25,13 +25,6 @@ func resourceAwsVpnGateway() *schema.Resource { ForceNew: true, }, - "type": &schema.Schema{ - Type: schema.TypeString, - Default: "ipsec.1", - Optional: true, - ForceNew: true, - }, - "vpc_id": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -48,7 +41,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error createOpts := &ec2.CreateVPNGatewayRequest{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String(d.Get("type").(string)), + Type: aws.String("ipsec.1"), } // Create the VPN gateway @@ -88,7 +81,6 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) } d.Set("availability_zone", vpnGateway.AvailabilityZone) - d.Set("type", vpnGateway.Type) d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) return nil From c172fd373669014a0857b0d4efe943532b18aef7 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 10:28:44 +1000 Subject: [PATCH 16/46] Fix error handling. AWS returns IncorrectState not DependencyViolation when a VPN gateway is still attached to a VPC. --- builtin/providers/aws/resource_aws_vpn_gateway.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 4b047af320b8..2f06a29a73d9 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -128,7 +128,7 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error return nil } - ec2err, ok := err.(*aws.APIError) + ec2err, ok := err.(aws.APIError) if !ok { return err } @@ -136,7 +136,7 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error switch ec2err.Code { case "InvalidVpnGatewayID.NotFound": return nil - case "DependencyViolation": + case "IncorrectState": return err // retry } @@ -213,7 +213,7 @@ func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error VPCID: aws.String(d.Get("vpc_id").(string)), }) if err != nil { - ec2err, ok := err.(*aws.APIError) + ec2err, ok := err.(aws.APIError) if ok { if ec2err.Code == "InvalidVpnGatewayID.NotFound" { err = nil From d6a731040c8d4eb308e621b69c0f997c40b53043 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 10:30:29 +1000 Subject: [PATCH 17/46] Format the resource_vpn_gateway*.go files. --- .../providers/aws/resource_aws_vpn_gateway.go | 464 +++++++++--------- .../aws/resource_aws_vpn_gateway_test.go | 16 +- 2 files changed, 239 insertions(+), 241 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 2f06a29a73d9..e00472576d22 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -25,43 +25,42 @@ func resourceAwsVpnGateway() *schema.Resource { ForceNew: true, }, - "vpc_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - - "tags": tagsSchema(), + "vpc_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "tags": tagsSchema(), }, } } func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - createOpts := &ec2.CreateVPNGatewayRequest{ - AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String("ipsec.1"), - } - - // Create the VPN gateway - log.Printf("[DEBUG] Creating VPN gateway") - resp, err := ec2conn.CreateVPNGateway(createOpts) - if err != nil { - return fmt.Errorf("Error creating VPN gateway: %s", err) - } - - // Get the ID and store it - vpnGateway := resp.VPNGateway - d.SetId(*vpnGateway.VPNGatewayID) - log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VPNGatewayID) - - // Attach the VPN gateway to the correct VPC - return resourceAwsVpnGatewayUpdate(d, meta) + ec2conn := meta.(*AWSClient).awsEC2conn + + createOpts := &ec2.CreateVPNGatewayRequest{ + AvailabilityZone: aws.String(d.Get("availability_zone").(string)), + Type: aws.String("ipsec.1"), + } + + // Create the VPN gateway + log.Printf("[DEBUG] Creating VPN gateway") + resp, err := ec2conn.CreateVPNGateway(createOpts) + if err != nil { + return fmt.Errorf("Error creating VPN gateway: %s", err) + } + + // Get the ID and store it + vpnGateway := resp.VPNGateway + d.SetId(*vpnGateway.VPNGatewayID) + log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VPNGatewayID) + + // Attach the VPN gateway to the correct VPC + return resourceAwsVpnGatewayUpdate(d, meta) } func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).awsEC2conn vpnGatewayRaw, _, err := VpnGatewayStateRefreshFunc(ec2conn, d.Id())() if err != nil { @@ -80,92 +79,92 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { } else { d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) } - d.Set("availability_zone", vpnGateway.AvailabilityZone) - d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) + d.Set("availability_zone", vpnGateway.AvailabilityZone) + d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) - return nil + return nil } func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error { - if d.HasChange("vpc_id") { - // If we're already attached, detach it first - if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { - return err - } - - // Attach the VPN gateway to the new vpc - if err := resourceAwsVpnGatewayAttach(d, meta); err != nil { - return err - } - } + if d.HasChange("vpc_id") { + // If we're already attached, detach it first + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + // Attach the VPN gateway to the new vpc + if err := resourceAwsVpnGatewayAttach(d, meta); err != nil { + return err + } + } - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).awsEC2conn - if err := setTagsSDK(ec2conn, d); err != nil { - return err - } + if err := setTagsSDK(ec2conn, d); err != nil { + return err + } - d.SetPartial("tags") + d.SetPartial("tags") - return resourceAwsVpnGatewayRead(d, meta) + return resourceAwsVpnGatewayRead(d, meta) } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - // Detach if it is attached - if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { - return err - } - - log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) - - return resource.Retry(5*time.Minute, func() error { - err := ec2conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayRequest{ - VPNGatewayID: aws.String(d.Id()), - }) - if err == nil { - return nil - } - - ec2err, ok := err.(aws.APIError) - if !ok { - return err - } - - switch ec2err.Code { - case "InvalidVpnGatewayID.NotFound": - return nil - case "IncorrectState": - return err // retry - } - - return resource.RetryError{Err: err} - }) + ec2conn := meta.(*AWSClient).awsEC2conn + + // Detach if it is attached + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } + + log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() error { + err := ec2conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + }) + if err == nil { + return nil + } + + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } + + switch ec2err.Code { + case "InvalidVpnGatewayID.NotFound": + return nil + case "IncorrectState": + return err // retry + } + + return resource.RetryError{Err: err} + }) } func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - if d.Get("vpc_id").(string) == "" { - log.Printf( - "[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set", - d.Id()) - return nil - } - - log.Printf( - "[INFO] Attaching VPN Gateway '%s' to VPC '%s'", - d.Id(), - d.Get("vpc_id").(string)) - - _, err := ec2conn.AttachVPNGateway(&ec2.AttachVPNGatewayRequest{ - VPNGatewayID: aws.String(d.Id()), - VPCID: aws.String(d.Get("vpc_id").(string)), - }) - if err != nil { - return err - } + ec2conn := meta.(*AWSClient).awsEC2conn + + if d.Get("vpc_id").(string) == "" { + log.Printf( + "[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Attaching VPN Gateway '%s' to VPC '%s'", + d.Id(), + d.Get("vpc_id").(string)) + + _, err := ec2conn.AttachVPNGateway(&ec2.AttachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + return err + } // A note on the states below: the AWS docs (as of July, 2014) say // that the states would be: attached, attaching, detached, detaching, @@ -173,148 +172,147 @@ func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error // it is attached. // Wait for it to be fully attached before continuing - log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"detached", "attaching"}, - Target: "available", - Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "available"), - Timeout: 1 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for VPN gateway (%s) to attach: %s", - d.Id(), err) - } - - return nil + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"detached", "attaching"}, + Target: "available", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "available"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for VPN gateway (%s) to attach: %s", + d.Id(), err) + } + + return nil } func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn - - // Get the old VPC ID to detach from - vpcID, _ := d.GetChange("vpc_id") - - if vpcID.(string) == "" { - log.Printf( - "[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set", - d.Id()) - return nil - } - - log.Printf( - "[INFO] Detaching VPN Gateway '%s' from VPC '%s'", - d.Id(), - vpcID.(string)) - - wait := true - err := ec2conn.DetachVPNGateway(&ec2.DetachVPNGatewayRequest{ - VPNGatewayID: aws.String(d.Id()), - VPCID: aws.String(d.Get("vpc_id").(string)), - }) - if err != nil { - ec2err, ok := err.(aws.APIError) - if ok { - if ec2err.Code == "InvalidVpnGatewayID.NotFound" { - err = nil - wait = false - } else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" { - err = nil - wait = false - } - } - - if err != nil { - return err - } - } - - if !wait { - return nil - } - - // Wait for it to be fully detached before continuing - log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id()) - stateConf := &resource.StateChangeConf{ - Pending: []string{"attached", "detaching", "available"}, - Target: "detached", - Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "detached"), - Timeout: 1 * time.Minute, - } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for vpn gateway (%s) to detach: %s", - d.Id(), err) - } - - return nil -} + ec2conn := meta.(*AWSClient).awsEC2conn + + // Get the old VPC ID to detach from + vpcID, _ := d.GetChange("vpc_id") + + if vpcID.(string) == "" { + log.Printf( + "[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set", + d.Id()) + return nil + } + + log.Printf( + "[INFO] Detaching VPN Gateway '%s' from VPC '%s'", + d.Id(), + vpcID.(string)) + wait := true + err := ec2conn.DetachVPNGateway(&ec2.DetachVPNGatewayRequest{ + VPNGatewayID: aws.String(d.Id()), + VPCID: aws.String(d.Get("vpc_id").(string)), + }) + if err != nil { + ec2err, ok := err.(aws.APIError) + if ok { + if ec2err.Code == "InvalidVpnGatewayID.NotFound" { + err = nil + wait = false + } else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" { + err = nil + wait = false + } + } + + if err != nil { + return err + } + } + + if !wait { + return nil + } + + // Wait for it to be fully detached before continuing + log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"attached", "detaching", "available"}, + Target: "detached", + Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "detached"), + Timeout: 1 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "Error waiting for vpn gateway (%s) to detach: %s", + d.Id(), err) + } + + return nil +} // VpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. func VpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{id}, - }) - if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { - resp = nil - } else { - log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) - return nil, "", err - } - } - - if resp == nil { - // Sometimes AWS just has consistency issues and doesn't see - // our instance yet. Return an empty state. - return nil, "", nil - } - - vpnGateway := &resp.VPNGateways[0] - return vpnGateway, *vpnGateway.State, nil - } + return func() (interface{}, string, error) { + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + return vpnGateway, *vpnGateway.State, nil + } } -// VpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// VpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch // the state of a VPN gateway's attachment func VpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc { - var start time.Time - return func() (interface{}, string, error) { - if start.IsZero() { - start = time.Now() - } - - resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{id}, - }) - if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { - resp = nil - } else { - log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) - return nil, "", err - } - } - - if resp == nil { - // Sometimes AWS just has consistency issues and doesn't see - // our instance yet. Return an empty state. - return nil, "", nil - } - - vpnGateway := &resp.VPNGateways[0] - - if time.Now().Sub(start) > 10*time.Second { - return vpnGateway, expected, nil - } - - if len(vpnGateway.VPCAttachments) == 0 { - // No attachments, we're detached - return vpnGateway, "detached", nil - } - - return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil - } + var start time.Time + return func() (interface{}, string, error) { + if start.IsZero() { + start = time.Now() + } + + resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ + VPNGatewayIDs: []string{id}, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" { + resp = nil + } else { + log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + vpnGateway := &resp.VPNGateways[0] + + if time.Now().Sub(start) > 10*time.Second { + return vpnGateway, expected, nil + } + + if len(vpnGateway.VPCAttachments) == 0 { + // No attachments, we're detached + return vpnGateway, "detached", nil + } + + return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil + } } diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 33d54d0ae1df..2d3edbe3bf52 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/aws-sdk-go/aws" - "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) @@ -115,7 +115,7 @@ func TestAccVpnGateway_tags(t *testing.T) { } func testAccCheckVpnGatewayDestroy(s *terraform.State) error { - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_vpn_gateway" { @@ -124,8 +124,8 @@ func testAccCheckVpnGatewayDestroy(s *terraform.State) error { // Try to find the resource resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{rs.Primary.ID}, - }) + VPNGatewayIDs: []string{rs.Primary.ID}, + }) if err == nil { if len(resp.VPNGateways) > 0 { return fmt.Errorf("still exists") @@ -158,10 +158,10 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestChe return fmt.Errorf("No ID is set") } - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ - VPNGatewayIDs: []string{rs.Primary.ID}, - }) + VPNGatewayIDs: []string{rs.Primary.ID}, + }) if err != nil { return err } From 0900452113a24bb607a2c532c2aeb2104e908960 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 10:32:49 +1000 Subject: [PATCH 18/46] Remove type parameter from vpn_gateway docs --- website/source/docs/providers/aws/r/vpn_gateway.html.markdown | 1 - 1 file changed, 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/vpn_gateway.html.markdown b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown index 7f72d6ebacad..b64000ce5576 100644 --- a/website/source/docs/providers/aws/r/vpn_gateway.html.markdown +++ b/website/source/docs/providers/aws/r/vpn_gateway.html.markdown @@ -27,7 +27,6 @@ resource "aws_vpn_gateway" "vpn_gw" { The following arguments are supported: * `vpc_id` - (Required) The VPC ID to create in. -* `type` - (Optional) The type of VPN connection this virtual private gateway supports. * `availability_zone` - (Optional) The Availability Zone for the virtual private gateway. * `tags` - (Optional) A mapping of tags to assign to the resource. From cfd8d913bdffde7ea7536e53f474303227d3498e Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 12 Mar 2015 08:13:39 +1000 Subject: [PATCH 19/46] Make vpnGatewayStateRefreshFunc private --- builtin/providers/aws/resource_aws_vpn_gateway.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index e00472576d22..d6ffcef97fd3 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -62,7 +62,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).awsEC2conn - vpnGatewayRaw, _, err := VpnGatewayStateRefreshFunc(ec2conn, d.Id())() + vpnGatewayRaw, _, err := vpnGatewayStateRefreshFunc(ec2conn, d.Id())() if err != nil { return err } @@ -249,8 +249,8 @@ func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error return nil } -// VpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. -func VpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { +// vpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway. +func vpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{id}, From 7d5f7cbf2022a63901862a689586d45acfbf67c6 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 12 Mar 2015 08:16:22 +1000 Subject: [PATCH 20/46] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8470f90b69a6..c9134c82b786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ FEATURES: * **New provider: `dme` (DNSMadeEasy)** [GH-855] * **New command: `taint`** - Manually mark a resource as tainted, causing a destroy and recreate on the next plan/apply. + * **New resource: `aws_vpn_gateway`** [GH-1137] * **Self-variables** can be used to reference the current resource's attributes within a provisioner. Ex. `${self.private_ip_address}` [GH-1033] * **Continous state** saving during `terraform apply`. The state file is From f4808b1ea7050ef26e28c1a53a485712bbbdd5eb Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 16 Mar 2015 15:28:45 -0500 Subject: [PATCH 21/46] provider/aws: Add test for TXT route53 record --- .../aws/resource_aws_route53_record_test.go | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index 08325c783f40..f3e4df755fe4 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -28,6 +28,22 @@ func TestAccRoute53Record(t *testing.T) { }) } +func TestAccRoute53Record_txtSupport(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53RecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRoute53RecordConfigTXT, + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53RecordExists("aws_route53_record.default"), + ), + }, + }, + }) +} + func TestAccRoute53Record_generatesSuffix(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -142,3 +158,17 @@ resource "aws_route53_record" "default" { records = ["127.0.0.1", "127.0.0.27"] } ` + +const testAccRoute53RecordConfigTXT = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_route53_record" "default" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "subdomain" + type = "TXT" + ttl = "30" + records = ["lalalala"] +} +` From 346ff12bc5656a1f757fd312597aabc9a02d1420 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 16 Mar 2015 15:36:18 -0500 Subject: [PATCH 22/46] provider/aws: Fix issue with Route53 and TXT records --- builtin/providers/aws/resource_aws_route53_record.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index fcd781c610cf..323a67486c4c 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -261,8 +261,15 @@ func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData) (*route53.Resource recs := d.Get("records").(*schema.Set).List() records := make([]route53.ResourceRecord, 0, len(recs)) + typeStr := d.Get("type").(string) for _, r := range recs { - records = append(records, route53.ResourceRecord{Value: aws.String(r.(string))}) + switch typeStr { + case "TXT": + str := fmt.Sprintf("\"%s\"", r.(string)) + records = append(records, route53.ResourceRecord{Value: aws.String(str)}) + default: + records = append(records, route53.ResourceRecord{Value: aws.String(r.(string))}) + } } rec := &route53.ResourceRecordSet{ From 130775f38ace9b8ae244678cf961f29b30cb912e Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Tue, 17 Mar 2015 09:48:08 +0000 Subject: [PATCH 23/46] changes after ec2 connection renamed --- .../providers/aws/resource_aws_network_interface.go | 12 ++++++------ .../aws/resource_aws_network_interface_test.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index acf5b6a1708f..282ba91603cf 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -78,7 +78,7 @@ func resourceAwsNetworkInterface() *schema.Resource { func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn request := &ec2.CreateNetworkInterfaceRequest{ Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), @@ -101,7 +101,7 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ NetworkInterfaceIDs: []string{d.Id()}, } @@ -162,7 +162,7 @@ func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId s AttachmentID: aws.String(old_attachment["attachment_id"].(string)), Force: aws.Boolean(true), } - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn detach_err := ec2conn.DetachNetworkInterface(detach_request) if detach_err != nil { return fmt.Errorf("Error detaching ENI: %s", detach_err) @@ -189,7 +189,7 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) d.Partial(true) if d.HasChange("attachment") { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn oa, na := d.GetChange("attachment") detach_err := resourceAwsNetworkInterfaceDetach(oa.(*schema.Set), meta, d.Id()) @@ -220,7 +220,7 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), } - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn err := ec2conn.ModifyNetworkInterfaceAttribute(request) if err != nil { return fmt.Errorf("Failure updating ENI: %s", err) @@ -235,7 +235,7 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) } func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn log.Printf("[INFO] Deleting ENI: %s", d.Id()) diff --git a/builtin/providers/aws/resource_aws_network_interface_test.go b/builtin/providers/aws/resource_aws_network_interface_test.go index b6388151acb5..1478c289eaf9 100644 --- a/builtin/providers/aws/resource_aws_network_interface_test.go +++ b/builtin/providers/aws/resource_aws_network_interface_test.go @@ -67,7 +67,7 @@ func testAccCheckAWSENIExists(n string, res *ec2.NetworkInterface) resource.Test return fmt.Errorf("No ENI ID is set") } - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ NetworkInterfaceIDs: []string{rs.Primary.ID}, } @@ -144,7 +144,7 @@ func testAccCheckAWSENIDestroy(s *terraform.State) error { continue } - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ NetworkInterfaceIDs: []string{rs.Primary.ID}, } From 34d2efa7df26f3470c4a9a739fa1248e0f825977 Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Tue, 17 Mar 2015 12:42:05 +0000 Subject: [PATCH 24/46] moving expand/flatten methods into structure.go and unit testing them --- .../aws/resource_aws_network_interface.go | 53 +--------- builtin/providers/aws/structure.go | 44 +++++++++ builtin/providers/aws/structure_test.go | 96 +++++++++++++++++++ 3 files changed, 144 insertions(+), 49 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index 282ba91603cf..f82bdc69b24c 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -83,7 +83,7 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) request := &ec2.CreateNetworkInterfaceRequest{ Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), SubnetID: aws.String(d.Get("subnet_id").(string)), - PrivateIPAddresses: convertToPrivateIPAddresses(d.Get("private_ips").(*schema.Set).List()), + PrivateIPAddresses: expandPrivateIPAddesses(d.Get("private_ips").(*schema.Set).List()), } log.Printf("[DEBUG] Creating network interface") @@ -92,8 +92,7 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating ENI: %s", err) } - new_interface_id := *resp.NetworkInterface.NetworkInterfaceID - d.SetId(new_interface_id) + d.SetId(*resp.NetworkInterface.NetworkInterfaceID) log.Printf("[INFO] ENI ID: %s", d.Id()) return resourceAwsNetworkInterfaceUpdate(d, meta) @@ -122,8 +121,8 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e eni := describeResp.NetworkInterfaces[0] d.Set("subnet_id", eni.SubnetID) - d.Set("private_ips", convertToJustAddresses(eni.PrivateIPAddresses)) - d.Set("security_groups", convertToGroupIds(eni.Groups)) + d.Set("private_ips", flattenNetworkInterfacesPrivateIPAddesses(eni.PrivateIPAddresses)) + d.Set("security_groups", flattenGroupIdentifiers(eni.Groups)) if eni.Attachment != nil { d.Set("attachment", flattenAttachment(eni.Attachment)) @@ -254,40 +253,6 @@ func resourceAwsNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) return nil } -func convertToJustAddresses(dtos []ec2.NetworkInterfacePrivateIPAddress) []string { - ips := make([]string, 0, len(dtos)) - for _, v := range dtos { - ip := *v.PrivateIPAddress - ips = append(ips, ip) - } - return ips -} - -func convertToGroupIds(dtos []ec2.GroupIdentifier) []string { - ids := make([]string, 0, len(dtos)) - for _, v := range dtos { - group_id := *v.GroupID - ids = append(ids, group_id) - } - return ids -} - -func convertToPrivateIPAddresses(ips []interface{}) []ec2.PrivateIPAddressSpecification { - dtos := make([]ec2.PrivateIPAddressSpecification, 0, len(ips)) - for i, v := range ips { - new_private_ip := ec2.PrivateIPAddressSpecification{ - PrivateIPAddress: aws.String(v.(string)), - } - - if i == 0 { - new_private_ip.Primary = aws.Boolean(true) - } - - dtos = append(dtos, new_private_ip) - } - return dtos -} - func resourceAwsEniAttachmentHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -295,13 +260,3 @@ func resourceAwsEniAttachmentHash(v interface{}) int { buf.WriteString(fmt.Sprintf("%d-", m["device_index"].(int))) return hashcode.String(buf.String()) } - -func flattenAttachment(a *ec2.NetworkInterfaceAttachment) []map[string]interface{} { - result := make([]map[string]interface{}, 0, 1) - att := make(map[string]interface{}) - att["instance"] = *a.InstanceID - att["device_index"] = *a.DeviceIndex - att["attachment_id"] = *a.AttachmentID - result = append(result, att) - return result -} \ No newline at end of file diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 617c2bbf94da..2855506df479 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -195,3 +195,47 @@ func expandStringList(configured []interface{}) []string { } return vs } + +//Flattens an array of private ip addresses into a []string, where the elements returned are the IP strings e.g. "192.168.0.0" +func flattenNetworkInterfacesPrivateIPAddesses(dtos []ec2.NetworkInterfacePrivateIPAddress) []string { + ips := make([]string, 0, len(dtos)) + for _, v := range dtos { + ip := *v.PrivateIPAddress + ips = append(ips, ip) + } + return ips +} + +//Flattens security group identifiers into a []string, where the elements returned are the GroupIDs +func flattenGroupIdentifiers(dtos []ec2.GroupIdentifier) []string { + ids := make([]string, 0, len(dtos)) + for _, v := range dtos { + group_id := *v.GroupID + ids = append(ids, group_id) + } + return ids +} + +//Expands an array of IPs into a ec2 Private IP Address Spec +func expandPrivateIPAddesses(ips []interface{}) []ec2.PrivateIPAddressSpecification { + dtos := make([]ec2.PrivateIPAddressSpecification, 0, len(ips)) + for i, v := range ips { + new_private_ip := ec2.PrivateIPAddressSpecification{ + PrivateIPAddress: aws.String(v.(string)), + } + + new_private_ip.Primary = aws.Boolean(i == 0) + + dtos = append(dtos, new_private_ip) + } + return dtos +} + +//Flattens network interface attachment into a map[string]interface +func flattenAttachment(a *ec2.NetworkInterfaceAttachment) map[string]interface{} { + att := make(map[string]interface{}) + att["instance"] = *a.InstanceID + att["device_index"] = *a.DeviceIndex + att["attachment_id"] = *a.AttachmentID + return att +} \ No newline at end of file diff --git a/builtin/providers/aws/structure_test.go b/builtin/providers/aws/structure_test.go index b85adc51a32c..84a7ad09d289 100644 --- a/builtin/providers/aws/structure_test.go +++ b/builtin/providers/aws/structure_test.go @@ -269,3 +269,99 @@ func TestExpandInstanceString(t *testing.T) { t.Fatalf("Expand Instance String output did not match.\nGot:\n%#v\n\nexpected:\n%#v", expanded, expected) } } + +func TestFlattenNetworkInterfacesPrivateIPAddesses(t *testing.T) { + expanded := []ec2.NetworkInterfacePrivateIPAddress { + ec2.NetworkInterfacePrivateIPAddress { PrivateIPAddress: aws.String("192.168.0.1") }, + ec2.NetworkInterfacePrivateIPAddress { PrivateIPAddress: aws.String("192.168.0.2") }, + } + + result := flattenNetworkInterfacesPrivateIPAddesses(expanded) + + if result == nil { + t.Fatal("result was nil") + } + + if len(result) != 2 { + t.Fatalf("expected result had %d elements, but got %d", 2, len(result)) + } + + if result[0] != "192.168.0.1" { + t.Fatalf("expected ip to be 192.168.0.1, but was %s", result[0]) + } + + if result[1] != "192.168.0.2" { + t.Fatalf("expected ip to be 192.168.0.2, but was %s", result[1]) + } +} + +func TestFlattenGroupIdentifiers(t *testing.T) { + expanded := []ec2.GroupIdentifier { + ec2.GroupIdentifier { GroupID: aws.String("sg-001") }, + ec2.GroupIdentifier { GroupID: aws.String("sg-002") }, + } + + result := flattenGroupIdentifiers(expanded) + + if len(result) != 2 { + t.Fatalf("expected result had %d elements, but got %d", 2, len(result)) + } + + if result[0] != "sg-001" { + t.Fatalf("expected id to be sg-001, but was %s", result[0]) + } + + if result[1] != "sg-002" { + t.Fatalf("expected id to be sg-002, but was %s", result[1]) + } +} + +func TestExpandPrivateIPAddesses(t *testing.T) { + + ip1 := "192.168.0.1" + ip2 := "192.168.0.2" + flattened := []interface{} { + ip1, + ip2, + } + + result := expandPrivateIPAddesses(flattened) + + if len(result) != 2 { + t.Fatalf("expected result had %d elements, but got %d", 2, len(result)) + } + + if *result[0].PrivateIPAddress != "192.168.0.1" || !*result[0].Primary { + t.Fatalf("expected ip to be 192.168.0.1 and Primary, but got %v, %b", *result[0].PrivateIPAddress, *result[0].Primary) + } + + if *result[1].PrivateIPAddress != "192.168.0.2" || *result[1].Primary { + t.Fatalf("expected ip to be 192.168.0.2 and not Primary, but got %v, %b", *result[1].PrivateIPAddress, *result[1].Primary) + } +} + +func TestFlattenAttachment(t *testing.T) { + expanded := &ec2.NetworkInterfaceAttachment{ + InstanceID: aws.String("i-00001"), + DeviceIndex: aws.Integer(1), + AttachmentID: aws.String("at-002"), + } + + result := flattenAttachment(expanded) + + if result == nil { + t.Fatal("expected result to have value, but got nil") + } + + if result["instance"] != "i-00001" { + t.Fatalf("expected instance to be i-00001, but got %s", result["instance"]) + } + + if result["device_index"] != 1 { + t.Fatalf("expected device_index to be 1, but got %d", result["device_index"]) + } + + if result["attachment_id"] != "at-002" { + t.Fatalf("expected attachment_id to be at-002, but got %s", result["attachment_id"]) + } +} \ No newline at end of file From e4214a998380e6872e01d8d6a0d6b9a45b91509d Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Tue, 17 Mar 2015 13:00:36 +0000 Subject: [PATCH 25/46] ran go fmt and made 1 fix after running tests again --- .../aws/resource_aws_network_interface.go | 89 ++++++++++--------- .../resource_aws_network_interface_test.go | 41 +++++---- builtin/providers/aws/structure.go | 16 ++-- builtin/providers/aws/structure_test.go | 24 ++--- 4 files changed, 85 insertions(+), 85 deletions(-) diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index f82bdc69b24c..c4829f4b8cb1 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -1,17 +1,17 @@ package aws import ( - "bytes" + "bytes" "fmt" "log" "strconv" "time" - + "github.com/hashicorp/aws-sdk-go/aws" - "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/aws-sdk-go/gen/ec2" ) func resourceAwsNetworkInterface() *schema.Resource { @@ -21,18 +21,18 @@ func resourceAwsNetworkInterface() *schema.Resource { Update: resourceAwsNetworkInterfaceUpdate, Delete: resourceAwsNetworkInterfaceDelete, - Schema: map[string]*schema.Schema{ - + Schema: map[string]*schema.Schema{ + "subnet_id": &schema.Schema{ Type: schema.TypeString, - Required: true, + Required: true, ForceNew: true, }, "private_ips": &schema.Schema{ Type: schema.TypeSet, Optional: true, - ForceNew: true, + ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: func(v interface{}) int { return hashcode.String(v.(string)) @@ -42,7 +42,7 @@ func resourceAwsNetworkInterface() *schema.Resource { "security_groups": &schema.Schema{ Type: schema.TypeSet, Optional: true, - Computed: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: func(v interface{}) int { return hashcode.String(v.(string)) @@ -63,29 +63,29 @@ func resourceAwsNetworkInterface() *schema.Resource { Required: true, }, "attachment_id": &schema.Schema{ - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, }, }, }, Set: resourceAwsEniAttachmentHash, }, - "tags": tagsSchema(), + "tags": tagsSchema(), }, } } func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { - + ec2conn := meta.(*AWSClient).ec2conn - request := &ec2.CreateNetworkInterfaceRequest{ - Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), - SubnetID: aws.String(d.Get("subnet_id").(string)), - PrivateIPAddresses: expandPrivateIPAddesses(d.Get("private_ips").(*schema.Set).List()), + request := &ec2.CreateNetworkInterfaceRequest{ + Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), + SubnetID: aws.String(d.Get("subnet_id").(string)), + PrivateIPAddresses: expandPrivateIPAddesses(d.Get("private_ips").(*schema.Set).List()), } - + log.Printf("[DEBUG] Creating network interface") resp, err := ec2conn.CreateNetworkInterface(request) if err != nil { @@ -95,14 +95,14 @@ func resourceAwsNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) d.SetId(*resp.NetworkInterface.NetworkInterfaceID) log.Printf("[INFO] ENI ID: %s", d.Id()) - return resourceAwsNetworkInterfaceUpdate(d, meta) + return resourceAwsNetworkInterfaceUpdate(d, meta) } func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).ec2conn - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ - NetworkInterfaceIDs: []string{d.Id()}, + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{d.Id()}, } describeResp, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) @@ -112,7 +112,7 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e d.SetId("") return nil } - + return fmt.Errorf("Error retrieving ENI: %s", err) } if len(describeResp.NetworkInterfaces) != 1 { @@ -125,41 +125,42 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e d.Set("security_groups", flattenGroupIdentifiers(eni.Groups)) if eni.Attachment != nil { - d.Set("attachment", flattenAttachment(eni.Attachment)) + attachment := []map[string]interface{} { flattenAttachment(eni.Attachment) } + d.Set("attachment", attachment) } else { d.Set("attachment", nil) } - + return nil } func networkInterfaceAttachmentRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ - NetworkInterfaceIDs: []string{id}, + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{id}, } describeResp, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) - if err != nil { - log.Printf("[ERROR] Could not find network interface %s. %s", id, err) + if err != nil { + log.Printf("[ERROR] Could not find network interface %s. %s", id, err) return nil, "", err } eni := describeResp.NetworkInterfaces[0] - hasAttachment := strconv.FormatBool(eni.Attachment != nil) - log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment) + hasAttachment := strconv.FormatBool(eni.Attachment != nil) + log.Printf("[DEBUG] ENI %s has attachment state %s", id, hasAttachment) return eni, hasAttachment, nil } } func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId string) error { // if there was an old attachment, remove it - if oa != nil && len(oa.List()) > 0 { + if oa != nil && len(oa.List()) > 0 { old_attachment := oa.List()[0].(map[string]interface{}) detach_request := &ec2.DetachNetworkInterfaceRequest{ - AttachmentID: aws.String(old_attachment["attachment_id"].(string)), - Force: aws.Boolean(true), + AttachmentID: aws.String(old_attachment["attachment_id"].(string)), + Force: aws.Boolean(true), } ec2conn := meta.(*AWSClient).ec2conn detach_err := ec2conn.DetachNetworkInterface(detach_request) @@ -175,9 +176,9 @@ func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId s Timeout: 10 * time.Minute, } if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "Error waiting for ENI (%s) to become dettached: %s", eniId, err) - } + return fmt.Errorf( + "Error waiting for ENI (%s) to become dettached: %s", eniId, err) + } } return nil @@ -189,20 +190,20 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) if d.HasChange("attachment") { ec2conn := meta.(*AWSClient).ec2conn - oa, na := d.GetChange("attachment") - + oa, na := d.GetChange("attachment") + detach_err := resourceAwsNetworkInterfaceDetach(oa.(*schema.Set), meta, d.Id()) if detach_err != nil { return detach_err } // if there is a new attachment, attach it - if na != nil && len(na.(*schema.Set).List()) > 0 { + if na != nil && len(na.(*schema.Set).List()) > 0 { new_attachment := na.(*schema.Set).List()[0].(map[string]interface{}) attach_request := &ec2.AttachNetworkInterfaceRequest{ - DeviceIndex: aws.Integer(new_attachment["device_index"].(int)), - InstanceID: aws.String(new_attachment["instance"].(string)), - NetworkInterfaceID: aws.String(d.Id()), + DeviceIndex: aws.Integer(new_attachment["device_index"].(int)), + InstanceID: aws.String(new_attachment["instance"].(string)), + NetworkInterfaceID: aws.String(d.Id()), } _, attach_err := ec2conn.AttachNetworkInterface(attach_request) if attach_err != nil { @@ -215,8 +216,8 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) if d.HasChange("security_groups") { request := &ec2.ModifyNetworkInterfaceAttributeRequest{ - NetworkInterfaceID: aws.String(d.Id()), - Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), + NetworkInterfaceID: aws.String(d.Id()), + Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), } ec2conn := meta.(*AWSClient).ec2conn diff --git a/builtin/providers/aws/resource_aws_network_interface_test.go b/builtin/providers/aws/resource_aws_network_interface_test.go index 1478c289eaf9..413533e566df 100644 --- a/builtin/providers/aws/resource_aws_network_interface_test.go +++ b/builtin/providers/aws/resource_aws_network_interface_test.go @@ -1,7 +1,7 @@ package aws -import ( - "fmt" +import ( + "fmt" "testing" "github.com/hashicorp/aws-sdk-go/aws" @@ -12,7 +12,7 @@ import ( func TestAccAWSENI_basic(t *testing.T) { var conf ec2.NetworkInterface - + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -22,7 +22,7 @@ func TestAccAWSENI_basic(t *testing.T) { Config: testAccAWSENIConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSENIExists("aws_network_interface.bar", &conf), - testAccCheckAWSENIAttributes(&conf), + testAccCheckAWSENIAttributes(&conf), resource.TestCheckResourceAttr( "aws_network_interface.bar", "private_ips.#", "1"), resource.TestCheckResourceAttr( @@ -35,7 +35,7 @@ func TestAccAWSENI_basic(t *testing.T) { func TestAccAWSENI_attached(t *testing.T) { var conf ec2.NetworkInterface - + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -45,7 +45,7 @@ func TestAccAWSENI_attached(t *testing.T) { Config: testAccAWSENIConfigWithAttachment, Check: resource.ComposeTestCheckFunc( testAccCheckAWSENIExists("aws_network_interface.bar", &conf), - testAccCheckAWSENIAttributesWithAttachment(&conf), + testAccCheckAWSENIAttributesWithAttachment(&conf), resource.TestCheckResourceAttr( "aws_network_interface.bar", "private_ips.#", "1"), resource.TestCheckResourceAttr( @@ -57,7 +57,7 @@ func TestAccAWSENI_attached(t *testing.T) { } func testAccCheckAWSENIExists(n string, res *ec2.NetworkInterface) resource.TestCheckFunc { - return func(s *terraform.State) error { + return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) @@ -68,8 +68,8 @@ func testAccCheckAWSENIExists(n string, res *ec2.NetworkInterface) resource.Test } ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ - NetworkInterfaceIDs: []string{rs.Primary.ID}, + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{rs.Primary.ID}, } describeResp, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) @@ -90,9 +90,9 @@ func testAccCheckAWSENIExists(n string, res *ec2.NetworkInterface) resource.Test func testAccCheckAWSENIAttributes(conf *ec2.NetworkInterface) resource.TestCheckFunc { return func(s *terraform.State) error { - + if conf.Attachment != nil { - return fmt.Errorf("expected attachment to be nil") + return fmt.Errorf("expected attachment to be nil") } if *conf.AvailabilityZone != "us-west-2a" { @@ -108,18 +108,18 @@ func testAccCheckAWSENIAttributes(conf *ec2.NetworkInterface) resource.TestCheck } return nil - } + } } func testAccCheckAWSENIAttributesWithAttachment(conf *ec2.NetworkInterface) resource.TestCheckFunc { return func(s *terraform.State) error { - + if conf.Attachment == nil { - return fmt.Errorf("expected attachment to be set, but was nil") + return fmt.Errorf("expected attachment to be set, but was nil") } if *conf.Attachment.DeviceIndex != 1 { - return fmt.Errorf("expected attachment device index to be 1, but was %d", *conf.Attachment.DeviceIndex) + return fmt.Errorf("expected attachment device index to be 1, but was %d", *conf.Attachment.DeviceIndex) } if *conf.AvailabilityZone != "us-west-2a" { @@ -135,7 +135,7 @@ func testAccCheckAWSENIAttributesWithAttachment(conf *ec2.NetworkInterface) reso } return nil - } + } } func testAccCheckAWSENIDestroy(s *terraform.State) error { @@ -145,8 +145,8 @@ func testAccCheckAWSENIDestroy(s *terraform.State) error { } ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn - describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ - NetworkInterfaceIDs: []string{rs.Primary.ID}, + describe_network_interfaces_request := &ec2.DescribeNetworkInterfacesRequest{ + NetworkInterfaceIDs: []string{rs.Primary.ID}, } _, err := ec2conn.DescribeNetworkInterfaces(describe_network_interfaces_request) @@ -156,13 +156,12 @@ func testAccCheckAWSENIDestroy(s *terraform.State) error { } return err - } + } } return nil } - const testAccAWSENIConfig = ` resource "aws_vpc" "foo" { cidr_block = "172.16.0.0/16" @@ -233,4 +232,4 @@ resource "aws_network_interface" "bar" { Name = "bar_interface" } } -` \ No newline at end of file +` diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 2855506df479..09d8a10be2ba 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -221,21 +221,21 @@ func expandPrivateIPAddesses(ips []interface{}) []ec2.PrivateIPAddressSpecificat dtos := make([]ec2.PrivateIPAddressSpecification, 0, len(ips)) for i, v := range ips { new_private_ip := ec2.PrivateIPAddressSpecification{ - PrivateIPAddress: aws.String(v.(string)), - } - + PrivateIPAddress: aws.String(v.(string)), + } + new_private_ip.Primary = aws.Boolean(i == 0) - + dtos = append(dtos, new_private_ip) } return dtos } //Flattens network interface attachment into a map[string]interface -func flattenAttachment(a *ec2.NetworkInterfaceAttachment) map[string]interface{} { - att := make(map[string]interface{}) +func flattenAttachment(a *ec2.NetworkInterfaceAttachment) map[string]interface{} { + att := make(map[string]interface{}) att["instance"] = *a.InstanceID att["device_index"] = *a.DeviceIndex - att["attachment_id"] = *a.AttachmentID + att["attachment_id"] = *a.AttachmentID return att -} \ No newline at end of file +} diff --git a/builtin/providers/aws/structure_test.go b/builtin/providers/aws/structure_test.go index 84a7ad09d289..bc5cf68e495b 100644 --- a/builtin/providers/aws/structure_test.go +++ b/builtin/providers/aws/structure_test.go @@ -271,9 +271,9 @@ func TestExpandInstanceString(t *testing.T) { } func TestFlattenNetworkInterfacesPrivateIPAddesses(t *testing.T) { - expanded := []ec2.NetworkInterfacePrivateIPAddress { - ec2.NetworkInterfacePrivateIPAddress { PrivateIPAddress: aws.String("192.168.0.1") }, - ec2.NetworkInterfacePrivateIPAddress { PrivateIPAddress: aws.String("192.168.0.2") }, + expanded := []ec2.NetworkInterfacePrivateIPAddress{ + ec2.NetworkInterfacePrivateIPAddress{PrivateIPAddress: aws.String("192.168.0.1")}, + ec2.NetworkInterfacePrivateIPAddress{PrivateIPAddress: aws.String("192.168.0.2")}, } result := flattenNetworkInterfacesPrivateIPAddesses(expanded) @@ -296,9 +296,9 @@ func TestFlattenNetworkInterfacesPrivateIPAddesses(t *testing.T) { } func TestFlattenGroupIdentifiers(t *testing.T) { - expanded := []ec2.GroupIdentifier { - ec2.GroupIdentifier { GroupID: aws.String("sg-001") }, - ec2.GroupIdentifier { GroupID: aws.String("sg-002") }, + expanded := []ec2.GroupIdentifier{ + ec2.GroupIdentifier{GroupID: aws.String("sg-001")}, + ec2.GroupIdentifier{GroupID: aws.String("sg-002")}, } result := flattenGroupIdentifiers(expanded) @@ -317,10 +317,10 @@ func TestFlattenGroupIdentifiers(t *testing.T) { } func TestExpandPrivateIPAddesses(t *testing.T) { - + ip1 := "192.168.0.1" ip2 := "192.168.0.2" - flattened := []interface{} { + flattened := []interface{}{ ip1, ip2, } @@ -330,7 +330,7 @@ func TestExpandPrivateIPAddesses(t *testing.T) { if len(result) != 2 { t.Fatalf("expected result had %d elements, but got %d", 2, len(result)) } - + if *result[0].PrivateIPAddress != "192.168.0.1" || !*result[0].Primary { t.Fatalf("expected ip to be 192.168.0.1 and Primary, but got %v, %b", *result[0].PrivateIPAddress, *result[0].Primary) } @@ -342,8 +342,8 @@ func TestExpandPrivateIPAddesses(t *testing.T) { func TestFlattenAttachment(t *testing.T) { expanded := &ec2.NetworkInterfaceAttachment{ - InstanceID: aws.String("i-00001"), - DeviceIndex: aws.Integer(1), + InstanceID: aws.String("i-00001"), + DeviceIndex: aws.Integer(1), AttachmentID: aws.String("at-002"), } @@ -364,4 +364,4 @@ func TestFlattenAttachment(t *testing.T) { if result["attachment_id"] != "at-002" { t.Fatalf("expected attachment_id to be at-002, but got %s", result["attachment_id"]) } -} \ No newline at end of file +} From 49e6c8fd87b4324ccd70853aeb5dbd044c1757c9 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 17 Mar 2015 14:57:45 -0500 Subject: [PATCH 26/46] provider/aws: Fix wildcard support in Route53 record Fixes a bug in Route53 and wildcard entries. Refs #501. Also fixes: - an issue in the library where we don't fully wait for the results, because the error code/condition changed with the migration to aws-sdk-go - a limitation in the test, where we only consider the first record returned --- .../aws/resource_aws_route53_record.go | 35 ++++-- .../aws/resource_aws_route53_record_test.go | 102 ++++++++++++++++-- 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index fcd781c610cf..abfa04942075 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -173,7 +173,8 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro // Scan for a matching record found := false for _, record := range resp.ResourceRecordSets { - if FQDN(*record.Name) != FQDN(*lopts.StartRecordName) { + name := cleanRecordName(*record.Name) + if FQDN(name) != FQDN(*lopts.StartRecordName) { continue } if strings.ToUpper(*record.Type) != strings.ToUpper(*lopts.StartRecordType) { @@ -232,15 +233,17 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er Refresh: func() (interface{}, string, error) { _, err := conn.ChangeResourceRecordSets(req) if err != nil { - if strings.Contains(err.Error(), "PriorRequestNotComplete") { - // There is some pending operation, so just retry - // in a bit. - return 42, "rejected", nil - } - - if strings.Contains(err.Error(), "InvalidChangeBatch") { - // This means that the record is already gone. - return 42, "accepted", nil + if r53err, ok := err.(aws.APIError); ok { + if r53err.Code == "PriorRequestNotComplete" { + // There is some pending operation, so just retry + // in a bit. + return 42, "rejected", nil + } + + if r53err.Code == "InvalidChangeBatch" { + // This means that the record is already gone. + return 42, "accepted", nil + } } return 42, "failure", err @@ -282,3 +285,15 @@ func FQDN(name string) string { return name + "." } } + +// Route 53 stores the "*" wildcard indicator as ASCII 42 and returns the +// octal equivalent, "\\052". Here we look for that, and convert back to "*" +// as needed. +func cleanRecordName(name string) string { + str := name + if strings.HasPrefix(name, "\\052") { + str = strings.Replace(name, "\\052", "*", 1) + log.Printf("[DEBUG] Replacing octal \\052 for * in: %s", name) + } + return str +} diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index 08325c783f40..0608c51d14d8 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -9,9 +9,26 @@ import ( "github.com/hashicorp/terraform/terraform" "github.com/hashicorp/aws-sdk-go/aws" - awsr53 "github.com/hashicorp/aws-sdk-go/gen/route53" + route53 "github.com/hashicorp/aws-sdk-go/gen/route53" ) +func TestCleanRecordName(t *testing.T) { + cases := []struct { + Input, Output string + }{ + {"www.nonexample.com", "www.nonexample.com"}, + {"\\052.nonexample.com", "*.nonexample.com"}, + {"nonexample.com", "nonexample.com"}, + } + + for _, tc := range cases { + actual := cleanRecordName(tc.Input) + if actual != tc.Output { + t.Fatalf("input: %s\noutput: %s", tc.Input, actual) + } + } +} + func TestAccRoute53Record(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -44,6 +61,30 @@ func TestAccRoute53Record_generatesSuffix(t *testing.T) { }) } +func TestAccRoute53Record_wildcard(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53RecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRoute53WildCardRecordConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53RecordExists("aws_route53_record.wildcard"), + ), + }, + + // Cause a change, which will trigger a refresh + resource.TestStep{ + Config: testAccRoute53WildCardRecordConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53RecordExists("aws_route53_record.wildcard"), + ), + }, + }, + }) +} + func testAccCheckRoute53RecordDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).r53conn for _, rs := range s.RootModule().Resources { @@ -56,7 +97,7 @@ func testAccCheckRoute53RecordDestroy(s *terraform.State) error { name := parts[1] rType := parts[2] - lopts := &awsr53.ListResourceRecordSetsRequest{ + lopts := &route53.ListResourceRecordSetsRequest{ HostedZoneID: aws.String(cleanZoneID(zone)), StartRecordName: aws.String(name), StartRecordType: aws.String(rType), @@ -94,7 +135,7 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { name := parts[1] rType := parts[2] - lopts := &awsr53.ListResourceRecordSetsRequest{ + lopts := &route53.ListResourceRecordSetsRequest{ HostedZoneID: aws.String(cleanZoneID(zone)), StartRecordName: aws.String(name), StartRecordType: aws.String(rType), @@ -107,11 +148,14 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { if len(resp.ResourceRecordSets) == 0 { return fmt.Errorf("Record does not exist") } - rec := resp.ResourceRecordSets[0] - if FQDN(*rec.Name) == FQDN(name) && *rec.Type == rType { - return nil + // rec := resp.ResourceRecordSets[0] + for _, rec := range resp.ResourceRecordSets { + recName := cleanRecordName(*rec.Name) + if FQDN(recName) == FQDN(name) && *rec.Type == rType { + return nil + } } - return fmt.Errorf("Record does not exist: %#v", rec) + return fmt.Errorf("Record does not exist: %#v", rs.Primary.ID) } } @@ -142,3 +186,47 @@ resource "aws_route53_record" "default" { records = ["127.0.0.1", "127.0.0.27"] } ` + +const testAccRoute53WildCardRecordConfig = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_route53_record" "default" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "subdomain" + type = "A" + ttl = "30" + records = ["127.0.0.1", "127.0.0.27"] +} + +resource "aws_route53_record" "wildcard" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "*.notexample.com" + type = "A" + ttl = "30" + records = ["127.0.0.1"] +} +` + +const testAccRoute53WildCardRecordConfigUpdate = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_route53_record" "default" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "subdomain" + type = "A" + ttl = "30" + records = ["127.0.0.1", "127.0.0.27"] +} + +resource "aws_route53_record" "wildcard" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "*.notexample.com" + type = "A" + ttl = "60" + records = ["127.0.0.1"] +} +` From 7034619863d14d4c43996e657467868fa8ddee07 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 17 Mar 2015 15:48:10 -0500 Subject: [PATCH 27/46] provider/aws: Fix issue where we ignored the 'self' attribute of a security group rule --- builtin/providers/aws/resource_aws_security_group.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/aws/resource_aws_security_group.go b/builtin/providers/aws/resource_aws_security_group.go index c8051813fe86..8a307babb767 100644 --- a/builtin/providers/aws/resource_aws_security_group.go +++ b/builtin/providers/aws/resource_aws_security_group.go @@ -285,6 +285,7 @@ func resourceAwsSecurityGroupRuleHash(v interface{}) int { buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) + buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) // We need to make sure to sort the strings below so that we always // generate the same hash code no matter what is in the set. From 0a24e72c3b740f54c385d4d78b4f049745bd4f07 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Tue, 17 Mar 2015 17:08:22 -0400 Subject: [PATCH 28/46] state/remote: allow https consul addresses Sending state over a cleartext protocol is bad in untrusted networks. Expose `-backend-config="scheme=https"` and wire it through to the Consul client. --- state/remote/consul.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/state/remote/consul.go b/state/remote/consul.go index 274b5e37d5ff..791f4dca376d 100644 --- a/state/remote/consul.go +++ b/state/remote/consul.go @@ -20,6 +20,9 @@ func consulFactory(conf map[string]string) (Client, error) { if addr, ok := conf["address"]; ok && addr != "" { config.Address = addr } + if scheme, ok := conf["scheme"]; ok && scheme != "" { + config.Scheme = scheme + } client, err := consulapi.NewClient(config) if err != nil { From f74e68ea46ace04a89174bee6eb448c66aad2463 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 17 Mar 2015 17:22:30 -0500 Subject: [PATCH 29/46] provider/aws: Fixes issue 886 in DB Parameter group --- builtin/providers/aws/resource_aws_db_parameter_group.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_db_parameter_group.go b/builtin/providers/aws/resource_aws_db_parameter_group.go index a5eda1a6440b..68c5b52e6c6d 100644 --- a/builtin/providers/aws/resource_aws_db_parameter_group.go +++ b/builtin/providers/aws/resource_aws_db_parameter_group.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "strings" "time" "github.com/hashicorp/terraform/helper/hashcode" @@ -220,7 +221,8 @@ func resourceAwsDbParameterHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["value"].(string))) + // Store the value as a lower case string, to match how we store them in flattenParameters + buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string)))) return hashcode.String(buf.String()) } From f794f30b7daead0d649eaa7a7e70dbc27e4664ae Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 18 Mar 2015 07:49:39 +0000 Subject: [PATCH 30/46] Ignore hidden files per Unix conventions --- config/loader.go | 4 ++-- config/test-fixtures/dir-temporary-files/.hidden.tf | 0 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 config/test-fixtures/dir-temporary-files/.hidden.tf diff --git a/config/loader.go b/config/loader.go index a1bd196d14e2..fe1494ff8b07 100644 --- a/config/loader.go +++ b/config/loader.go @@ -187,7 +187,7 @@ func dirFiles(dir string) ([]string, []string, error) { // provided file name is a temporary file for the following editors: // emacs or vim. func isTemporaryFile(name string) bool { - return strings.HasSuffix(name, "~") || // vim - strings.HasPrefix(name, ".#") || // emacs + return strings.HasPrefix(name, ".") || // Unix-like hidden files + strings.HasSuffix(name, "~") || // vim (strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs } diff --git a/config/test-fixtures/dir-temporary-files/.hidden.tf b/config/test-fixtures/dir-temporary-files/.hidden.tf new file mode 100644 index 000000000000..e69de29bb2d1 From d823a8cf81f7f30942dd18760bdc2d4d71df77e2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 18 Mar 2015 13:47:59 +0000 Subject: [PATCH 31/46] providers/aws: fix security group self ingress rules on EC2-classic --- .../aws/resource_aws_security_group.go | 9 ++- builtin/providers/aws/structure.go | 16 +++- builtin/providers/aws/structure_test.go | 79 ++++++++++++++++++- 3 files changed, 99 insertions(+), 5 deletions(-) diff --git a/builtin/providers/aws/resource_aws_security_group.go b/builtin/providers/aws/resource_aws_security_group.go index c8051813fe86..ec859b697b07 100644 --- a/builtin/providers/aws/resource_aws_security_group.go +++ b/builtin/providers/aws/resource_aws_security_group.go @@ -396,8 +396,8 @@ func resourceAwsSecurityGroupUpdateRules( os := o.(*schema.Set) ns := n.(*schema.Set) - remove := expandIPPerms(d.Id(), os.Difference(ns).List()) - add := expandIPPerms(d.Id(), ns.Difference(os).List()) + remove := expandIPPerms(group, os.Difference(ns).List()) + add := expandIPPerms(group, ns.Difference(os).List()) // TODO: We need to handle partial state better in the in-between // in this update. @@ -452,6 +452,11 @@ func resourceAwsSecurityGroupUpdateRules( GroupID: group.GroupID, IPPermissions: add, } + if group.VPCID == nil || *group.VPCID == "" { + req.GroupID = nil + req.GroupName = group.GroupName + } + err = ec2conn.AuthorizeSecurityGroupIngress(req) } diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 617c2bbf94da..3880f3e825fa 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -39,7 +39,10 @@ func expandListeners(configured []interface{}) ([]elb.Listener, error) { // Takes the result of flatmap.Expand for an array of ingress/egress // security group rules and returns EC2 API compatible objects -func expandIPPerms(id string, configured []interface{}) []ec2.IPPermission { +func expandIPPerms( + group ec2.SecurityGroup, configured []interface{}) []ec2.IPPermission { + vpc := group.VPCID != nil + perms := make([]ec2.IPPermission, len(configured)) for i, mRaw := range configured { var perm ec2.IPPermission @@ -57,7 +60,11 @@ func expandIPPerms(id string, configured []interface{}) []ec2.IPPermission { } } if v, ok := m["self"]; ok && v.(bool) { - groups = append(groups, id) + if vpc { + groups = append(groups, *group.GroupID) + } else { + groups = append(groups, *group.GroupName) + } } if len(groups) > 0 { @@ -72,6 +79,11 @@ func expandIPPerms(id string, configured []interface{}) []ec2.IPPermission { GroupID: aws.String(id), UserID: aws.String(ownerId), } + if !vpc { + perm.UserIDGroupPairs[i].GroupID = nil + perm.UserIDGroupPairs[i].GroupName = aws.String(id) + perm.UserIDGroupPairs[i].UserID = nil + } } } diff --git a/builtin/providers/aws/structure_test.go b/builtin/providers/aws/structure_test.go index b85adc51a32c..12af95328abd 100644 --- a/builtin/providers/aws/structure_test.go +++ b/builtin/providers/aws/structure_test.go @@ -59,7 +59,11 @@ func TestExpandIPPerms(t *testing.T) { "self": true, }, } - perms := expandIPPerms("foo", expanded) + group := ec2.SecurityGroup{ + GroupID: aws.String("foo"), + VPCID: aws.String("bar"), + } + perms := expandIPPerms(group, expanded) expected := []ec2.IPPermission{ ec2.IPPermission{ @@ -115,6 +119,79 @@ func TestExpandIPPerms(t *testing.T) { } +func TestExpandIPPerms_nonVPC(t *testing.T) { + hash := func(v interface{}) int { + return hashcode.String(v.(string)) + } + + expanded := []interface{}{ + map[string]interface{}{ + "protocol": "icmp", + "from_port": 1, + "to_port": -1, + "cidr_blocks": []interface{}{"0.0.0.0/0"}, + "security_groups": schema.NewSet(hash, []interface{}{ + "sg-11111", + "foo/sg-22222", + }), + }, + map[string]interface{}{ + "protocol": "icmp", + "from_port": 1, + "to_port": -1, + "self": true, + }, + } + group := ec2.SecurityGroup{ + GroupName: aws.String("foo"), + } + perms := expandIPPerms(group, expanded) + + expected := []ec2.IPPermission{ + ec2.IPPermission{ + IPProtocol: aws.String("icmp"), + FromPort: aws.Integer(1), + ToPort: aws.Integer(-1), + IPRanges: []ec2.IPRange{ec2.IPRange{aws.String("0.0.0.0/0")}}, + UserIDGroupPairs: []ec2.UserIDGroupPair{ + ec2.UserIDGroupPair{ + GroupName: aws.String("sg-22222"), + }, + ec2.UserIDGroupPair{ + GroupName: aws.String("sg-22222"), + }, + }, + }, + ec2.IPPermission{ + IPProtocol: aws.String("icmp"), + FromPort: aws.Integer(1), + ToPort: aws.Integer(-1), + UserIDGroupPairs: []ec2.UserIDGroupPair{ + ec2.UserIDGroupPair{ + GroupName: aws.String("foo"), + }, + }, + }, + } + + exp := expected[0] + perm := perms[0] + + if *exp.FromPort != *perm.FromPort { + t.Fatalf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + *perm.FromPort, + *exp.FromPort) + } + + if *exp.IPRanges[0].CIDRIP != *perm.IPRanges[0].CIDRIP { + t.Fatalf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + *perm.IPRanges[0].CIDRIP, + *exp.IPRanges[0].CIDRIP) + } +} + func TestExpandListeners(t *testing.T) { expanded := []interface{}{ map[string]interface{}{ From ca6f6a1e143d8875450c84478673182bad81ebb8 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 18 Mar 2015 08:57:41 -0500 Subject: [PATCH 32/46] providers/aws: fix TestAccAWSInstance_normal The test just needed updated SHAs for user_data. --- builtin/providers/aws/resource_aws_instance_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 8971435207e7..941dd5dd4125 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -44,7 +44,7 @@ func TestAccAWSInstance_normal(t *testing.T) { resource.TestCheckResourceAttr( "aws_instance.foo", "user_data", - "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), + "3dc39dda39be1205215e776bad998da361a5955d"), ), }, @@ -60,7 +60,7 @@ func TestAccAWSInstance_normal(t *testing.T) { resource.TestCheckResourceAttr( "aws_instance.foo", "user_data", - "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), + "3dc39dda39be1205215e776bad998da361a5955d"), ), }, }, From 1979d9b792c34bd9a49be77782c1a245bcfec536 Mon Sep 17 00:00:00 2001 From: "Michael H. Oshita" Date: Thu, 19 Mar 2015 03:45:32 +0900 Subject: [PATCH 33/46] fix indent align indentation with the rest of the code. --- .../source/docs/providers/aws/r/security_group.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/security_group.html.markdown b/website/source/docs/providers/aws/r/security_group.html.markdown index 869f4bdc52cb..f23bbcf16d29 100644 --- a/website/source/docs/providers/aws/r/security_group.html.markdown +++ b/website/source/docs/providers/aws/r/security_group.html.markdown @@ -17,7 +17,7 @@ Basic usage ``` resource "aws_security_group" "allow_all" { name = "allow_all" - description = "Allow all inbound traffic" + description = "Allow all inbound traffic" ingress { from_port = 0 From 50c49396f494c0f71cade0ae77f8d2baede655e8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 18 Mar 2015 20:48:39 +0100 Subject: [PATCH 34/46] providers/aws: only set instance tenancy if its set /cc @clint --- builtin/providers/aws/resource_aws_instance.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index 74c7b5845eac..5fb4f525aabd 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -264,7 +264,9 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { placement := &ec2.Placement{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Tenancy: aws.String(d.Get("tenancy").(string)), + } + if v := d.Get("tenancy").(string); v != "" { + placement.Tenancy = aws.String(v) } iam := &ec2.IAMInstanceProfileSpecification{ From e84711b46007e37b2e9ac974bad31ed174c1893a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 18 Mar 2015 20:54:44 +0100 Subject: [PATCH 35/46] providers/aws: more classic-mode fixes for instance /cc @catsby - Just a quick note to be careful about checking the nil of a field before cehcking the value (see the subnetid check), to avoid panics --- .../providers/aws/resource_aws_instance.go | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index 5fb4f525aabd..4e966e3b6ce3 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -496,7 +496,7 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { // we use IDs if we're in a VPC. However, if we previously had an // all-name list of security groups, we use names. Or, if we had any // IDs, we use IDs. - useID := *instance.SubnetID != "" + useID := instance.SubnetID != nil && *instance.SubnetID != "" if v := d.Get("security_groups"); v != nil { match := false for _, v := range v.(*schema.Set).List() { @@ -569,18 +569,19 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).ec2conn - opts := new(ec2.ModifyInstanceAttributeRequest) - log.Printf("[INFO] Modifying instance %s: %#v", d.Id(), opts) - err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeRequest{ - InstanceID: aws.String(d.Id()), - SourceDestCheck: &ec2.AttributeBooleanValue{ - Value: aws.Boolean(d.Get("source_dest_check").(bool)), - }, - }) - - if err != nil { - return err + // SourceDestCheck can only be set on VPC instances + if d.Get("subnet_id").(string) != "" { + log.Printf("[INFO] Modifying instance %s", d.Id()) + err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeRequest{ + InstanceID: aws.String(d.Id()), + SourceDestCheck: &ec2.AttributeBooleanValue{ + Value: aws.Boolean(d.Get("source_dest_check").(bool)), + }, + }) + if err != nil { + return err + } } // TODO(mitchellh): wait for the attributes we modified to From 3ba8ed536b373fd403c7f64103765b013164b1a8 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 18 Mar 2015 19:07:29 -0500 Subject: [PATCH 36/46] helper/schema: record schema version on apply We were previously only recording the schema version on refresh. This caused the state to be incorrectly written after a `terraform apply` causing subsequent commands to run the state through an unnecessary migration. --- helper/schema/resource.go | 22 +++++++++++++--------- helper/schema/resource_test.go | 8 ++++++++ terraform/state.go | 9 +++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/helper/schema/resource.go b/helper/schema/resource.go index a19912eed566..797d021ab65e 100644 --- a/helper/schema/resource.go +++ b/helper/schema/resource.go @@ -151,7 +151,7 @@ func (r *Resource) Apply( err = r.Update(data, meta) } - return data.State(), err + return r.recordCurrentSchemaVersion(data.State()), err } // Diff returns a diff of this resource and is API compatible with the @@ -207,14 +207,7 @@ func (r *Resource) Refresh( state = nil } - if state != nil && r.SchemaVersion > 0 { - if state.Meta == nil { - state.Meta = make(map[string]string) - } - state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) - } - - return state, err + return r.recordCurrentSchemaVersion(state), err } // InternalValidate should be called to validate the structure @@ -241,3 +234,14 @@ func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) { stateSchemaVersion, _ := strconv.Atoi(is.Meta["schema_version"]) return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion } + +func (r *Resource) recordCurrentSchemaVersion( + state *terraform.InstanceState) *terraform.InstanceState { + if state != nil && r.SchemaVersion > 0 { + if state.Meta == nil { + state.Meta = make(map[string]string) + } + state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) + } + return state +} diff --git a/helper/schema/resource_test.go b/helper/schema/resource_test.go index b1c42721f666..e406e55b9bb2 100644 --- a/helper/schema/resource_test.go +++ b/helper/schema/resource_test.go @@ -11,6 +11,7 @@ import ( func TestResourceApply_create(t *testing.T) { r := &Resource{ + SchemaVersion: 2, Schema: map[string]*Schema{ "foo": &Schema{ Type: TypeInt, @@ -51,6 +52,9 @@ func TestResourceApply_create(t *testing.T) { "id": "foo", "foo": "42", }, + Meta: map[string]string{ + "schema_version": "2", + }, } if !reflect.DeepEqual(actual, expected) { @@ -339,6 +343,7 @@ func TestResourceInternalValidate(t *testing.T) { func TestResourceRefresh(t *testing.T) { r := &Resource{ + SchemaVersion: 2, Schema: map[string]*Schema{ "foo": &Schema{ Type: TypeInt, @@ -368,6 +373,9 @@ func TestResourceRefresh(t *testing.T) { "id": "bar", "foo": "13", }, + Meta: map[string]string{ + "schema_version": "2", + }, } actual, err := r.Refresh(s, 42) diff --git a/terraform/state.go b/terraform/state.go index 3dbad7ae676c..42e9023baa58 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -843,6 +843,9 @@ func (i *InstanceState) init() { if i.Attributes == nil { i.Attributes = make(map[string]string) } + if i.Meta == nil { + i.Meta = make(map[string]string) + } i.Ephemeral.init() } @@ -860,6 +863,12 @@ func (i *InstanceState) deepcopy() *InstanceState { n.Attributes[k] = v } } + if i.Meta != nil { + n.Meta = make(map[string]string, len(i.Meta)) + for k, v := range i.Meta { + n.Meta[k] = v + } + } return n } From 8ebbaf550cd4033ce575c331026400d0c1e7decf Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 19 Mar 2015 11:14:41 +1000 Subject: [PATCH 37/46] Fixes for goamz removal. --- .../providers/aws/resource_aws_vpn_gateway.go | 16 ++++++++-------- .../aws/resource_aws_vpn_gateway_test.go | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index d6ffcef97fd3..b6ecba581363 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway.go @@ -36,7 +36,7 @@ func resourceAwsVpnGateway() *schema.Resource { } func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn createOpts := &ec2.CreateVPNGatewayRequest{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), @@ -60,7 +60,7 @@ func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn vpnGatewayRaw, _, err := vpnGatewayStateRefreshFunc(ec2conn, d.Id())() if err != nil { @@ -80,7 +80,7 @@ func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error { d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID) } d.Set("availability_zone", vpnGateway.AvailabilityZone) - d.Set("tags", tagsToMapSDK(vpnGateway.Tags)) + d.Set("tags", tagsToMap(vpnGateway.Tags)) return nil } @@ -98,9 +98,9 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error } } - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn - if err := setTagsSDK(ec2conn, d); err != nil { + if err := setTags(ec2conn, d); err != nil { return err } @@ -110,7 +110,7 @@ func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn // Detach if it is attached if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { @@ -144,7 +144,7 @@ func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn if d.Get("vpc_id").(string) == "" { log.Printf( @@ -189,7 +189,7 @@ func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error } func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).ec2conn // Get the old VPC ID to detach from vpcID, _ := d.GetChange("vpc_id") diff --git a/builtin/providers/aws/resource_aws_vpn_gateway_test.go b/builtin/providers/aws/resource_aws_vpn_gateway_test.go index 2d3edbe3bf52..21ccb980c4d3 100644 --- a/builtin/providers/aws/resource_aws_vpn_gateway_test.go +++ b/builtin/providers/aws/resource_aws_vpn_gateway_test.go @@ -98,7 +98,7 @@ func TestAccVpnGateway_tags(t *testing.T) { Config: testAccCheckVpnGatewayConfigTags, Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), - testAccCheckTagsSDK(&v.Tags, "foo", "bar"), + testAccCheckTags(&v.Tags, "foo", "bar"), ), }, @@ -106,8 +106,8 @@ func TestAccVpnGateway_tags(t *testing.T) { Config: testAccCheckVpnGatewayConfigTagsUpdate, Check: resource.ComposeTestCheckFunc( testAccCheckVpnGatewayExists("aws_vpn_gateway.foo", &v), - testAccCheckTagsSDK(&v.Tags, "foo", ""), - testAccCheckTagsSDK(&v.Tags, "bar", "baz"), + testAccCheckTags(&v.Tags, "foo", ""), + testAccCheckTags(&v.Tags, "bar", "baz"), ), }, }, @@ -115,7 +115,7 @@ func TestAccVpnGateway_tags(t *testing.T) { } func testAccCheckVpnGatewayDestroy(s *terraform.State) error { - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_vpn_gateway" { @@ -158,7 +158,7 @@ func testAccCheckVpnGatewayExists(n string, ig *ec2.VPNGateway) resource.TestChe return fmt.Errorf("No ID is set") } - ec2conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn resp, err := ec2conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{ VPNGatewayIDs: []string{rs.Primary.ID}, }) From 49f850f13bfe02ae05f9ad763ce3fa4ca823333d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Mar 2015 09:50:38 +0100 Subject: [PATCH 38/46] update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8470f90b69a6..79ca1d5f24cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ BACKWARDS INCOMPATIBILITIES: the `remote` command: `terraform remote push` and `terraform remote pull`. The old `remote` functionality is now at `terraform remote config`. This consolidates all remote state management under one command. + * Period-prefixed configuration files are now ignored. This might break + existing Terraform configurations if you had period-prefixed files. FEATURES: @@ -35,6 +37,7 @@ IMPROVEMENTS: change. This will lower the amount of state changing on things like refresh. * core: Autoload `terraform.tfvars.json` as well as `terraform.tfvars` [GH-1030] + * core: `.tf` files that start with a period are now ignored. [GH-1227] BUG FIXES: From f84ae29cf8236cc5751161bcbf88688780ecfa4c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Mar 2015 09:51:14 +0100 Subject: [PATCH 39/46] config: isTemporaryFile -> isIgnoredFile --- config/loader.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/config/loader.go b/config/loader.go index fe1494ff8b07..1848f314d1c5 100644 --- a/config/loader.go +++ b/config/loader.go @@ -162,7 +162,7 @@ func dirFiles(dir string) ([]string, []string, error) { // Only care about files that are valid to load name := fi.Name() extValue := ext(name) - if extValue == "" || isTemporaryFile(name) { + if extValue == "" || isIgnoredFile(name) { continue } @@ -183,10 +183,9 @@ func dirFiles(dir string) ([]string, []string, error) { return files, overrides, nil } -// isTemporaryFile returns true or false depending on whether the -// provided file name is a temporary file for the following editors: -// emacs or vim. -func isTemporaryFile(name string) bool { +// isIgnoredFile returns true or false depending on whether the +// provided file name is a file that should be ignored. +func isIgnoredFile(name string) bool { return strings.HasPrefix(name, ".") || // Unix-like hidden files strings.HasSuffix(name, "~") || // vim (strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs From 2b23c402eeaf579e20e11375a25bd7879e211f9e Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Tue, 24 Feb 2015 11:00:22 -0600 Subject: [PATCH 40/46] providers/aws: rework instance block devices Instance block devices are now managed by three distinct sub-resources: * `root_block_device` - introduced previously * `ebs_block_device` - all additional ebs-backed volumes * `ephemeral_block_device` - instance store / ephemeral devices The AWS API support around BlockDeviceMapping is pretty confusing. It's a single collection type that supports these three members each of which has different fields and different behavior. My biggest hiccup came from the fact that Instance Store volumes do not show up in any response BlockDeviceMapping for any EC2 `Describe*` API calls. They're only available from the instance meta-data service as queried from inside the node. This removes `block_device` altogether for a clean break from old configs. New configs will need to sort their `block_device` declarations into the three new types. The field has been marked `Removed` to indicate this to users. With the new block device format being introduced, we need to ensure Terraform is able to properly read statefiles written in the old format. So we use the new `helper/schema` facility of "state migrations" to transform statefiles in the old format to something that the current version of the schema can use. Fixes #858 --- .../providers/aws/resource_aws_instance.go | 332 ++++++++++++------ .../aws/resource_aws_instance_migrate.go | 107 ++++++ .../aws/resource_aws_instance_migrate_test.go | 135 +++++++ .../aws/resource_aws_instance_test.go | 41 ++- .../providers/aws/r/instance.html.markdown | 84 ++++- 5 files changed, 566 insertions(+), 133 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_instance_migrate.go create mode 100644 builtin/providers/aws/resource_aws_instance_migrate_test.go diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index 74c7b5845eac..9ef546764af5 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -24,6 +24,9 @@ func resourceAwsInstance() *schema.Resource { Update: resourceAwsInstanceUpdate, Delete: resourceAwsInstanceDelete, + SchemaVersion: 1, + MigrateState: resourceAwsInstanceMigrateState, + Schema: map[string]*schema.Schema{ "ami": &schema.Schema{ Type: schema.TypeString, @@ -127,40 +130,56 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, Optional: true, }, + "tenancy": &schema.Schema{ Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, + "tags": tagsSchema(), "block_device": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Removed: "Split out into three sub-types; see Changelog and Docs", + }, + + "ebs_block_device": &schema.Schema{ Type: schema.TypeSet, Optional: true, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "delete_on_termination": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + "device_name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "virtual_name": &schema.Schema{ - Type: schema.TypeString, + "encrypted": &schema.Schema{ + Type: schema.TypeBool, Optional: true, + Computed: true, ForceNew: true, }, - "snapshot_id": &schema.Schema{ - Type: schema.TypeString, + "iops": &schema.Schema{ + Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, }, - "volume_type": &schema.Schema{ + "snapshot_id": &schema.Schema{ Type: schema.TypeString, Optional: true, Computed: true, @@ -174,37 +193,64 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, }, - "delete_on_termination": &schema.Schema{ - Type: schema.TypeBool, + "volume_type": &schema.Schema{ + Type: schema.TypeString, Optional: true, - Default: true, + Computed: true, ForceNew: true, }, + }, + }, + Set: func(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) + buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) + buf.WriteString(fmt.Sprintf("%t-", m["encrypted"].(bool))) + // NOTE: Not considering IOPS in hash; when using gp2, IOPS can come + // back set to something like "33", which throws off the set + // calculation and generates an unresolvable diff. + // buf.WriteString(fmt.Sprintf("%d-", m["iops"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string))) + buf.WriteString(fmt.Sprintf("%d-", m["volume_size"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["volume_type"].(string))) + return hashcode.String(buf.String()) + }, + }, - "encrypted": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Computed: true, - ForceNew: true, + "ephemeral_block_device": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, }, - "iops": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, + "virtual_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, }, }, }, - Set: resourceAwsInstanceBlockDevicesHash, + Set: func(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) + return hashcode.String(buf.String()) + }, }, "root_block_device": &schema.Schema{ - // TODO: This is a list because we don't support singleton - // sub-resources today. We'll enforce that the list only ever has + // TODO: This is a set because we don't support singleton + // sub-resources today. We'll enforce that the set only ever has // length zero or one below. When TF gains support for // sub-resources this can be converted. - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Computed: true, Elem: &schema.Resource{ @@ -226,28 +272,39 @@ func resourceAwsInstance() *schema.Resource { Default: "/dev/sda1", }, - "volume_size": &schema.Schema{ + "iops": &schema.Schema{ Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, }, - "volume_type": &schema.Schema{ - Type: schema.TypeString, + "volume_size": &schema.Schema{ + Type: schema.TypeInt, Optional: true, Computed: true, ForceNew: true, }, - "iops": &schema.Schema{ - Type: schema.TypeInt, + "volume_type": &schema.Schema{ + Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, }, }, + Set: func(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) + buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) + // See the NOTE in "ebs_block_device" for why we skip iops here. + // buf.WriteString(fmt.Sprintf("%d-", m["iops"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["volume_size"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["volume_type"].(string))) + return hashcode.String(buf.String()) + }, }, }, } @@ -347,46 +404,87 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { runOpts.KeyName = aws.String(v.(string)) } - blockDevices := make([]interface{}, 0) + blockDevices := make([]ec2.BlockDeviceMapping, 0) + + if v, ok := d.GetOk("ebs_block_device"); ok { + vL := v.(*schema.Set).List() + for _, v := range vL { + bd := v.(map[string]interface{}) + ebs := &ec2.EBSBlockDevice{ + DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), + } + + if v, ok := bd["snapshot_id"].(string); ok && v != "" { + ebs.SnapshotID = aws.String(v) + } - if v := d.Get("block_device"); v != nil { - blockDevices = append(blockDevices, v.(*schema.Set).List()...) + if v, ok := bd["volume_size"].(int); ok && v != 0 { + ebs.VolumeSize = aws.Integer(v) + } + + if v, ok := bd["volume_type"].(string); ok && v != "" { + ebs.VolumeType = aws.String(v) + } + + if v, ok := bd["iops"].(int); ok && v > 0 { + ebs.IOPS = aws.Integer(v) + } + + blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ + DeviceName: aws.String(bd["device_name"].(string)), + EBS: ebs, + }) + } } - if v := d.Get("root_block_device"); v != nil { - rootBlockDevices := v.([]interface{}) - if len(rootBlockDevices) > 1 { - return fmt.Errorf("Cannot specify more than one root_block_device.") + if v, ok := d.GetOk("ephemeral_block_device"); ok { + vL := v.(*schema.Set).List() + for _, v := range vL { + bd := v.(map[string]interface{}) + blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ + DeviceName: aws.String(bd["device_name"].(string)), + VirtualName: aws.String(bd["virtual_name"].(string)), + }) } - blockDevices = append(blockDevices, rootBlockDevices...) + // if err := d.Set("ephemeral_block_device", vL); err != nil { + // return err + // } } - if len(blockDevices) > 0 { - runOpts.BlockDeviceMappings = make([]ec2.BlockDeviceMapping, len(blockDevices)) - for i, v := range blockDevices { + if v, ok := d.GetOk("root_block_device"); ok { + vL := v.(*schema.Set).List() + if len(vL) > 1 { + return fmt.Errorf("Cannot specify more than one root_block_device.") + } + for _, v := range vL { bd := v.(map[string]interface{}) - runOpts.BlockDeviceMappings[i].DeviceName = aws.String(bd["device_name"].(string)) - runOpts.BlockDeviceMappings[i].EBS = &ec2.EBSBlockDevice{ - VolumeType: aws.String(bd["volume_type"].(string)), - VolumeSize: aws.Integer(bd["volume_size"].(int)), + ebs := &ec2.EBSBlockDevice{ DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), } - if v, ok := bd["virtual_name"].(string); ok { - runOpts.BlockDeviceMappings[i].VirtualName = aws.String(v) + if v, ok := bd["volume_size"].(int); ok && v != 0 { + ebs.VolumeSize = aws.Integer(v) } - if v, ok := bd["snapshot_id"].(string); ok && v != "" { - runOpts.BlockDeviceMappings[i].EBS.SnapshotID = aws.String(v) - } - if v, ok := bd["encrypted"].(bool); ok { - runOpts.BlockDeviceMappings[i].EBS.Encrypted = aws.Boolean(v) + + if v, ok := bd["volume_type"].(string); ok && v != "" { + ebs.VolumeType = aws.String(v) } + if v, ok := bd["iops"].(int); ok && v > 0 { - runOpts.BlockDeviceMappings[i].EBS.IOPS = aws.Integer(v) + ebs.IOPS = aws.Integer(v) } + + blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ + DeviceName: aws.String(bd["device_name"].(string)), + EBS: ebs, + }) } } + if len(blockDevices) > 0 { + runOpts.BlockDeviceMappings = blockDevices + } + // Create the instance log.Printf("[DEBUG] Run configuration: %#v", runOpts) runResp, err := ec2conn.RunInstances(runOpts) @@ -518,50 +616,10 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { } d.Set("security_groups", sgs) - blockDevices := make(map[string]ec2.InstanceBlockDeviceMapping) - for _, bd := range instance.BlockDeviceMappings { - blockDevices[*bd.EBS.VolumeID] = bd - } - - volIDs := make([]string, 0, len(blockDevices)) - for _, vol := range blockDevices { - volIDs = append(volIDs, *vol.EBS.VolumeID) - } - - volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{ - VolumeIDs: volIDs, - }) - if err != nil { + if err := readBlockDevices(d, instance, ec2conn); err != nil { return err } - nonRootBlockDevices := make([]map[string]interface{}, 0) - rootBlockDevice := make([]interface{}, 0, 1) - for _, vol := range volResp.Volumes { - blockDevice := make(map[string]interface{}) - blockDevice["device_name"] = *blockDevices[*vol.VolumeID].DeviceName - blockDevice["volume_type"] = *vol.VolumeType - blockDevice["volume_size"] = *vol.Size - if vol.IOPS != nil { - blockDevice["iops"] = *vol.IOPS - } - blockDevice["delete_on_termination"] = - *blockDevices[*vol.VolumeID].EBS.DeleteOnTermination - - // If this is the root device, save it. We stop here since we - // can't put invalid keys into this map. - if blockDevice["device_name"] == *instance.RootDeviceName { - rootBlockDevice = []interface{}{blockDevice} - continue - } - - blockDevice["snapshot_id"] = *vol.SnapshotID - blockDevice["encrypted"] = *vol.Encrypted - nonRootBlockDevices = append(nonRootBlockDevices, blockDevice) - } - d.Set("block_device", nonRootBlockDevices) - d.Set("root_block_device", rootBlockDevice) - return nil } @@ -656,11 +714,89 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRe } } -func resourceAwsInstanceBlockDevicesHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string))) - buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) - return hashcode.String(buf.String()) +func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, ec2conn *ec2.EC2) error { + ibds, err := readBlockDevicesFromInstance(instance, ec2conn) + if err != nil { + return err + } + + if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil { + return err + } + if ibds["root"] != nil { + if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil { + return err + } + } + + return nil +} + +func readBlockDevicesFromInstance(instance *ec2.Instance, ec2conn *ec2.EC2) (map[string]interface{}, error) { + blockDevices := make(map[string]interface{}) + blockDevices["ebs"] = make([]map[string]interface{}, 0) + blockDevices["root"] = nil + + instanceBlockDevices := make(map[string]ec2.InstanceBlockDeviceMapping) + for _, bd := range instance.BlockDeviceMappings { + if bd.EBS != nil { + instanceBlockDevices[*(bd.EBS.VolumeID)] = bd + } + } + + volIDs := make([]string, 0, len(instanceBlockDevices)) + for volID := range instanceBlockDevices { + volIDs = append(volIDs, volID) + } + + // Need to call DescribeVolumes to get volume_size and volume_type for each + // EBS block device + volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{ + VolumeIDs: volIDs, + }) + if err != nil { + return nil, err + } + + for _, vol := range volResp.Volumes { + instanceBd := instanceBlockDevices[*vol.VolumeID] + bd := make(map[string]interface{}) + + if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil { + bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination + } + if instanceBd.DeviceName != nil { + bd["device_name"] = *instanceBd.DeviceName + } + if vol.Size != nil { + bd["volume_size"] = *vol.Size + } + if vol.VolumeType != nil { + bd["volume_type"] = *vol.VolumeType + } + if vol.IOPS != nil { + bd["iops"] = *vol.IOPS + } + + if blockDeviceIsRoot(instanceBd, instance) { + blockDevices["root"] = bd + } else { + if vol.Encrypted != nil { + bd["encrypted"] = *vol.Encrypted + } + if vol.SnapshotID != nil { + bd["snapshot_id"] = *vol.SnapshotID + } + + blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd) + } + } + + return blockDevices, nil +} + +func blockDeviceIsRoot(bd ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool { + return (bd.DeviceName != nil && + instance.RootDeviceName != nil && + *bd.DeviceName == *instance.RootDeviceName) } diff --git a/builtin/providers/aws/resource_aws_instance_migrate.go b/builtin/providers/aws/resource_aws_instance_migrate.go new file mode 100644 index 000000000000..adb7a01ec9f5 --- /dev/null +++ b/builtin/providers/aws/resource_aws_instance_migrate.go @@ -0,0 +1,107 @@ +package aws + +import ( + "fmt" + "log" + "strconv" + "strings" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/terraform" +) + +func resourceAwsInstanceMigrateState( + v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { + switch v { + case 0: + log.Println("[INFO] Found AWS Instance State v0; migrating to v1") + return migrateStateV0toV1(is) + default: + return is, fmt.Errorf("Unexpected schema version: %d", v) + } + + return is, nil +} + +func migrateStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { + log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes) + // Delete old count + delete(is.Attributes, "block_device.#") + + oldBds, err := readV0BlockDevices(is) + if err != nil { + return is, err + } + // seed count fields for new types + is.Attributes["ebs_block_device.#"] = "0" + is.Attributes["ephemeral_block_device.#"] = "0" + // depending on if state was v0.3.7 or an earlier version, it might have + // root_block_device defined already + if _, ok := is.Attributes["root_block_device.#"]; !ok { + is.Attributes["root_block_device.#"] = "0" + } + for _, oldBd := range oldBds { + if err := writeV1BlockDevice(is, oldBd); err != nil { + return is, err + } + } + log.Printf("[DEBUG] Attributes after migration: %#v", is.Attributes) + return is, nil +} + +func readV0BlockDevices(is *terraform.InstanceState) (map[string]map[string]string, error) { + oldBds := make(map[string]map[string]string) + for k, v := range is.Attributes { + if !strings.HasPrefix(k, "block_device.") { + continue + } + path := strings.Split(k, ".") + if len(path) != 3 { + return oldBds, fmt.Errorf("Found unexpected block_device field: %#v", k) + } + hashcode, attribute := path[1], path[2] + oldBd, ok := oldBds[hashcode] + if !ok { + oldBd = make(map[string]string) + oldBds[hashcode] = oldBd + } + oldBd[attribute] = v + delete(is.Attributes, k) + } + return oldBds, nil +} + +func writeV1BlockDevice( + is *terraform.InstanceState, oldBd map[string]string) error { + code := hashcode.String(oldBd["device_name"]) + bdType := "ebs_block_device" + if vn, ok := oldBd["virtual_name"]; ok && strings.HasPrefix(vn, "ephemeral") { + bdType = "ephemeral_block_device" + } else if dn, ok := oldBd["device_name"]; ok && dn == "/dev/sda1" { + bdType = "root_block_device" + } + + switch bdType { + case "ebs_block_device": + delete(oldBd, "virtual_name") + case "root_block_device": + delete(oldBd, "virtual_name") + delete(oldBd, "encrypted") + delete(oldBd, "snapshot_id") + case "ephemeral_block_device": + delete(oldBd, "delete_on_termination") + delete(oldBd, "encrypted") + delete(oldBd, "iops") + delete(oldBd, "volume_size") + delete(oldBd, "volume_type") + } + for attr, val := range oldBd { + attrKey := fmt.Sprintf("%s.%d.%s", bdType, code, attr) + is.Attributes[attrKey] = val + } + + countAttr := fmt.Sprintf("%s.#", bdType) + count, _ := strconv.Atoi(is.Attributes[countAttr]) + is.Attributes[countAttr] = strconv.Itoa(count + 1) + return nil +} diff --git a/builtin/providers/aws/resource_aws_instance_migrate_test.go b/builtin/providers/aws/resource_aws_instance_migrate_test.go new file mode 100644 index 000000000000..5738f2508e86 --- /dev/null +++ b/builtin/providers/aws/resource_aws_instance_migrate_test.go @@ -0,0 +1,135 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestAWSInstanceMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + Expected map[string]string + Meta interface{} + }{ + "v0.3.6 and earlier": { + StateVersion: 0, + Attributes: map[string]string{ + // EBS + "block_device.#": "2", + "block_device.3851383343.delete_on_termination": "true", + "block_device.3851383343.device_name": "/dev/sdx", + "block_device.3851383343.encrypted": "false", + "block_device.3851383343.snapshot_id": "", + "block_device.3851383343.virtual_name": "", + "block_device.3851383343.volume_size": "5", + "block_device.3851383343.volume_type": "standard", + // Ephemeral + "block_device.3101711606.delete_on_termination": "false", + "block_device.3101711606.device_name": "/dev/sdy", + "block_device.3101711606.encrypted": "false", + "block_device.3101711606.snapshot_id": "", + "block_device.3101711606.virtual_name": "ephemeral0", + "block_device.3101711606.volume_size": "", + "block_device.3101711606.volume_type": "", + // Root + "block_device.56575650.delete_on_termination": "true", + "block_device.56575650.device_name": "/dev/sda1", + "block_device.56575650.encrypted": "false", + "block_device.56575650.snapshot_id": "", + "block_device.56575650.volume_size": "10", + "block_device.56575650.volume_type": "standard", + }, + Expected: map[string]string{ + "ebs_block_device.#": "1", + "ebs_block_device.3851383343.delete_on_termination": "true", + "ebs_block_device.3851383343.device_name": "/dev/sdx", + "ebs_block_device.3851383343.encrypted": "false", + "ebs_block_device.3851383343.snapshot_id": "", + "ebs_block_device.3851383343.volume_size": "5", + "ebs_block_device.3851383343.volume_type": "standard", + "ephemeral_block_device.#": "1", + "ephemeral_block_device.2458403513.device_name": "/dev/sdy", + "ephemeral_block_device.2458403513.virtual_name": "ephemeral0", + "root_block_device.#": "1", + "root_block_device.3018388612.delete_on_termination": "true", + "root_block_device.3018388612.device_name": "/dev/sda1", + "root_block_device.3018388612.snapshot_id": "", + "root_block_device.3018388612.volume_size": "10", + "root_block_device.3018388612.volume_type": "standard", + }, + }, + "v0.3.7": { + StateVersion: 0, + Attributes: map[string]string{ + // EBS + "block_device.#": "2", + "block_device.3851383343.delete_on_termination": "true", + "block_device.3851383343.device_name": "/dev/sdx", + "block_device.3851383343.encrypted": "false", + "block_device.3851383343.snapshot_id": "", + "block_device.3851383343.virtual_name": "", + "block_device.3851383343.volume_size": "5", + "block_device.3851383343.volume_type": "standard", + "block_device.3851383343.iops": "", + // Ephemeral + "block_device.3101711606.delete_on_termination": "false", + "block_device.3101711606.device_name": "/dev/sdy", + "block_device.3101711606.encrypted": "false", + "block_device.3101711606.snapshot_id": "", + "block_device.3101711606.virtual_name": "ephemeral0", + "block_device.3101711606.volume_size": "", + "block_device.3101711606.volume_type": "", + "block_device.3101711606.iops": "", + // Root + "root_block_device.#": "1", + "root_block_device.3018388612.delete_on_termination": "true", + "root_block_device.3018388612.device_name": "/dev/sda1", + "root_block_device.3018388612.snapshot_id": "", + "root_block_device.3018388612.volume_size": "10", + "root_block_device.3018388612.volume_type": "io1", + "root_block_device.3018388612.iops": "1000", + }, + Expected: map[string]string{ + "ebs_block_device.#": "1", + "ebs_block_device.3851383343.delete_on_termination": "true", + "ebs_block_device.3851383343.device_name": "/dev/sdx", + "ebs_block_device.3851383343.encrypted": "false", + "ebs_block_device.3851383343.snapshot_id": "", + "ebs_block_device.3851383343.volume_size": "5", + "ebs_block_device.3851383343.volume_type": "standard", + "ephemeral_block_device.#": "1", + "ephemeral_block_device.2458403513.device_name": "/dev/sdy", + "ephemeral_block_device.2458403513.virtual_name": "ephemeral0", + "root_block_device.#": "1", + "root_block_device.3018388612.delete_on_termination": "true", + "root_block_device.3018388612.device_name": "/dev/sda1", + "root_block_device.3018388612.snapshot_id": "", + "root_block_device.3018388612.volume_size": "10", + "root_block_device.3018388612.volume_type": "io1", + "root_block_device.3018388612.iops": "1000", + }, + }, + } + + for tn, tc := range cases { + is := &terraform.InstanceState{ + Attributes: tc.Attributes, + } + is, err := resourceAwsInstanceMigrateState( + tc.StateVersion, is, tc.Meta) + + if err != nil { + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + for k, v := range tc.Expected { + if is.Attributes[k] != v { + t.Fatalf( + "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", + tn, k, v, k, is.Attributes[k], is.Attributes) + } + } + } +} diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 941dd5dd4125..9e847c43b4f8 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -111,31 +111,33 @@ func TestAccAWSInstance_blockDevices(t *testing.T) { resource.TestCheckResourceAttr( "aws_instance.foo", "root_block_device.#", "1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.0.device_name", "/dev/sda1"), + "aws_instance.foo", "root_block_device.3018388612.device_name", "/dev/sda1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.0.volume_size", "11"), - // this one is important because it's the only root_block_device - // attribute that comes back from the API. so checking it verifies - // that we set state properly + "aws_instance.foo", "root_block_device.3018388612.volume_size", "11"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.0.volume_type", "gp2"), + "aws_instance.foo", "root_block_device.3018388612.volume_type", "gp2"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.#", "2"), + "aws_instance.foo", "ebs_block_device.#", "2"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.172787947.device_name", "/dev/sdb"), + "aws_instance.foo", "ebs_block_device.418220885.device_name", "/dev/sdb"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.172787947.volume_size", "9"), + "aws_instance.foo", "ebs_block_device.418220885.volume_size", "9"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.172787947.iops", "0"), - // Check provisioned SSD device + "aws_instance.foo", "ebs_block_device.418220885.volume_type", "standard"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.3336996981.volume_type", "io1"), + "aws_instance.foo", "ebs_block_device.1877654467.device_name", "/dev/sdc"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.3336996981.device_name", "/dev/sdc"), + "aws_instance.foo", "ebs_block_device.1877654467.volume_size", "10"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.3336996981.volume_size", "10"), + "aws_instance.foo", "ebs_block_device.1877654467.volume_type", "io1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "block_device.3336996981.iops", "100"), + "aws_instance.foo", "ebs_block_device.1877654467.iops", "100"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.#", "1"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.2087552357.device_name", "/dev/sde"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ephemeral_block_device.2087552357.virtual_name", "ephemeral0"), testCheck(), ), }, @@ -420,21 +422,26 @@ resource "aws_instance" "foo" { # us-west-2 ami = "ami-55a7ea65" instance_type = "m1.small" + root_block_device { device_name = "/dev/sda1" volume_type = "gp2" volume_size = 11 } - block_device { + ebs_block_device { device_name = "/dev/sdb" volume_size = 9 } - block_device { + ebs_block_device { device_name = "/dev/sdc" volume_size = 10 volume_type = "io1" iops = 100 } + ephemeral_block_device { + device_name = "/dev/sde" + virtual_name = "ephemeral0" + } } ` diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index 94f042af3fb7..545dff44b79f 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -14,7 +14,8 @@ and deleted. Instances also support [provisioning](/docs/provisioners/index.html ## Example Usage ``` -# Create a new instance of the ami-1234 on an m1.small node with an AWS Tag naming it "HelloWorld" +# Create a new instance of the ami-1234 on an m1.small node +# with an AWS Tag naming it "HelloWorld" resource "aws_instance" "web" { ami = "ami-1234" instance_type = "m1.small" @@ -47,32 +48,79 @@ The following arguments are supported: * `iam_instance_profile` - (Optional) The IAM Instance Profile to launch the instance with. * `tags` - (Optional) A mapping of tags to assign to the resource. -* `block_device` - (Optional) A list of block devices to add. Their keys are documented below. * `root_block_device` - (Optional) Customize details about the root block - device of the instance. Available keys are documented below. + device of the instance. See [Block Devices](#block-devices) below for details. +* `ebs_block_device` - (Optional) Additional EBS block devices to attach to the + instance. See [Block Devices](#block-devices) below for details. +* `ephemeral_block_device` - (Optional) Customize Ephemeral (also known as + "Instance Store") volumes on the instance. See [Block Devices](#block-devices) below for details. -Each `block_device` supports the following: -* `device_name` - The name of the device to mount. -* `virtual_name` - (Optional) The virtual device name. -* `snapshot_id` - (Optional) The Snapshot ID to mount. -* `volume_type` - (Optional) The type of volume. Can be standard, gp2, or io1. Defaults to standard. -* `volume_size` - (Optional) The size of the volume in gigabytes. -* `iops` - (Optional) The amount of provisioned IOPS. Setting this implies a - volume_type of "io1". -* `delete_on_termination` - (Optional) Should the volume be destroyed on instance termination (defaults true). -* `encrypted` - (Optional) Should encryption be enabled (defaults false). + +## Block devices + +Each of the `*_block_device` attributes controls a portion of the AWS +Instance's "Block Device Mapping". It's a good idea to familiarize yourself with [AWS's Block Device +Mapping docs](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html) +to understand the implications of using these attributes. The `root_block_device` mapping supports the following: * `device_name` - The name of the root device on the target instance. Must - match the root device as defined in the AMI. Defaults to "/dev/sda1", which + match the root device as defined in the AMI. Defaults to `"/dev/sda1"`, which is the typical root volume for Linux instances. -* `volume_type` - (Optional) The type of volume. Can be standard, gp2, or io1. Defaults to standard. +* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`, + or `"io1"`. (Default: `"standard"`). * `volume_size` - (Optional) The size of the volume in gigabytes. -* `iops` - (Optional) The amount of provisioned IOPS. Setting this implies a - volume_type of "io1". -* `delete_on_termination` - (Optional) Should the volume be destroyed on instance termination (defaults true). +* `iops` - (Optional) The amount of provisioned + [IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). + This must be set with a `volume_type` of `"io1"`. +* `delete_on_termination` - (Optional) Whether the volume should be destroyed + on instance termination (Default: `true`). + +Modifying any of the `root_block_device` settings requires resource +replacement. + +Each `ebs_block_device` supports the following: + +* `device_name` - The name of the device to mount. +* `snapshot_id` - (Optional) The Snapshot ID to mount. +* `volume_type` - (Optional) The type of volume. Can be `"standard"`, `"gp2"`, + or `"io1"`. (Default: `"standard"`). +* `volume_size` - (Optional) The size of the volume in gigabytes. +* `iops` - (Optional) The amount of provisioned + [IOPS](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html). + This must be set with a `volume_type` of `"io1"`. +* `delete_on_termination` - (Optional) Whether the volume should be destroyed + on instance termination (Default: `true`). +* `encrypted` - (Optional) Enables [EBS + encryption](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html) + on the volume (Default: `false`). + +Modifying any `ebs_block_device` currently requires resource replacement. + +Each `ephemeral_block_device` supports the following: + +* `device_name` - The name of the block device to mount on the instance. +* `virtual_name` - The [Instance Store Device + Name](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#InstanceStoreDeviceNames) + (e.g. `"ephemeral0"`) + +Each AWS Instance type has a different set of Instance Store block devices +available for attachment. AWS [publishes a +list](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#StorageOnInstanceTypes) +of which ephemeral devices are available on each type. The devices are always +identified by the `virtual_name` in the format `"ephemeral{0..N}"`. + + +~> **NOTE:** Because AWS [does not expose Instance Store mapping +details](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html#bdm-instance-metadata) +via an externally accessible API, `ephemeral_block_device` configuration may +only be applied at instance creation time, and changes to configuration of +existing resources cannot be detected by Terraform. Updates to Instance Store +block device configuration can be manually triggered by using the [`taint` +command](/docs/commands/taint.html). + ## Attributes Reference From 5fca25ae5eaadf63f514ce67d27f72572b90c2d6 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Thu, 19 Mar 2015 09:19:10 -0500 Subject: [PATCH 41/46] providers/aws: remove commented code oopsie! --- builtin/providers/aws/resource_aws_instance.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index b19c90304a56..40e6d64b0156 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -448,9 +448,6 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { VirtualName: aws.String(bd["virtual_name"].(string)), }) } - // if err := d.Set("ephemeral_block_device", vL); err != nil { - // return err - // } } if v, ok := d.GetOk("root_block_device"); ok { From c1ccbb5c7d1baeae754996ff6ed2df5cf508771a Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 19 Mar 2015 10:07:46 -0500 Subject: [PATCH 42/46] provider/aws: Add VPC guards for Tenancy, SourceDestCheck --- .../providers/aws/resource_aws_instance.go | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index 40e6d64b0156..20d0701e50dc 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -319,11 +319,21 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { userData = base64.StdEncoding.EncodeToString([]byte(v.(string))) } + // check for non-default Subnet, and cast it to a String + var hasSubnet bool + subnet, hasSubnet := d.GetOk("subnet_id") + subnetID := subnet.(string) + placement := &ec2.Placement{ AvailabilityZone: aws.String(d.Get("availability_zone").(string)), } - if v := d.Get("tenancy").(string); v != "" { - placement.Tenancy = aws.String(v) + + if hasSubnet { + // Tenancy is only valid inside a VPC + // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Placement.html + if v := d.Get("tenancy").(string); v != "" { + placement.Tenancy = aws.String(v) + } } iam := &ec2.IAMInstanceProfileSpecification{ @@ -347,11 +357,6 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { associatePublicIPAddress = v.(bool) } - // check for non-default Subnet, and cast it to a String - var hasSubnet bool - subnet, hasSubnet := d.GetOk("subnet_id") - subnetID := subnet.(string) - var groups []string if v := d.Get("security_groups"); v != nil { // Security group names. @@ -570,13 +575,18 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { return nil } - d.Set("availability_zone", instance.Placement.AvailabilityZone) + if instance.Placement != nil { + d.Set("availability_zone", instance.Placement.AvailabilityZone) + } + if instance.Placement.Tenancy != nil { + d.Set("tenancy", instance.Placement.Tenancy) + } + d.Set("key_name", instance.KeyName) d.Set("public_dns", instance.PublicDNSName) d.Set("public_ip", instance.PublicIPAddress) d.Set("private_dns", instance.PrivateDNSName) d.Set("private_ip", instance.PrivateIPAddress) - d.Set("subnet_id", instance.SubnetID) if len(instance.NetworkInterfaces) > 0 { d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID) } else { @@ -584,7 +594,6 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { } d.Set("ebs_optimized", instance.EBSOptimized) d.Set("tags", tagsToMap(instance.Tags)) - d.Set("tenancy", instance.Placement.Tenancy) // Determine whether we're referring to security groups with // IDs or names. We use a heuristic to figure this out. By default, From a063ebe9922f4df70c7967fb5b1ac53fdeb2d895 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 19 Mar 2015 11:07:01 -0500 Subject: [PATCH 43/46] provider/aws: Update tag support in AWS Elastic Network Interfaces --- CHANGELOG.md | 1 + .../aws/resource_aws_network_interface.go | 14 +++++++++++--- .../aws/resource_aws_network_interface_test.go | 4 ++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ca1d5f24cb..9769581af00c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ IMPROVEMENTS: * **New config function: `split`** - Split a value based on a delimiter. This is useful for faking lists as parameters to modules. * **New resource: `digitalocean_ssh_key`** [GH-1074] + * **New resource: `aws_elastic_network_interfaces`** [GH-1149] * core: The serial of the state is only updated if there is an actual change. This will lower the amount of state changing on things like refresh. diff --git a/builtin/providers/aws/resource_aws_network_interface.go b/builtin/providers/aws/resource_aws_network_interface.go index c4829f4b8cb1..242e0d80c6a5 100644 --- a/builtin/providers/aws/resource_aws_network_interface.go +++ b/builtin/providers/aws/resource_aws_network_interface.go @@ -124,8 +124,11 @@ func resourceAwsNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) e d.Set("private_ips", flattenNetworkInterfacesPrivateIPAddesses(eni.PrivateIPAddresses)) d.Set("security_groups", flattenGroupIdentifiers(eni.Groups)) + // Tags + d.Set("tags", tagsToMap(eni.TagSet)) + if eni.Attachment != nil { - attachment := []map[string]interface{} { flattenAttachment(eni.Attachment) } + attachment := []map[string]interface{}{flattenAttachment(eni.Attachment)} d.Set("attachment", attachment) } else { d.Set("attachment", nil) @@ -185,7 +188,7 @@ func resourceAwsNetworkInterfaceDetach(oa *schema.Set, meta interface{}, eniId s } func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { - + ec2conn := meta.(*AWSClient).ec2conn d.Partial(true) if d.HasChange("attachment") { @@ -220,7 +223,6 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) Groups: expandStringList(d.Get("security_groups").(*schema.Set).List()), } - ec2conn := meta.(*AWSClient).ec2conn err := ec2conn.ModifyNetworkInterfaceAttribute(request) if err != nil { return fmt.Errorf("Failure updating ENI: %s", err) @@ -229,6 +231,12 @@ func resourceAwsNetworkInterfaceUpdate(d *schema.ResourceData, meta interface{}) d.SetPartial("security_groups") } + if err := setTags(ec2conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + d.Partial(false) return resourceAwsNetworkInterfaceRead(d, meta) diff --git a/builtin/providers/aws/resource_aws_network_interface_test.go b/builtin/providers/aws/resource_aws_network_interface_test.go index 413533e566df..5c65cfa0c3b8 100644 --- a/builtin/providers/aws/resource_aws_network_interface_test.go +++ b/builtin/providers/aws/resource_aws_network_interface_test.go @@ -107,6 +107,10 @@ func testAccCheckAWSENIAttributes(conf *ec2.NetworkInterface) resource.TestCheck return fmt.Errorf("expected private ip to be 172.16.10.100, but was %s", *conf.PrivateIPAddress) } + if len(conf.TagSet) == 0 { + return fmt.Errorf("expected tags") + } + return nil } } From f990c3b02bfe2ed457ea8a6d083767cd8ac61fcf Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Thu, 19 Mar 2015 11:40:48 -0500 Subject: [PATCH 44/46] providers/aws: fix blockdevices acceptance test hashcodes just needed updating from latest hash func tweaks --- .../aws/resource_aws_instance_test.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 9e847c43b4f8..e4ba29807d43 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -111,33 +111,33 @@ func TestAccAWSInstance_blockDevices(t *testing.T) { resource.TestCheckResourceAttr( "aws_instance.foo", "root_block_device.#", "1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.3018388612.device_name", "/dev/sda1"), + "aws_instance.foo", "root_block_device.1246122048.device_name", "/dev/sda1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.3018388612.volume_size", "11"), + "aws_instance.foo", "root_block_device.1246122048.volume_size", "11"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.3018388612.volume_type", "gp2"), + "aws_instance.foo", "root_block_device.1246122048.volume_type", "gp2"), resource.TestCheckResourceAttr( "aws_instance.foo", "ebs_block_device.#", "2"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.418220885.device_name", "/dev/sdb"), + "aws_instance.foo", "ebs_block_device.2225977507.device_name", "/dev/sdb"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.418220885.volume_size", "9"), + "aws_instance.foo", "ebs_block_device.2225977507.volume_size", "9"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.418220885.volume_type", "standard"), + "aws_instance.foo", "ebs_block_device.2225977507.volume_type", "standard"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.1877654467.device_name", "/dev/sdc"), + "aws_instance.foo", "ebs_block_device.1977224956.device_name", "/dev/sdc"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.1877654467.volume_size", "10"), + "aws_instance.foo", "ebs_block_device.1977224956.volume_size", "10"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.1877654467.volume_type", "io1"), + "aws_instance.foo", "ebs_block_device.1977224956.volume_type", "io1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.1877654467.iops", "100"), + "aws_instance.foo", "ebs_block_device.1977224956.iops", "100"), resource.TestCheckResourceAttr( "aws_instance.foo", "ephemeral_block_device.#", "1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ephemeral_block_device.2087552357.device_name", "/dev/sde"), + "aws_instance.foo", "ephemeral_block_device.1692014856.device_name", "/dev/sde"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ephemeral_block_device.2087552357.virtual_name", "ephemeral0"), + "aws_instance.foo", "ephemeral_block_device.1692014856.virtual_name", "ephemeral0"), testCheck(), ), }, From 6c62e238290caf80fe0ed9cd64ca368ebe523b54 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Thu, 19 Mar 2015 13:14:31 -0500 Subject: [PATCH 45/46] providers/aws: fix bug w/ empty block dev mapping fixes #1249 --- .../providers/aws/resource_aws_instance.go | 4 ++ .../aws/resource_aws_instance_test.go | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index 40e6d64b0156..27edd3256c16 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -744,6 +744,10 @@ func readBlockDevicesFromInstance(instance *ec2.Instance, ec2conn *ec2.EC2) (map } } + if len(instanceBlockDevices) == 0 { + return nil, nil + } + volIDs := make([]string, 0, len(instanceBlockDevices)) for volID := range instanceBlockDevices { volIDs = append(volIDs, volID) diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index e4ba29807d43..638fceb374bc 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -14,6 +14,7 @@ import ( func TestAccAWSInstance_normal(t *testing.T) { var v ec2.Instance + var vol *ec2.Volume testCheck := func(*terraform.State) error { if *v.Placement.AvailabilityZone != "us-west-2a" { @@ -35,6 +36,21 @@ func TestAccAWSInstance_normal(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ + // Create a volume to cover #1249 + resource.TestStep{ + // Need a resource in this config so the provisioner will be available + Config: testAccInstanceConfig_pre, + Check: func(*terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + var err error + vol, err = conn.CreateVolume(&ec2.CreateVolumeRequest{ + AvailabilityZone: aws.String("us-west-2a"), + Size: aws.Integer(5), + }) + return err + }, + }, + resource.TestStep{ Config: testAccInstanceConfig, Check: resource.ComposeTestCheckFunc( @@ -45,6 +61,8 @@ func TestAccAWSInstance_normal(t *testing.T) { "aws_instance.foo", "user_data", "3dc39dda39be1205215e776bad998da361a5955d"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ebs_block_device.#", "0"), ), }, @@ -61,8 +79,19 @@ func TestAccAWSInstance_normal(t *testing.T) { "aws_instance.foo", "user_data", "3dc39dda39be1205215e776bad998da361a5955d"), + resource.TestCheckResourceAttr( + "aws_instance.foo", "ebs_block_device.#", "0"), ), }, + + // Clean up volume created above + resource.TestStep{ + Config: testAccInstanceConfig, + Check: func(*terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + return conn.DeleteVolume(&ec2.DeleteVolumeRequest{VolumeID: vol.VolumeID}) + }, + }, }, }) } @@ -393,6 +422,20 @@ func TestInstanceTenancySchema(t *testing.T) { } } +const testAccInstanceConfig_pre = ` +resource "aws_security_group" "tf_test_foo" { + name = "tf_test_foo" + description = "foo" + + ingress { + protocol = "icmp" + from_port = -1 + to_port = -1 + cidr_blocks = ["0.0.0.0/0"] + } +} +` + const testAccInstanceConfig = ` resource "aws_security_group" "tf_test_foo" { name = "tf_test_foo" From 3d8005729d3e307535ea505c63fc238aae7beb87 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 19 Mar 2015 15:10:49 -0500 Subject: [PATCH 46/46] provider/aws: Fix dependency violation with subnets and security groups Though not directly connected, trying to delete a subnet and security group in parallel can cause a dependency violation from the subnet, claiming there are dependencies. This commit fixes that by allowing subnet deletion to tolerate failure with a retry / refresh function. Fixes #934 --- builtin/providers/aws/resource_aws_subnet.go | 37 +++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/builtin/providers/aws/resource_aws_subnet.go b/builtin/providers/aws/resource_aws_subnet.go index d1db5aed9816..4922b035f699 100644 --- a/builtin/providers/aws/resource_aws_subnet.go +++ b/builtin/providers/aws/resource_aws_subnet.go @@ -157,17 +157,38 @@ func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error { ec2conn := meta.(*AWSClient).ec2conn log.Printf("[INFO] Deleting subnet: %s", d.Id()) - - err := ec2conn.DeleteSubnet(&ec2.DeleteSubnetRequest{ + req := &ec2.DeleteSubnetRequest{ SubnetID: aws.String(d.Id()), - }) + } - if err != nil { - ec2err, ok := err.(aws.APIError) - if ok && ec2err.Code == "InvalidSubnetID.NotFound" { - return nil - } + wait := resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: "destroyed", + Timeout: 5 * time.Minute, + MinTimeout: 1 * time.Second, + Refresh: func() (interface{}, string, error) { + err := ec2conn.DeleteSubnet(req) + if err != nil { + if apiErr, ok := err.(aws.APIError); ok { + if apiErr.Code == "DependencyViolation" { + // There is some pending operation, so just retry + // in a bit. + return 42, "pending", nil + } + + if apiErr.Code == "InvalidSubnetID.NotFound" { + return 42, "destroyed", nil + } + } + + return 42, "failure", err + } + + return 42, "destroyed", nil + }, + } + if _, err := wait.WaitForState(); err != nil { return fmt.Errorf("Error deleting subnet: %s", err) }